问题场景
我们项目有个订单出库的业务,作为kafka消费者去消费商城项目给我们发的消息。
某次突然出现大批订单的出库状态没改的情况,拉日志一看,报了如下异常。
org.apache.kafka.clients.consumer.CommitFailedException: Commit cannot
be completed since the group has already rebalanced and assigned the
partitions to another member. This means that the time between
subsequent calls to poll() was longer than the configured
session.timeout.ms, which typically implies that the poll loop is
spending too much time message processing. You can address this either
by increasing the session timeout or by reducing the maximum size of
batches returned in poll() with max.poll.records.
并且一直在拉取重复的消息,导致新的消息没有消费掉
分析
kafka的Consumer 端还有一个参数,用于控制 Consumer 实际消费能力对 Rebalance 的影响,即 max.poll.interval.ms 参数。它限定了 Consumer 端应用程序两次调用 poll 方法的最大时间间隔。它的默认值是 5 分钟,表示你的 Consumer 程序如果在 5 分钟之内无法消费完 poll 方法返回的消息,那么 Consumer 会主动发起 “离开组” 的请求,此时就不会提交偏移量了,Coordinator 也会开启新一轮 Rebalance。
解决
个人发现有两种解决方式
- 使消费者poll的频率在单位时间内增大
这种方式,可以让消费者有更多发送心跳的次数,从而不让它挂。通过下面地方式来实现。
1.1 调整max.poll.records参数
调小每次poll消息的个数,从而缩短业务逻辑处理时长,最终减少下次再poll的等待时长
1.2 消费逻辑异步执行
将根据消息处理业务的service方法,放在异步线程里执行,使得每次poll到一批消息后,缩短业务逻辑处理时长,最终减少下次再poll的等待时长。
- 调整max.poll.interval.ms参数
这种方式没有实践过
后续问题
后来发现,由于改成了异步,业务上出现了并发问题。
场景是,收到消息后多个线程会同时去远程调用库存服务,导致库存服务插入重复的订单库存流水。
原因是,虽然分区只有一个,订阅的消费者(此时为feign调用方)也只有一个,但是被调用方有3个节点,所以小概率下会同时调用3个节点,而每个节点的逻辑是先判断有没有流水,没有就插入,此时都判断没有,所以各自插入一条。
解决
由于上述问题,最终还是改为同步,只是通过max.poll.records参数调小每次poll消息的个数了。
最终订阅的消费者(此时为feign调用方)收到消息后,会等每个消息业务处理完,再处理下一个消息,因此不再出现并发问题。