消息队列选型:rocketmq or rabbitmq

1.目前我们用的activemq,面临一些问题

  • activemq高可用基于leveldb的复制,但activemq从5.*开始,leveldb已经不再有任何更新

The LevelDB store has been deprecated and is no longer supported or recommended for use. The recommended store is KahaDB
但官方推荐的KahaDB存储方案,目前没有原生的高可用支持。

  • 我司使用leveldb过程中,遇到一些怪异的问题:队列被消费后,日志不自动清理;删除queue后日志才清理;日志手动清理后,activemq记录的“空间使用百分比”不减少,需要重启才能显示正确的值;“空间使用百分比”一旦100%,集群将不可用。官网上很多人提了类似issue,但都已经不再支持处理。
  • leveldb不支持延迟消息。
  • leveldb在节点failover时,会出现重启后“空间使用百分比”一直不减少的情况,只有将整个集群停止后,清空所有leveldb目录,重新初始化才能解决(预计整个集群停服1分钟)

基于以上原因,我建议考虑新的队列方案:rabbitmq或者rocketmq。

2.rocketmq介绍

  • Nameserver负责broke的自动发现(和dubbo中的zk角色类似);nameserver在rocketmq的早期版本是直接用zk做的,后来自己开发;nameserver无状态,可任意个节点。
  • Producer cluster,可以理解为一个应用的n个节点,rocketmq中用producer cluster的主要作用是一个produder挂掉后,可以通知别的producer继续执行其未完成的事务;
  • Consumer cluster,也可理解为一个应用的n个节点,主要是实现n个consumer的负载均衡。
  • 1个cluster中可以n个topic,1个topic中可以有n个broken,1个broken中可以有1个master和n个slave。
  • Producer向topic 发送消息,n个broken负载均衡,横向扩展;某个broken中的master宕机后,master被摘除,消息负载到其他broken,但原broken的slave仍然可以提供消费服务。

  • Rocketmq中的queue和别的mq不同,不是activemq或rabbitmq中的给特定生产者和消费者配对的逻辑对象,而是为了提高并发性设置的消息存储的sharding。
  • 生产者向每个queue轮询发送消息,消费者负载均衡地消费这些queue;每个broken节点中的queue数目默认16,可自定义,数目必须超过consumer数,否则会出现多出的consumer无法消费的情况。
  • Rocketmq中会对每个消息加特定的tag,这些tag和别的mq中的queue的概念类似,用于生产者和消费者配对特定的消息。

示例代码

public class Producer {
    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("sina-www");
        producer.setNamesrvAddr("10.40.20.200:9876,10.40.20.201:9876");
        producer.start();
        for (int i = 0; i < 100000; i++) {
            Message msg = new Message("topic1", "sina.www.abc", ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
            SendResult sendResult = producer.send(msg);
            System.out.printf("%s%n", sendResult);
        }
        producer.shutdown();
    }
}

public class Consumer {
    public static void main(String[] args) throws InterruptedException, MQClientException {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("sina-www");
        consumer.setNamesrvAddr("10.40.20.200:9876,10.40.20.201:9876");
        consumer.subscribe("topic1", "sina.www.abc");
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,ConsumeConcurrentlyContext context) {
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        consumer.start();
    }
}

3.rabbitmq介绍

  • Exchange的职责类似于交换机,提供数据交换功能,将数据分发到特定的queue。
  • Binding: exchange和queue的绑定关系; exchange的不同类型,加上binging配置的routekey,可以实现exchange与queue的任意组合分发规则。
  • 一个cluster中每个节点都有相同的exchange和binding信息。
  • 每个exchange必须将消息发送至master queue,不管master是否与该exchange在一个节点;Master queue与slave queue自动同步,queue副本数可以指定,可以少于node数;Node宕机后,node上的master queue对应的别的节点的副本会升级为master。
     

示例程序

public class Producer {
    private static String queueName = "sina.goods.updatestock";
    public static void main(String[] args) throws Exception{
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("10.40.20.203");
        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setVirtualHost("/");
        factory.setPort(5672);
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare(queueName, true, false, false, null);
        channel.queueBind(queueName,"lbexchange", queueName);
        for (int i = 0; i < 100000; i++) {
            String message = "hello world + " + i;
            channel.basicPublish("lbexchange", queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
            System.out.println(" [x] Sent '" + i + "' ");
        }
        channel.close();
        connection.close();
    }
}

public class Consumer {
    private static String queueName = "sina.goods.updatestock";
    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("10.40.20.203");
        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setVirtualHost("/");
        factory.setPort(5672);
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare(queueName, true, false, false, null);
        QueueingConsumer consumer = new QueueingConsumer(channel);
        int prefetchCount = 1;
        channel.basicQos(prefetchCount);
        channel.basicConsume(queueName, true, consumer); 
        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
        }
    }
}

4.rabbitmq性能测试

因为rocketmq的性能肯定比rabbitmq强,因此只测试了rabbitmq的性能。
测试场景:10个生产者,10个消费者,消费者自动ACK,2节点集群,服务器配置8C8G
压测工具:github.com/rabbitmq/rabbitmq-perf-test
以下分别测试消息大小为10字节和1K、直连主节点和直连从节点,一共4种组合。

消息大小10字节,直连主节点
Cpu 负载:主65%,从25%

消息大小1K,直连主节点
Cpu 负载:主65%,从25%

消息大小10字节,直连从节点
Cpu 负载:主35%,从40%

消息大小1K,直连从节点
Cpu 负载:主30%,从35%

消息积压的表现
关闭消费者,让消息积压,消息大小1K

因为内存限制,消息积压到一定程度要page out到磁盘,因此生产者发送消息会有明显的抖动(page out时会block)。

5.使用总结

Rocketmq

  1. Rocketmq为高并发海量数据设计,架构很先进,支持容量横向扩展。
  2. Rocketmq没有我们传统意义上的queue对象,而是通过tag实现消息的分类。消息的负载数据(enqueue\dequeue\pending等)无法精确到tag维度,只能到broken或consumer group。
  3. 控制台没有包含到原生项目,而是在rocketmq-externals中;控制台更新缓慢,2017年6月第一版后再无新的版本;控制台做得很差
  4. 没有完全遵守AMQP,只支持java客户端;官方文档简陋。

Rabbitmq

  1. 一个2节点的集群能保守提供6-7K/S的请求量,性能满足要求(目前我司还没有单个应用队列吞吐超过该数量的请求);
  2. 准守AMQP 协议,支持多种客户端,如:Python、Ruby、.NET、Java、C、PHP等;
  3. 控制台很友好,很强大,能看每个queue最近24小时的生产、消费、积压走势图
     
发布了24 篇原创文章 · 获赞 25 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/sdmei/article/details/90646445