什么是offset(消息位点)
参考 Apache RocketMQ 主题和队列的定义,消息是按到达服务端的先后顺序存储在指定主题的多个队列中,每条消息在队列中都有一个唯一的Long类型坐标,这个坐标被定义为消息位点。
任意一个消息队列在逻辑上都是无限存储,即消息位点会从0到Long.MAX无限增加。通过主题、队列和位点就可以定位任意一条消息的位置,具体关系如下图所示
ref: RocketMQ 官方文档 rocketmq.apache.org/zh/docs/fea…
offset 记录在broker 还是本地
广播模式
在广播模式下,offset会存储在消费者本地。默认文件路径为当前用户主目录的.rocketmq_offsets/${clientId}/${group}/Offsets.json
为什么广播模式下offset 要存储在本地而不是broker 端呢?
在 RocketMQ 的广播模式下,每个实例都会从服务器上获取所有的消息,并将它们存储在本地的队列中。这样做的好处是可以提高消息的传输效率,因为不需要在服务器和客户端之间进行多次数据传输。
如果consumer 宕机了新连上的consumer从哪里消费呢?
核心是启动的consumer 能否在本地找到消费进度文件,如果可以的话,默认从消费进度的位置开始消费。否则默认从最新的位置开始消费。
ref: ost.51cto.com/posts/21100
集群模式
在集群模式下,offset 会存储在Broker 端,位置为store/config/consumerOffset.json。其格式如下图所示:
为什么集群模式下要存在Broker 端呢: 因为集群模式下对于某consumerGroup,一个QueueID中的消息只能被一个消费者实例消费到,如果由于重平衡导致这个queueID 转为被其他消费者实例消费时,broker 需要确保之前的消费进度被保留下来,从而保证消息尽可能不被重复消费。试想对于下图而言,如果Broker 端不保存消费进度的话,consumer1宕机后,consumer2 又得从头开始消费了。
RocketMQ消费时,到底从哪里开始消费
新创建的 ConsumerGroup 从哪里开始消费消息?对于集群模式。
5.x SDK
-
首次上线时默认会从服务器中的最新消息开始消费,也就是从队列的尾部开始消费。
-
再次重新启动后
-
如果broker 端保存的offset 还能在 commitlog 中找到(消息有效期为3天),则消费者会从服务器中保存的offset 开始消费。
-
如果broker端保存的offset 已经找不到了(比如超过3天),则消费者会从服务器中的最新消息开始消费,也就是从队列的尾部开始消费。
-
3.x/4.x SDK 则比较复杂
-
生产者上线发送消息在三天之内,那么消费者会从服务器中保存的第一条消息开始消费
-
如果发送的消息已经超过三天,则消费者会从服务器中的最新消息开始消费,也就是从队列的尾部开始消费。
-
再次重新启动后
-
如果broker 端保存的offset 还能在 commitlog 中找到(消息有效期为3天),则消费者会从服务器中保存的offset 开始消费。
-
如果broker端保存的offset 已经找不到了(比如超过3天),则消费者会从服务器中的最新消息开始消费,也就是从队列的尾部开始消费。
-
当消费失败的时候如何重新消费消息?
-
在集群模式下,消费的业务逻辑代码会返回消费失败状态,或者抛出异常,如果一条消息消费失败,则会按照设置的最大重试次数重试,之后该消息会被丢弃。
-
在广播消费模式下,广播消费仍然保证消息至少被消费一次,但不提供重发的选项。
offset的同步提交与异步提交
同步提交
consumer提交了其消费完毕的一批消息的offset给broker后,需要等待broker的成功ACK,收到ACK后,consumer才会继续获取并消费下一批消息。在等待ACK期间,consumer是阻塞的。
异步提交
consumer提交了消费完毕的一批消息的offset后,不需要等待Broker 的成功ack,consumer可以直接获取并消费下一批消息
offSet管理机制
-
Consumer成功消费消息后,会将messageQueue的offset进行更新,同时Consumer客户端会开启一个定时任务,每隔5s将messageQueue的offset同步给Broker
-
Broker接收到更新offset请求后,会更新本地维护offsetTable,通过也会开启一个定时任务,每隔5秒将ConsumerOffsetManager中的offsetTable持久化到consumerOffset.json文件中
offset 确定机制,如何确保不丢消息
consumer 每次处理完一批消息后,会获取消费成功最小的offset,并且提交到broker。从而保证未消费成功的消息的offset 一定不会提交到broker,因此一定不会漏消费。
比如对于如下case,消费者由于并发消费的缘故提前消费成功msg0 以及msg3~msg7。此时只会提交offset = 0 到broker端。
如果后续consumer 重启,那么consumer 会继续从offset = 1 开始消费,这会导致msg3~msg7 重复消费,但是不会导致漏消费。