背景
如果你习惯了使用RocketMQ这种自动挡管理消费位点,消息失败重试的方式。你再来使用kafka,会发现kafka这种手动挡的消费位点管理就没那么容易了
熟悉RocketMQ的小伙伴都知道RocketMQ已经默认帮我实现好了消息消费失败重试,消费位点自动提交,死信队列等功能,那么kafka是否也是如此呢?
kafka消费位点管理
kafka消费位点有两种管理方式
- 手动提交消费位点
- 自动提交消费位点
自动提交消费位点
想要设置自动提交消费位点我们只需要设置两个属性
- ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG 自动提交消费位点
- ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG 自动提交消费位点的时间间隔
一个简单的消费代码如下
自动提交消费位点有几个缺点
- 会出现重复消费:比如Consumer每5秒自动提交一次位移,如果在第4秒时,消费了消息,但是还没有提交位移,此时Consumer挂掉了,那么下次Consumer启动时,会从上次提交的位移开始消费,这样就会导致消息重复消费。 当然比如出现Rebalance也是会出现重复消费的情况
- 无法精准控制消费位点
手动提交消费位点
手动提交消费位点又分两种
- 同步提交(commitSync)
- 异步提交(commitAsync)
同步提交(commitSync)
同步提交的方式很简单,就是每次消费完通过调用API consumer.commitSync。
相关的代码如下:
同步提交的方式有一个缺点,调用commitSync()时,Consumer会处于阻塞状态,直到broker返回提交成功,严重影响消费性能。
异步提交(commitAsync)
异步提交的方式很简单,就是每次消费完通过调用API consumer.commitAsync。
commitAsync主要是提供了异步回调,通过回调来通知消费位点是否提交成功。
异步提交消费位点也有一些缺点,比如消费位点不能重复提交。因为提交位点失败后,重新提交位点可能更晚的消费位点已经提交了,这里提交已经是没有意义的了。
spring-kafka消息消费
可以看到不管是同步提交消费位点还是异步提交消费位点,都有一些问题,想要写出生产可用的消费代码,需要注意的细节非常多。
比如消费失败后的消息如何处理,是停止消费跳出循环,还是说记录消费失败的消息,人工处理等。
这里我们可以简单看看spring-kafka是如何消费消息的。
我们简单看看主流程代码:
图片
这里我们忽略源码的一些其他细节。只分析主要的消费流程。
- invokeOnMessage(cRecord); 处理消息
可以看到invokeOnMessage是被整个try-catch包裹的,这样就保证了消费失败后不会影响整个消费流程。
具体我们先看看消息正常处理的逻辑。
这里主要是一些异常校验,然后就是判断是否可以提交消费位点。如果可以则调用doProcessCommits()进行正常的消费位点提交。
- doProcessCommits() 消费位点处理
如果消费位点提交失败也会进行一些异常处理。
如果消费位点提交失败则会调用commonErrorHandler进行异常处理。
commonErrorHandler有多个实现类,有一个默认实现DefaultErrorHandler
- 消息消费失败异常处理
如果消息消费失败,也提供了一个异常处理扩展invokeErrorHandler(cRecord, iterator, e);
里面实际使用的也是DefaultErrorHandler
核心的处理逻辑主要还是在SeekUtils中封装
- DefaultErrorHandler
- SeekUtils
可以看到有一个RecoveryStrategy参数,这个是消息消费失败如何恢复,比如我们需要手动增加一个类似死信队列的topic,这里消息消费失败就会自动发送到我们的死信队列
死信队列的topic名字生成规则主要是topicName + -dlt
总结
可以看到如果我们单纯的使用kafka-client原生的sdk来进行消息消费,是非常容易出现问题的。
我们需要很多细节,比如
- 消息消费失败了如何处理,是否需要重试,如果重试还是失败怎么办?丢掉还是手动处理丢到自己创建的死信队列中。
- 消费位点提交失败了如何处理。
- 消费位点是使用同步提交还是异步提交?或者混合提交?
所以如果spring boot项目还是建议使用spring相关已经封装好的kafka sdk。
非必要尽量不要使用原生的kafka-client sdk。