https://zhuanlan.zhihu.com/p/83505477
保证消息局部顺序消费的重点在于:
- 生产者组通过计算,将相同
ID
的订单消息发往同一个MessageQueue
- 消费者组通过分别位于
Broker
和客户端的两把锁,保证对该MessageQueue
内消息的顺序消费
发往同一个 MessageQueue
保证了该 MessageQueue
内消息是局部有序的,但是无法保证全局有序,想要全局有序?那这个 Topic
只能配一个 MessageQueue
,然后全部消息都发到这一个 MessageQueue
中。一般来说,局部有序已经可以满足绝大部分应用场景了。
生产端的保证达到了,下面就是消费端,依靠的是两把锁,分别位于 Broker
侧和消费者实例客户端侧。Broker
侧的锁是 MessageQueue
粒度的,保证同一时间至多只有一个消费者实例消费该 MessageQueue
。
你可能疑惑,本来不就是一对一的关系么?原因是在消费者组进行 Rebalance
的时候可能会造成某个时间窗口内单个 MessageQueue
被多个消费者实例同时消费,这里通过加锁限制了这种情况。一旦启动时加锁失败,意味着该 MessageQueue
还在被其他消费者实例锁定,因此不创建相应的消息拉取任务,等到锁被释放或者超时(默认 60s
)。加锁成功后消费者实例还会每隔 20s 定时锁定该 MessageQueue
一次。
消费者实例侧由于可能同时负责消费多个 MessageQueue
,因此采用了线程池消费消息,需要在客户端提供加锁的方式保证单个 MessageQueue
内的消息同一时间仅被一个线程消费。
Rebalance
消息队列系统中,经常会出现 Broker
实例的增删、Topic
的增减、Topic
下 MessageQueue
数目的增减、消费组实例数目的增减等情况,它们都会触发消费关系的重新分配,这个过程称之为 Rebalance
。
RocketMQ
的 Rebalance
机制有主动和被动之分,主动意为消费者实例每隔 20s
会定时计算自己的消费拓扑并和内存中的对比,一旦发现部分 MessageQueue
不再是自己负责消费,则停止对它的消息拉取任务;如果有新的 MessageQueue
变为自己负责,则创建对它的消息拉取任务。
被动意为,Broker
可以主动通知某个消费组下的所有实例,要求它们立即开始一次 Rebalance
,常用于新的消费者实例加入、或者 Broker 检测到有消费者实例心跳失联等情况,下面是一个消费者实例新加入的场景。
RocketMQ
的 Rebalance
由于部分时刻的视图可能存在不一致,因此单次 Rebalance
并不能完全保证一定达到最终效果,但是由于它是一种周期性的任务,所以最终系统里的 MessageQueue
会被分配完全。
RocketMQ
的 Rebalance
机制依靠客户端各自单独计算得到,Kafka
新版本中则依靠 Consumer Leader
单点计算后再上传至 Group Coordinator
,由它下发至每个消费者实例进行更新。
这两种方式各有优缺点,通常来说,单点计算可以最大程度减小视图不一致导致的频繁 Rebalance
现象(但也不能杜绝),但是缺点在于逻辑复杂,消费者组和 Broker
中都需要选取单点,一个负责计算一个负责下发通知;客户端计算实现上更简单,彼此独立,通过周期性任务最终也能完成重新分配的任务,但是由于客户端彼此获取的视图不做校验,因此可能存在由于视图不一致导致的重复消费和频繁 Rebalance
。