目录
常见问题汇总
1、RocketMQ,不同Group的消费者能消费相同topic的消息
2、RocketMQ中对同一个消费组设置不同的tag订阅关系,出现消息丢失的问题
官方告诉使用者:同一个消费组,必须保持订阅关系一致
丢失原因分析:
同组消费实例订阅不同topic丢失原因总结
同一组订阅topic 不一致,也会造成消息部分不消费;由于负载均衡消费,分发消息给了各个实例,而有的消费者实例刚好不是订阅该主题的。
消息过滤实现机制
消费端队列存储的是 tag 的 hashcode,不同的字符串得到的hashcode值可能一样,故在服务端是无法精确对消息进行过滤的,所以在RocketMQ中会进行两次消息过滤。
当客户端向服务端拉取消息时,服务端在返回消息之前,会先根据hashcode进行过滤,然后客户端收到服务端的消息后,再根据消息的tag字符串进行精确过滤。
那为什么会丢失消息呢?这其实和消息队列负载机制有关.
在RocketMQ中使用集群模式消费时,同一个消费组中的多个消费者共同完成主题中的队列的消费,即一个消费者只会分配到其中某几个队列,并且同一时间,一个队列只会分配给一个消费者,这样结合上面的的过滤机制,就会明显有问题,请看示例图:
其问题的核心关键是,同一个tag会分布在不同的队列中,但消费者C1分配到的队列为q0,q1,q0,q1中有taga和tagb的消息,但tagb的消息会被消费者C1过滤,但这部分消息却不会被C2消费,造成了消息丢失。
3、一个消费实例中是可以多线程处理消费消息的
单实例消费者也是多线程消费的,线程池默认最小线程20个,无界队列,所以不会开到最大64个线程,最多就20个线程消费
一个消费者实例拉取的消息数如果大于分批处理数,就分成多批给多个线程进行处理
4、rocketMQ 消费进度问题
rocketmq 进度更新,不受消费失败的影响,继续前进?
rocketMQ本地消费缓存队列偏移量: 消费缓存偏移量即以最小消费成功或者消费失败但成功回发重试消息的偏移量为准
不断消费更新本地缓存偏移量; broker上的远程偏移量是 异步线程检测操作进行远程更新的,并不是同步
消费端有拉取消息队列:不断循环拉取消息来进行消费;根据拉取进度进行循环拉取,不是以消费进度进行拉取; 与本地缓存消费队列 消费进度不同,后者是作用于更新服务端消费偏移量
因此,宕机等因素,可能导致服务端已消费偏移量不同步,导致重启后出现重复拉取消费的情况
消费者消费失败后,消息如何处理?
消费失败后,消费端会回发消费失败的消息(topic是不一样)给broker,broker收到后交给定时任务,由定时任务根据延迟消费策略,进行定时推送消费消息
5、rocketMQ 默认消费模式是 push or pull 模式?
就两种消费模式:push模式和pull模式;默认消费模式是push模式
其实,严格意思上,rocketMQ并没有实现push模式,而是对拉模式进行了一种包装。
push原理:消费端通过pull不断轮询broker获取消息。不存在新消息时,broker挂起消费端请求,直到有新消息,取消挂起,返回新消息给消费端。这样,基本和broker主动push做到接近的实时性。原理类似长轮询
两种模式各自优缺点
(1)pull 方式
优点:理论上可以减轻服务端压力
缺点:设置合适拉取消息的频率成为难点。设置的时间间隔过长,消息就可能有所延迟消费,并造成MQ服务端堆积的消息变多;设置过短,可能会产生很多无效的pull请求,造成一定的开销,影响整体性能
(2)push 方式
优点:主动推消息给消费端,可以让客户端比较实时的消费消息,延迟较小
缺点:在消费者处理消息较弱的时候,或处理单条消息耗时过长,会引起问题。慢消费问题,在MQ不断push消息到消费端,会可能导致消费端的缓冲区溢出,从而引起系统异常
6、rocketMQ再平衡过程中,是如何避免两个消费实例消费同一队列,即存在重复消费消息问题?
为啥会存在两个消费实例消费同一消息队列?
如果某个消费者网络不稳定等,导致其他消费者认为其掉线了,那么可能出现同一个时刻,有两个消费者同时消费同一个队列的情况。
再平衡过程是由消费端各自处理的、但执行会有先后快慢(毕竟是个定时任务,各自起始启动时间不一)。这样,就会造成一个消费端再平衡后,拥有了另一个还没来得及执行再平衡的消费端的某些消息队列。因此,有可能会造成重复消费问题
这种情况下,MQ是如何避免重复消费问题?
为防止两个消费实例重复消费同一队列同一消息,rocketMQ提供了对broker上的队列加锁的功能,消费端只有加锁才能进行消费,加锁后定时任务在锁过期前,又重复加锁,延长锁定时间,这样就避免了同时消费、重复消费问题。
ConsumeRequest的run()方法下有个判断:
this.processQueue.isLocked() && !this.processQueue.isLockExpired()
这个判断便是校验是否已经对broke上的队列加锁了,如果没有加锁,则禁止消费消息,需要执行else里面的逻辑:
ConsumeMessageOrderlyService.this.tryLockLaterAndReconsume(this.messageQueue, this.processQueue, 100);
tryLockLaterAndReconsume()会创建一个定时任务,定时任务完成两件事:
(1)向broker发送LockBatchRequestBody请求,请求将该broker上当前消费者消费的队列加锁;
(2)加锁成功后,重新调用submitConsumeRequest(),开启线程重新处理消息。
除了上面这种方式加锁之外,消费者还有一个定时任务,每隔20s启动一次,它是由ConsumerMessageOrderlyService.lockMQPeriodically()触发的,该任务获取当前消费者正在消费的所有队列,然后向这些队列所在的broker发送LockBatchRequestBody请求,请求对这些队列加锁。
无论是通过定时任务还是其他方式加锁,每次加的锁的过期时间是30s。而且同一个消费者可以重复对一个队列进行加锁。
在broker上对队列加的锁,消费者是不主动释放的,也就是说一个消费者加了锁之后,只能等待锁超时,然后大家再去竞争锁。消费者只有一种情况会主动释放锁,就是消费者消息消费结束,主动调用shutdown方法关闭。
7、消费确认,自动 or 手动?
rocketmq
仅支持手动确认。
消费手动确认前,业务逻辑发生异常,消息未成功消费,后续消费重试。
rabbitmq
同时具有自动确认和手动确认。
自动确认:不在乎消费者对消息处理是否成功,都会告诉队列删除消息。如果处理消息失败,实现自动补偿(队列投递过去,重新处理)
手动确认: 确认后才删除消息
kafaka
待补充