RocketMQ调研笔记
毫无疑问,RocketMQ是目前最受欢迎的消息中间件之一,由淘宝中间件团队研发,目前已经广泛应用于淘宝,天猫,蚂蚁金服,口碑等各式各样的复杂业务。
为什么选择RocketMQ?
- 消息不丢失【重点】
- 稳定性高,吞吐量高,性能好
- 强大的技术团队,健壮的社区力量
- 架构主轻量,组件无状态
- 采用Java语言编写,技术体系上更加契合
概念
Producer
消息生产者。生产者将业务应用系统产生的消息投递到Broker机器上,投递方式有同步,异步,单向等。
Producer Group:
消息生产者组。多个相同的生产者事例集群为一组。同一个生产者组的不同生产者实例可以被Broker处理以提交或者回滚(事务保证)。生产者组只允许一个实例存在,即不支持生产者组的集群处理。
Consumer
从Broker集群上拉取消息进行处理。并且提供了两种模型:
- 拉模型,consumer集群主动从broker拉去数据
- 推模型,在拉模型的基础上进行处理
Consumer Group
消息者组,支持负载均衡,容错系统。消费者组的消费者实例必须具有完全相同的topic订阅。
Topic
topic是producer与consumer投递消息与拉取消息的区分点,但是topic与producer与consumer之间的关系非常松散。具体而言,一个topic可能有零个,一个或多个向其发送消息的producer; 相反,producer可以发送不同topic的信息。从consumer的角度来看,一个subject可能由零个,一个或多个consumer群体订阅。同样,一个consumer群体可以订阅一个或多个topic,只要这个群体的实例保持其订阅的一致性。
Broker
它接收producer发送的消息,存储消息并准备处理来自consumer的请求(consumer拉模型)。它还存储消息相关的元数据,包括消费者组,消费进度偏移和topic队列信息。
Name Server
用来保存topic的路由信息。
- NameServer用来保存活跃的broker列表,包括Master和Slave。
- NameServer用来保存所有topic和该topic所有队列的列表。
- NameServer用来保存所有broker的Filter列表。
消息模型
集群、广播
消息顺序
有序:费消息有序意味着消息被消费者按照消息队列发送的顺序进行消费。如果您正在处理强制使用全局顺序的情况,请确保您使用的topic只有一个消息队列。警告:如果指定消耗有序,则消费消息的最大并发度是消费者组订阅的消息队列的数量。
无序:同时使用消息时,消息消息的最大并发性仅受限于为每个客户端指定的线程池。警告:在此模式下不再保证消息顺序。
RocketMQ架构设计
架构上分为四个部分,由producer,consumer,namesrv以及broker组成。
生产者
SendStatus,包含于SendResult中,表示消息的投递状态
-
FLUSH_DISK_TIMEOUT
:表示如果Broker配置了FlushDiskType(刷盘策略)为SYNC_FLUSH(同步刷盘),并且Broker在默认时间(syncFlushTimeout刷盘超时时间)内没有完成刷新磁盘则返回该状态。 -
FLUSH_SLAVE_TIMEOUT
:表示如果Broker配置了SYNC_MASTER,没有在默认时间(syncFlushTimeout刷盘超时时间)内与主Broker完成同步,则返回该状态。 -
SLAVE_NOT_AVAILABLE
:表示Broker配置了SYNC_MASTER,但是没有配置从库,则返回该状态。 -
SEND_OK
:表示成功。但是它不代表这可靠,为了确保不会丢失任何消息,您还应该启用SYNC_MASTER或SYNC_FLUSH(指刷盘策略)。
Duplication or Missing
如果投递消息失败,返回状态为:FLUSH_DISK_TIMEOUT
或者 FLUSH_SLAVE_TIMEOUT
- 如果选择继续执行程序,则消息丢失。
- 否则选择重发该消息,但是可能会导致消息重复。建议重发,因为可以通过其他手段来规避消息重复消费问题。
producer工作机制
producer
定期从namesrv
获取可用的topic
路由信息,包括可用的broker
列表,并缓存在本地。producer
发送消息时通过轮训的方式从namesrv
上拉取的路由信息中选择一个可用的broker
,根据某种策略把消息发送到该broker
。如果namesrv
挂掉,producer
就使用本地的topic
路由信息,如果此时producer
重启了,那就没有topic
路由信息了,也就无法发送消息。
消费者
Consumer Group and Subscriptions
不同的Consumer Group可以独立消费相同的topic,并且在消费失败之后都可以有各自的消费补偿(重试机制)。
请确保同一个Consumer Group中的每个consumer实例订阅相同的topic。
MessageListener
-
Orderly
可以保证消息顺序地被消费,因为它会将锁住每一个消息队列并依次消费。如果不是对顺序消息有极强的要求,不建议使用。异常请使用ConsumeConcurrentlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT
-
Concurrently
不保证消息的顺序问题,consumer同时拉取消息并消费。异常请使用:ConsumeConcurrentlyStatus.RECONSUME_LATER
ConsumeFromWhere 如果新建一个consumer group ,需要确认是否消费broker保存的历史数据
-
CONSUME_FROM_LAST_OFFSET
将忽略历史消息,并消耗之后产生的任何东西。 -
CONSUME_FROM_FIRST_OFFSET
将消耗Broker中存在的所有消息。 -
CONSUME_FROM_TIMESTAMP
消费在指定的时间戳之后产生的消息。
Duplication 重复消费
建议通过业务上的其他手段来保证消息的不重复消费,因为在消息队列在消费体系中有非常多的情况会出现消息重复消费的问题。
服务注册与发现
服务注册与发现是一个无状态的组件,即namesrv与namesrv相互隔离,没有心跳检查,没有主从复制,没有主从选举等问题。
broker
brokerRole
ASYNC_MASTER
SYNC_MASTER
SLAVE
如果用户无法容忍消息丢失,那么建议用户部署一个同步主库并配上一个从库来处理。如果用户可以容忍消息缺失但是一定要保证Broker的高可用状态,那么建议用户部署一个异步主库配置一个从库来处理。如果用户只想要简单上手使用(不谈高可用,零容错等问题),建议用户只需要配置一个异步主库即可处理。
FlushDiskType
ASYNC_FLUSH
SYNC_FLUSH
推荐使用ASYNC_FLUSH
,因为SYNC_FLUSH
代价昂贵,会造成太多的性能损失。如果你想要可靠性,我们建议你使用SYNC_MASTER
和SLAVE
。
broker工作机制
broker
负责存储消息,存储队列信息,维护消费进度,定时向namesrv
上报topic
路由信息。如果一台broker
挂掉,那producer
不会马上感知到,namesrv
也不会立即把这个broker
从它维护的可用broker
列表摘除,而是要等到broker
心跳超时后才会摘除。摘除后,producer
下次从namesrv
获取最新的可用的broker
列表时,才会发现此broker
不存在了,然后也就不会再发送消息到此broker
。在此之前broker
挂掉的这段时间内,producer
还是会发消息到这个已经挂掉的broker
的,但是producer
内部有重试机制,如果发送到某个broker
失败几次,那就会选择其他可用broker
来发送。所以,对于用户而言,最终发送消息都是成功的。broker
到namesrv
的心跳超时时间,以及producer
到namesrv
定时拉取topic
路由信息的时间,都可配置。
弊端
- 主从复制不支持
master
选举,master
宕机后消息服务近乎不可用(slave
可继续消费旧的消息) - 无法做到真正意义上的读写分离,只有当
master
消费性能太低时(由RocketMQ决定)才会将读请求分摊到slave
上 - 主从数据不一致,有可能造成数据重复消费