messageModel有两种方式:BROADCASTING 和 CLUSTERING,

消费者收到消息也有两种消费方式:orderly和concurrently,

1、BROADCASTING模式下,所有注册的消费者都会消费,而这些消费者通常是集群部署的一个个微服务,这样就会多台机器重复消费。

2、在CLUSTERING模式下,如果一个topic被多个consumerGroup消费,也会重复消费。

3、即使是在CLUSTERING模式下,同一个consumerGroup下,一个队列只会分配给一个消费者,看起来好像是不会重复消费。但是,有个特殊情况:一个消费者上线后,同组的所有消费者要重新负载均衡(反之一个消费者掉线后,也一样)。一个队列所对应的新的消费者要获取之前消费的offset,此时之前的消费者可能已经消费了一条消息,但是并没有把offset提交给broker,那么新的消费者可能会重新消费一次。虽然orderly是前一个消费者先解锁,后一个消费者加锁再消费的模式,比起concurrently要严格了,但是加锁的线程和提交offset的线程不是同一个,所以还是会出现极端情况下的重复消费。

4、默认情况下orderly和concurrently模式都是一条一条的消费,但是如果在消费一条消息的时候要进行比如AB两个操作,在没有事务控制的情况下,如果A操作成功而B操作失败,就会重新消费导致A操作会再执行一次,这样虽然不是重复的消费整条消息,但是一部分的操作会重复。如果放宽条件到一次消费多条的情况,orderly模式是一批中有一条消费失败,一批统一重新消费,直到达到最大消费次数的限制,发送到死信队列。而concurrently情况下,在返回成功(CONSUME_SUCCESS)的前提下,有个ackIndex可以分隔成功和失败的消息,失败的、没有消费的消息发送到retry队列,不会造成重复消费,而如果返回的是(RECONSUME_LATER),仍然是和orderly一样的同批次全部重新消费。(其实orderly也可以这样做,但是不知道为什么没有,而是全部重新放入队列消费)。

5、消费者pullRequest发出去,如果长时间收不到请求,是会被取出来重新再放入队列再请求一次的,所以也是会重复拉取消息的。

注:orderly和concurrently消费失败的处理:

orderly:没有ackIndex,整批消费成功/失败,如果失败,必须是整批的重新消费次数都达到最大值,然后整批发送到重试队列(broker那边会放到死信队列),如果并不满足这个苛刻的条件,整批都会重新消费。如果都被发送回死信队列,commitOffset会包含这批消息,否则还是之前的。

concurrently:有context可以记录一批内,成功的和失败的分界线ackIndex,如果是广播模式,丢掉不管。如果是集群模式,把失败的发送回重试队列,如果发送失败,收集起来再放到消费队列里重新消费。集群模式下,记录下一个待消费的offset是消费失败并且发送回去失败的那一批消息的最小offset。