消息积压:业务突然增长,导致消息消费不过来怎么办?
消息积压指消息生产速度大于消费速度,导致消息在broker上存放。消息积压可能导致消息很久才被消费。对于时效性有要求的业务是不可容忍的。
消费者和分区的关系
- Kafka中,一个分区只能有一个消费者,但一个消费者可以同时消费多个分区。
- 如果有N个分区,最多有N个消费者,再增加消费者也不能提供消费速度
- 如果不足N个消费者,一些消费者会从多个分区拉数据
这种设计无法通过无限增加消费者来解决消息积压问题。如果没有这种限制就不会有消息积压了
确定分区数量
- 分区数量要保证生产者不会阻塞,同时消费者来的及消费消息
- 可以通过压测或者观测消息集群性能来确定
解决方案
解决消息积压要区分是临时积压还是永久积压
- 临时积压:是突然来的流量,导致消费者一时半会跟不上
- 永久积压:则是消费者消费速率跟不上生产速率
- 如果临时积压,并且消费者处理的时间可以接受,可以不解决
- 如果接受不了,或是永久积压。就需要尝试解决。最简单的方案是增加消费者到分区数量一致
增加分区
如果消费者和分区数量一致可以尝试增加分区。
- 增加新分区意味着可以增加新的消费者来增速
创建新的topic
有时候公司不允许增加分区,可以考虑创建新的topic
- 准备一个新topic,消费老topic的同时也消费新的topic。等老topic数据消费完后完全切换到新topic
- 或者创建新topic,把老topic上的消息转发到新topic上。只需要启动消费者消费新topic就可以了
创建新topic的难点是究竟需要几个分区,最佳办法就是压测来确定
新分区计算方式:假设所有生产者QPS是3000。一个消费者处理的QPS是200,那么就是3000÷200=15。考虑业务增长或者突发流量可以使用18到20个分区
优化消费者性能
不能增加消费者和分区的时候,可以考虑优化消费者性能,大体两种思路
- 消费者部署更好的实例上(不是我们能左右的)
- 优化消费者消费逻辑
方案
- 最开始考虑同一个业务的消息可能被不同的消费者消费,就引入了分布式锁
- 后面通过主动选择目标分区使相同业务总把消息发到同一分区,确保同一时间只有一个消费者处理一个业务的消息,就可以去掉分布式锁,没有分布式锁就不会因为等待分布式锁而导致消费速率下降
聚合消息和批量操作
- 这种方式适用于消费者可以改造成批量接口的场景
- 可以考虑不改造生产者,只改造消费者
- 把消费者改造成批量消费、批量提交偏移量
- 比如消费者一次性拉去100条消息,构造批量处理请求。请求处理成功后,再提交偏移量
异步消费(核心解决方案)
- 消费者弄一个消费线程,负责从消息队列拉消息。拉到的消息立刻转发给一个线程池,线程池有一些工作线程处理消息。
- 注意消息丢失的问题
消息丢失
- 消息丢失:指消费者线程取出消息后,要消费下一条就要先提交当前这条。这时可能出现一个问题:消费者线程提交了,但是工作线程还没处理就挂了。因为已经提交了,就算重启也是从下一条开始消费
- 解决方案:可以考虑批量提交的方法。消费者线程一次拉一批消息。比如说10条,然后不是立刻提交这10条消息,而是开十个线程并行处理这10条消息,等10条消息都处理完,再批量提交
- 问题:批量提交效果很好,但是会带来重复消费和部分失败的问题
重复消费
消费者线程拉去一批消息后,如果还没提交就挂了。当消费者恢复后,就会拉取同一批继续消费。
- 解决方案:保证处理消息的逻辑是幂等的。这样即使宕机导致消费被消费还没提交,也可以保证下次恢复时,重复处理不会引起业务问题
部分失败
要保证部分失败不会影响继续向前消费
- 第一种做法是要求工作线程立即重试。比如重试三次,或者用一个新的异步线程来重试
- 第二种做法是把消费失败的消息丢回消息队列,后面轮到它又会被处理,相当于重试