分布式消息中间件RocketMQ原理解析与集群模式实战

最近的项目开发中涉及到支付业务的模块需要用到MQ进行业务解耦以及把用户请求量削峰填谷,提高系统的可用性和可靠性,我们选择了RocketMQ来部署消息中间件集群,我也在此回顾和归纳下RocketMQ的相关知识。

什么是RocketMQ

阿里开源的分布式消息中间件,单机就能支持千万级的消息堆积,集群模式能满足海量消息堆积的场景

RocketMQ的特点

支持发布/订阅(Pub/Sub)和点对点(P2P)消息模型

在一个队列中可靠的先进先出(FIFO)和严格的顺序传递

支持拉(pull)和推(push)两种消息模式

单一队列百万消息的堆积能力

支持多种消息协议,如 JMS、MQTT 等

分布式高可用的部署架构,满足至少一次消息传递语义

提供 docker 镜像用于隔离测试和云集群部署

提供配置、指标和监控等功能丰富的 Dashboard

核心机制与原理

RocketMQ底层基于队列模型来实现消息收发功能。RocketMQ集群中包含4个模块:Namesrv, Broker, Producer, Consumer。

Namesrv: 存储当前集群所有Brokers信息、Topic跟Broker的对应关系。

Broker: 集群最核心模块,主要负责Topic消息存储、消费者的消费位点管理(消费进度)。

Producer: 消息生产者,每个生产者都有一个ID(编号),多个生产者实例可以共用同一个ID。同一个ID下所有实例组成一个生产者集群。

Consumer: 消息消费者,每个订阅者也有一个ID(编号),多个消费者实例可以共用同一个ID。同一个ID下所有实例组成一个消费者集群。

每30秒心跳机制

单个Broker跟所有Namesrv保持心跳请求,心跳间隔为30秒,心跳请求中包括当前Broker所有的Topic信息。Namesrv会反查Broer的心跳信息, 如果某个Broker在2分钟之内都没有心跳,则认为该Broker下线,调整Topic跟Broker的对应关系。但此时Namesrv不会主动通知Producer、Consumer有Broker宕机。

Consumer跟Broker是长连接,会每隔30秒发心跳信息到Broker。Broker端每10秒检查一次当前存活的Consumer,若发现某个Consumer 2分钟内没有心跳, 就断开与该Consumer的连接,并且向该消费组的其他实例发送通知,触发该消费者集群的负载均衡(rebalance)。

生产者每30秒从Namesrv获取Topic跟Broker的映射关系,更新到本地内存中。再跟Topic涉及的所有Broker建立长连接,每隔30秒发一次心跳。 在Broker端也会每10秒扫描一次当前注册的Producer,如果发现某个Producer超过2分钟都没有发心跳,则断开连接。

集群模式部署

单 master 模式 也就是只有一个 master 节点,称不上是集群,一旦这个 master 节点宕机,那么整个服务就不可用,适合个人学习使用。

多 master 模式 多个 master 节点组成集群,单个 master 节点宕机或者重启对应用没有影响。

优点:所有模式中性能最高

缺点:单个master节点宕机期间,未被消费的消息在节点恢复之前不可用,消息的实时性就受到影响。注意:使用同步刷盘可以保证消息不丢失,同时 Topic 相对应的 queue 应该分布在集群中各个节点,而不是只在某各节点上,否则,该节点宕机会对订阅该 topic 的应用造成影响。

多 master 多 slave 异步复制模式 在多 master 模式的基础上,每个 master 节点都有至少一个对应的 slave。master 节点可读可写,但是 slave 只能读不能写,类似于 mysql 的主备模式。 优点: 在 master 宕机时,消费者可以从 slave 读取消息,消息的实时性不会受影响,性能几乎和多 master 一样。 缺点:使用异步复制的同步方式有可能会有消息丢失的问题。

多 master 多 slave 同步双写模式 同多 master 多 slave 异步复制模式类似,区别在于 master 和 slave 之间的数据同步方式。 优点:同步双写的同步模式能保证数据不丢失。 缺点:发送单个消息 RT 会略长,性能相比异步复制低10%左右。 刷盘策略:同步刷盘和异步刷盘(指的是节点自身数据是同步还是异步存储) 同步方式:同步双写和异步复制(指的一组 master 和 slave 之间数据的同步) 注意:要保证数据可靠,需采用同步刷盘和同步双写的方式,但性能会较其他方式低。

在此举个可靠异步消息发布和消息订阅的例子  , 更多的实战经历后续文章继续发布

可靠异步消息

发送可靠异步消息
       
        DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
        // MQ服务器地址端口
        producer.setNamesrvAddr("localhost:9876");
        //初始化对象实例
        producer.start();
        producer.setRetryTimesWhenSendAsyncFailed(0);
        for (int i = 0; i < 100; i++) {
                final int index = i;
                //创建消息实体 包括 topic, tag and message body.
                Message msg = new Message("TopicTest",
                    "TagA",
                    "OrderID188",
                    "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));

                producer.send(msg, new SendCallback() {
                    @Override
                    public void onSuccess(SendResult sendResult) {
                        System.out.printf("%-10d OK %s %n", index,
                            sendResult.getMsgId());
                    }
                    @Override
                    public void onException(Throwable e) {
                        System.out.printf("%-10d Exception %s %n", index, e);
                        e.printStackTrace();
                    }
                });
        }
        //关闭不再使用的连接,释放资源
        producer.shutdown();

消费消息

        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("example_group_name");

        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);

        consumer.subscribe("TopicTest", "TagA || TagC || TagD");

        consumer.registerMessageListener(new MessageListenerOrderly() {

            AtomicLong consumeTimes = new AtomicLong(0);
            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs,
                                                       ConsumeOrderlyContext context) {
                context.setAutoCommit(false);
                System.out.printf(Thread.currentThread().getName() + " Receive New Messages: " + msgs + "%n");
                this.consumeTimes.incrementAndGet();
                if ((this.consumeTimes.get() % 2) == 0) {
                    return ConsumeOrderlyStatus.SUCCESS;
                } else if ((this.consumeTimes.get() % 3) == 0) {
                    return ConsumeOrderlyStatus.ROLLBACK;
                } else if ((this.consumeTimes.get() % 4) == 0) {
                    return ConsumeOrderlyStatus.COMMIT;
                } else if ((this.consumeTimes.get() % 5) == 0) {
                    context.setSuspendCurrentQueueTimeMillis(3000);
                    return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
                }
                return ConsumeOrderlyStatus.SUCCESS;

            }
        });

        consumer.start();
发布了117 篇原创文章 · 获赞 17 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/Jack__iT/article/details/103003972