浅谈RocketMQ 原理-源码流程

3 篇文章 0 订阅

浅谈RocketMQ 原理及使用

官网:http://rocketmq.apache.org/

背景

1、为什么使用 MQ

MQ 的作用:

1)削峰填谷

2) 异步处理

3) 系统解耦

2、 为什么使用rocketMQ

早期阿里曾经基于ActiveMQ研发消息系统, 随着业务消息的规模增大,瓶颈逐渐显现,后来也考虑过Kafka,但因为在低延迟和高可靠性方面没有选择,最后才自主研发了RocketMQ, 各方面的性能都比目前已有的消息队列要好,RocketMQ和Kafka在概念和原理上都非常相似,所以也经常被拿来对比;RocketMQ默认采用长轮询的拉模式, 单机支持千万级别的消息堆积,可以非常好的应用在海量消息系统中。

优点: 高吞吐,低延迟,高可用,事务消息,顺序消费

对比:

 

 

 

 

概述

 

系统架构

image-20210425165941552

 

概念

Name Server

提供路由信息。NameServer 无状态节点,节点之间无信息同步。类似于简单的注册中心(zk 的服务发现)

Broker

rabbitMQ 也有broker ,与rabbitMQ broker功能类似。 Broker 主要 接收 producer 消息,存储消息。push 给 consumer或者 接收处理 consumer poll消息请求;

部署时,Broker 分为 Master 和 slave (一对多) ,通过broker name关联。每个 broker 都需要与nameserver 集群中所有服务器连接,用于同步注册的topic信息到nameserver,Name Server定时(每隔10s)扫描所有存活broker的连接,如果Name Server超过2分钟没有收到心跳,则Name Server断开与Broker的连接。

Producer

生产者,发送消息。Producer与Name Server集群中的其中一个节点(随机选择)建立长连接,定期从Name Server取Topic路由信息,并向提供Topic服务的Master建立长连接,且定时向Master发送心跳。Producer完全无状态,可集群部署。

Consumer

Consumer与Name Server集群中的其中一个节点(随机选择)建立长连接,定期从Name Server取Topic路由信息,并向提供Topic服务的Master、Slave建立长连接,且定时向Master、Slave发送心跳。Consumer既可以从Master订阅消息,也可以从Slave订阅消息,订阅规则由Broker配置决定。

 

由于NameServer 只存储路由信息,无状态,且NameServer 之间无数据同步。所以NameServer 中的数据,依赖于broker同步。由于没有类似zk的watch等机制,所以需要与broker, producer, consumer 等所有连接之间建立心跳机制。

单个broker 跟所有NameServer保持心跳请求,心跳间隔为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)。 Producer 每30秒从Namesrv获取Topic跟Broker的映射关系,更新到本地内存中。再跟Topic涉及的所有Broker建立长连接,每隔30秒发一次心跳。 在Broker端也会每10秒扫描一次当前注册的Producer,如果发现某个Producer超过2分钟都没有发心跳,则断开连接。

 

事务消息

1、为什么需要事务消息?

生产着 与 MQ:

生产者本地操作 与 消息 投递 一致性。

本地成功,消息发送失败;或者消息发送成功,本地失败;

消费者与MQ:

消费者本地业务操作 与 消息接收 保持一致性

存在接收消息,消费者执行失败场景。

 

2、rocketMQ 是如何实现事务消息的

1、消费者,通过失败重试消费。

2、生产者与MQ 保持一致:

image-20200604142839587

步骤5,通过 生产者 实现 TransactionCheckListener 监听 来实现。

// 代码示例

 

顺序消费

1、为什么需要顺序消费?

某些业务场景下,业务操作需要按顺序执行。如 新增,更新,查询,删除, 4条消息。如果生产不按照顺序消费,则最终结果会与预期不一致。

2、如何实现顺序消费?

消息队列(Message Queue),队列 queue 本质上 FIFO 就是按照顺序推送消息。

有不按顺序消费问题,MQ支持多队列。以及在分布式环境下,多机消费,或者多线程消费。无法保证消费顺序

produce在发送消息的时候,把消息发到同一个队列(queue)中,消费者注册消息监听器为MessageListenerOrderly,这样就可以保证消费端只有一个线程去消费消息。

// 代码示例

 

 

 

原理分析

发送消息流程

producer 发送普通消息。

DefaultMQProducerImpl:sendDefaultImpl

1、校验producer状态正常,校验消息格式

2、根据 topic ,从NameServer 中获取路由信息(路由信息中包含broker以及queues信息)

3、组装发送到broker的请求,并根据不同发送方式发送请求到broker,

4、处理发送结果

 

producer 发送事务消息

DefaultMQProducerImpl:sendMessageInTransaction

1、校验 producer是否正确配置 TransactionListener,校验消息格式

2、组装 Half 消息并发送

3、如果发送消息正常,则执行本地事务逻辑,记录本地事务执行后的结果状态;如果消息发送失败,则标记本地事务为回滚

4、根据本地事务状态,决定是进一步发送消息到broker,还是回滚

5、返回发送结果

 

Broker 接收处理producer 发送消息请求

SendMessageProcessor:processRequest

