架构模型
rocketmq各模块之间的关系模型
topic、broker、queue之间的关系模型
基本概念
-
Producer
负责生产消息的生产者,同一类的生产者组成一个生产者组(producer group) -
Consumer
负责消费消息的消费者,同一类的消费者组成一个消费者组(consumer group),一个消费者组只能订阅一个topic,消费者消费消息有两种方式:主动拉取消息和broker推送消息,rocketmqq支持两种消息模式:广播消费clustering(消息分发给消费者组内的所有消费者)和集群消息Broadcasting(消息平均分发个消费者组内的消费者) -
Topic
主题表示一类的消息,每个主题包含多条消息,每条消息只属于一个主题,主题的数据可以分片保存在不同的broker上,每一个分片包含一个或多个message queue,message queue是消费者消费消息和生产者生产消息的最小单位 -
Broker Server
负责接收、存储、转发消息,有两种集群模式:普通集群(master挂掉,slave无法切换,服务不可用)、Dledger集群(master挂掉,slave采用Raft算法选举出新的master) -
Name Server
类似zookeeper ,但是相比zookeeper轻量很多,broker server在启动时会向所有的name server发送自己的数据信息并通过心跳保证消息的实时性,生产者和消费者能通过name server找到topic对应的broker server的ip列表,可以用多个name server组成集群,但各个服务器之间都是独立的,没有信息交换,集群中只要有一台name server正常就可以提供服务 -
Messge
每个消息必须属于一个topic,消息有个属性叫tag,用于区分同一个topic下的不同类型的消息
rocketmq的特点
消息存储机制
broker接收producer生产的消息后会将消息持久化到磁盘上,采用顺序写保证消息存盘的速度,用零拷贝(mmap)的方式提高发送消息的速度,
rocketmq消息存储结构分为三个部分:
-
CommitLog
存储消息的元数据,单个文件的大小默认是1G,文件名长度为20位,以每次第一条写入文件的消息的起始偏移量为名,消息顺序写入 -
ConsumeQueue
存储消费消息的索引,保存了消息在commitlog中的起始物理偏移量offset、消息大小size、tag的hashcode,一个message queue对应一个consumeQueue文件,Consumer 可以根据 ConsumeQueue 来查找待消费的消息,ConsumeQueue文件夹的路径为topic/queue/file,consumeQueue每条记录固定20个字节,分别为 8 字节的 commitlog 物理偏移量、4 字节的消息长度、8 字节 tag hashcode,每个文件由 30W 个条目组成 -
IndexFile
提供了一种可以用key或时间区间查询消息的方法
消息存储结构模型图:
刷盘机制
broker接收消息后会将消息持久化到磁盘上,采用顺序写的方式提高读写性能,写磁盘有两种方式:同步刷盘和异步刷盘
-
同步刷盘
确认消息写入磁盘再返回写成功,消息写入内存的pagecache后通知刷盘线程刷盘,刷盘线程将消息写入磁盘后唤醒等待线程返回写成功,同步刷盘提高了消息可靠性降低了吞吐量 -
异步刷盘
不等消息写入磁盘就返回写成功,此时消息可能只是写入pagecache,等内存中存储一定量的消息后再写入磁盘,异步刷盘提高了性能和吞吐量但是有可能丢消息
可以调整Broker 的参数 FlushDiskType 来选择刷盘策略(ASYNC_FLUSH 或者 SYNC_FLUSH)
主从复制机制
如果部署了集群,那么broker的master和slave就需要同步数据,slave复制数据一般有两种方式:同步复制和异步复制
- 同步复制
客户端在master和slave都写成功后才收到写成功的响应,slave有master全量的数据,如果master故障,数据也是一致的,同步复制会增加写入的延迟,降低mq的吞吐量 - 异步复制
客户端在master写入成功后就会收到写成功的响应,master异步复制消息给slave,这样写延迟较低、吞吐量较高,但是如果master故障,来不及复制的消息就会丢失
负载均衡机制
- Producer负载均衡
producer会用递增取模的方式将消息发送到不同的message queue实现发送消息的负载均衡,也可以指定发送到某个message queue实现局部有序 - Consumer负载均衡
consumer有两种消费方式:集群模式和广播模式
集群模式:每条消息只会被订阅了Topic的consumer group下的一个consumer实例消费,consumer主动拉取消息消费并指定拉取的message queue,每当实例的数量有变更,都会触发一次所有实例的负载均衡,这个时候会按照queure的数量和实例的数量平均分配queue给每个实例
注意:集群模式下,每个queue只会分配给一个实例,consumer的实例数量不能大于queue的数量,否则多出的consumer无法消费消息 - 广播模式
每条消息都会投递给订阅了topic的所有消费者实例
消息重试机制
广播模式下,消息消费失败没有重试机制,集群模式下消息消费失败后期望消息重试,有三种方式:
- 返回 Action.ReconsumeLater
- 返回null
- 抛出异常
重试的消息会被分配到名称为“%RETRY%”+ConsumeGroup的队列中,rocketmq默认每条消息最多重试16次,自定义重试次数如果大于16次则16次后的重试时间间隔均为2个小时,重试的间隔时间如下:
如果消息重试16次之后仍然失败,消息将进入死信队列,消息最大重试次数的设置对相同GroupId下的所有consumer实例都有效,并且最后启动的consumer实例会覆盖之前启动的consumer的配置,在较老版本中,一条消息无论重试发送多少次,重试消息的MessageId始终都是一样的,
死信队列机制
消息重试超过最大次数后,消息会被投递到死信队列,名称为%DLQ%+ConsumGroup
死信队列的特征:
- 一个死信队列对应一个ConsumerGroup
- 如果一个ConsumerGroup没有产生死信消息,不会创建对应的死信队列
- 一个死信队列包含ConsumerGroup下的所有死信消息而不会按照Topic划分
- 死信队列中的消息不会再被消费者正常消息
- 死信队列的有效期默认3天,超过有消息则被删除不管是否被消费
消息幂等机制
在MQ系统中,对于消息幂等有三种实现语义:
- at most once 最多一次:每条消息最多只会被消费一次
- at least once 至少一次:每条消息至少会被消费一次
- exactly once 刚刚好一次:每条消息都只会确定的消费一次(最理想也是最难实现的)
消息重复发送的可能情况:
-
发送时消息重复
当一条消息已被成功发送到服务端并完成持久化,此时发生网络闪断或者客户端宕机,导致服务端应答失败,如果此时生产者认为消息发送失败重新发送消息,消费者会收到两条内容相同并且MessageId相同的消息 -
投递时消息重复
消息消费时,消息已投递给消费者,当客户端对服务端应答时网络闪断,rocketmq将在网络恢复后重新发送消息,消费者会收到两条内容相同并且MessageId相同的消息 -
负载均衡时消息重复(包括但不限于网络抖动、Broker重启、订阅方应用重启)
当消息队列RocketMQ的Borker或客户端重启、扩容或缩容时,会触发Rebalance,此时消费者课鞥呢会收到重复消息
RocketMQ无法保证每个消息只被投递一次,必须在业务中保证消息消费的幂等性,一个方案是可以用消息的唯一MessageId,或者由业务决定唯一的标识例如订单ID