RocketMQ ACK机制浅析

648 阅读4分钟

背景

  • 在实际的业务需求开发中,我们更多的只是使用了Rocket Mq里消费者的消费功能,只需要重写消息监听器里的onMessage方法,对消息内容进行业务逻辑的处理。
  • 但是我们很少去关注当前消费者是怎么去队列里拉取消息的?一次性拉取多少消息?(单条拉取?还是批量拉取?),拉取到消息之后会进行消费,那么如果消费成功,下一次拉取是什么时候?从队列哪个位置开始拉取?如果消费失败呢?会不会重新拉取失败的消息再次消费?还是直接丢弃?................

问题

  • 消费者拉取队列消息时,是单条拉取还是批量拉取?
  • 批量消费时,如果消息全部消费成功,怎么保存消费进度(offset)?
  • 批量消费时,如果存在部分消息消费失败,怎么保存消费进度?
  • 批量消费时,消费到一半,因为迭代发布导致机器重启,消费进度没有及时提交,会存在什么问题?

浅析MQ ACK机制

消费方式

  • 并发消费:一个队列中的消息可同时被消费者的多个线程并发消费
  • 顺序消费:一个队列中的消息同一时间只能被一个消费者的一个线程消费,通过加锁达到顺序消费的效果

消费模式

  • 广播模式:消费者组里的每个消费者都能消费Topic下所有队列的消息(全量数据)

  • 集群模式:消费者组里的每个消费者只能消费部分队列的消息(部分数据)

  • offset:表示消费者组在当前消息队列的消费进度

ACK机制

  • Rocket Mq是通过offset来标记一个消费者组在队列上的消费进度,消费成功之后都会返回一个ack消息告诉broker去更新offset,但是RocketMQ并不是每消费一条消息就做一次ack,而是消费完批量消息后只做一次ack
  • 所以ACK机制是为了准确的告知Broker批量消费成功的信息并且更新消费进度
  • 那批量消费时具体是如何更新消费进度?
    • 每一条消息消费成功后,会按照当前消息最小的offset来更新本地的消费进度
    • 由5秒的定时任务将offset提交到Broker

  • 批量更新的好处在于不用每次消费成功都提交offset,提升了消费效率,但也会随着产生一下两个问题
    • 比如101消息由于一些原因没有消费完成,而 102 - 110 消息都消费完了,此时broker的消费进度仍然是101,如果此时该消费者宕机或者实例被kill,当前queue会通过负载均衡策略会重新被分配给其它的消费者,这个时候从broker拉取消息时,是从101开始拉取消费,但是实际102-110这9条消息已经消费完成,造成这9条消息重复消费
    • 既然是按批量来更新消费进度,但是那些虽然消费完成但是实际是失败的消息(主动返回RECONSUME_LATER和抛出异常)的消息是如何处理?
    • consumer在处理消费失败的消息时,会单独把该消息的信息通过rpc调用通知给broker,那broker会把该消息做重新的投递,从而做到了消息的重置机制

总结:ACK机制

    • 优点:防止消息丢失
    • 缺点:会造成消息重复消费(使用方做幂等)

问题解答

  • 消费者拉取队列消息时,是单条拉取还是批量拉取?
    • 批量
  • 批量消费时,如果消息全部消费成功,怎么保存消费进度(offset)?是每条消息消费成功之后都会提交一次offset?还是批量消息全部消费成功之后只提交一次offset?
    • 每一条消息消费成功会按照当前消息最小的offset来更新本地的消费进度
    • 由5秒的定时任务将offset提交到Broker
  • 批量消费时,如果存在消息消费失败,怎么保存消费进度?
    • 消费失败的消息也算消费成功(所以按照消费成功的逻辑保存消费进度),只不过失败的消息会重新投递给Broker的 RETRY Topic下(并非原来Topic)
  • 批量消费时,消费到一半,因为迭代发布导致机器重启,导致消费进度没有提交,会存在什么问题
    • 会导致当前队列被负载均衡到其他消费者消费时,发生重复消费问题,所以一般都我们业务方保证消费幂等