Broker 职责,需要处理 producer请求,并存储消息(存储消息,后续流程再发送到消费者)

1、校验 消息中 topic 配置

2、如果请求中的消息队列编号小于0,则随机选择一个消息队列

3、处理重试的消息请求,如果重试次数大于配置的最大重试次数,则将消息后续放入到死信队列处理

4、创建broker 内部的消息请求,并区分是否为事务消息,区别存储消息到 commitLog 中

5、处理最终的发送结果,并把结果响应给producer

 

 

 

Consumer 从 broker 拉取消息

RocketMQ 提供了 push 和 poll 两种模式。但是主要是依靠 consumer 不断的轮询拉取,达到 broker push消息的效果

DefaultMQPushConsumerImpl:pullMessage

1、 PullMessageService 是 Consumer 从 broker 拉取消息

2、拉取消息后,两个操作:1)添加消息到ProcessQueue中;2)提交消息消费任务

3、ConsumeMessageService ,进行消息消费 (核心类:ConsumeRequest)

4、消费之后的处理:1)消费失败,则提交消息消费任务可进行重试,及发送消费失败消息到broker;2)消费成功的消息,则从 processQueue 中移除;3)更新消费进度,定时同步到broker

 

 

消息存储

RocketMQ 最重要 最核心的 消息存储

CommitLog 与 ConsumeQueue

消费逻辑图

 

CommitLog:消息存储主体,存储producer写入的消息。单个文件大小默认1G ,文件名长度为20位,左边补零,剩余为起始偏移量,比如00000000000000000000代表了第一个文件,起始偏移量为0,文件大小为1G=1073741824;当第一个文件写满了,第二个文件为00000000001073741824,起始偏移量为1073741824,以此类推。消息主要是顺序写入日志文件,当文件满了,写入下一个文件;

ConsumeQueue:逻辑队列,不实际存储消息内容,可以理解为消息的索引,ConsumeQueue 保存了指定Topic下的队列消息在CommitLog中的起始物理偏移量offset,消息的size以及消息tag的hashCode值。

 

数据和索引部分相分离的存储架构设计的优点:高性能的消息持久化刷盘;消息查询等性能高;消费者消费消息性能高。

1、如果没有commitLog,ConsumeQueue 实际存储消息内容,则不利于统一的消息持久化,不利于事务消息处理,消息查询等

2、如果只有commitLog,没有逻辑队列ConsumeQueue,由于RocketMQ是基于主题topic的订阅模式,消息消费是针对主题进行的,如果要遍历commitlog文件中根据topic检索消息是非常低效的。

 

commitLog 文件的存储与读取

 

顺序消费的实现

Producer : 把需要保证顺序的消息,放到同一队列中

Consumer 严格的顺序消费:

Broker 消息队列锁(分布式锁):

1)集群模式下,consumer 需要先从broker获取锁之后,才能进行消息的拉取和消费;

2) 广播模式下,Consumer 则无需该锁

Consumer 消息队列锁(consumer本地锁):Consumer 获得该锁才能操作消息队列。

Consumer 消息处理队列消费锁(本地锁) :Consumer 获得该锁才能消费消息队列。

 

Consumer 消费流程:

ConsumeRequest

1、获取broker 分布式锁。

2、获取consumer消费队列锁

3、获得消费消息

4、获取消息队列消费锁

5、消费消息

6、释放消费锁

7、处理消费结果

8、循环执行步骤3

9、循环结束,则释放消费队列锁。循环结束的条件: 未获取broker分布式锁或者获取的分布式锁已经过期。

 

事务消息的核心处理,事务回查

(1) 处理存储事务消息时,会备份消息及主体。然后生成新的half topic,这样消息就不会被消费

RocketMQ的具体实现策略是:写入的如果事务消息,对消息的Topic和Queue等属性进行替换,同时将原来的Topic和Queue信息存储到消息的属性中,正因为消息主题被替换,故消息并不会转发到该原主题的消息消费队列,消费者无法感知消息的存在,不会消费

 // CommitLog:asyncPutMessage
 if (tranType == MessageSysFlag.TRANSACTION_NOT_TYPE
                || tranType == MessageSysFlag.TRANSACTION_COMMIT_TYPE) {
            // Delay Delivery
            if (msg.getDelayTimeLevel() > 0) {
                if (msg.getDelayTimeLevel() > this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel()) {
                    msg.setDelayTimeLevel(this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel());
                }
​
                topic = TopicValidator.RMQ_SYS_SCHEDULE_TOPIC;
                queueId = ScheduleMessageService.delayLevel2QueueId(msg.getDelayTimeLevel());
​
                // Backup real topic, queueId
                MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_TOPIC, msg.getTopic());
                MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_QUEUE_ID, String.valueOf(msg.getQueueId()));
                msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties()));
​
                msg.setTopic(topic);
                msg.setQueueId(queueId);
            }
        }

(2) broker 定时发起事务回查,检查producer本地事务执行状态

定时发起任务回查

TransactionalMessageCheckService:onWaitEnd
TransactionalMessageServiceImpl:check
​

实际检查本地事务状态:

DefaultMQProducerImpl:checkTransactionState

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值