Kafka消息堆积解决思路

82 阅读2分钟

记录一次生产问题排查

2024年12月4日

一次发版之后,kafka的一个group突然开始堆积。

kafka消息堆积,最简单粗暴的方法是增加消费线程来加快消费的速度。但这个方法并不总能奏效,因为消费慢有很多方面的原因,有可能是系统CPU、内存使用率过高,也有可能是高并发读写数据库导致数据库压力过大,响应。如果瓶颈在读写数据库,那么再怎么增加消费线程也没办法,甚至会使问题更严重。

在增加消费线程之后,虽然堆积有所缓解,呈下降趋势,但下降速度仍然非常缓慢。

通过工具arthas查看线程执行情况,发现主要耗时是Redis,达到几十毫秒最高甚至几百毫秒。

什么?Redis竟然也能成为瓶颈?

查看Redis监控,CPU、内存使用率、QPS一切正常,平均响应时间也在10微秒级别。

经过思考,大致有2种可能:1. 网络延迟或带宽不足;2. 服务端Redis配置问题,导致请求阻塞。

虽然可能是第一个原因,但是第一个原因不太可能。

于是开始排查Redis配置。服务使用lettuce连接池,lettuce基于netty的异步IO,这里不展开。

lettuce默认多个线程共享同一个Redis连接,也就是单线程TCP通道。在高并发场景下,可能会导致性能瓶颈,当某些请求耗时较长时,其他请求会被阻塞,造成整体响应变慢。

解决方案是将lettuceConnectionFactory的shareNativeConnection设为false,通过禁用连接复用,避免线程竞争同一个TCP连接。

改完之后果然消费速度大大提高!瞬间就把堆积的几十万条消息消费完了!

但是shareNativeConnection这个参数不能无脑设为false:

  1. 如果redis访问不高或者周期很长,可能反而造成性能下降,因为每次Redis操作,LettuceConnectionFactory都会为当前线程创建一个独立的连接。不恰当的配置会导致连接反复建立/断开造成开销。
  2. 无法利用Netty的异步IO,shareNativeConnection设置为false之后,异步模型的优势减少,变成了更重的同步调用模型。
  3. 大量的连接池造成资源浪费。大部分get/set场景,共享连接完全足够

所以只有在高并发和存在阻塞操作的场景下,才会选择将shareNativeConnection设为false。