1 问题现象
某微服务,使用 log4j kafka appender 写日志到 kafka 中。业务同学反馈,微服务日志中有频繁出现类似如下的kafka连接断开的日志,希望分析下kafka连接断开的原因,并确认是否会因此丢失微服务日志。
2024-08-08 07:10:06.994 -INFO [kafka-producer-network-thread | producer-1] org.apache.kafka.clients. NetworkClient -[Producer clientId=producer-1] Node 2 disconnected.)
kafka连接断开的具体日志如下图所示:
图片
2 问题分析
- 由于kafka集群有多个broker节点,kafka topic 也有多个分区,所以微服务作为 kafka producer,在底层会建立对个 tcp 连接,以发送日志到各个 kafka topic partition。
- 通过 awk 筛选微服务日志可见,微服务针对同一个 kafka broker的tcp 连接,并不会频繁断开,且断开的时点,一般是在业务低峰期,打印日志不多的时候:
图片
- 为进一步确认问题,在微服务节点上,可以查看下 TCP 连接的状态(可以通过命令 netstat -apn -t|grep -i 9092 或 ss -antop |grep -i 9092),甚至通过 tcpdump 抓包分析下(可以通过命令nohup tcpdump tcp port 9092 -i any -s100 -B 8192 -p -n -w /tmp/9092.pcap >> /tmp/9092.out 2>&1 &)。
- 在生产环境中,使用上述命令,抓了近2个小时的包,并对 tcpdump 包文件进行了分析,如下图可见,所有的tcp连接断开,都是由微服务(即 kafka producer)主动断开的,且具体来说,是微服务长达540秒,都不需要发送日志到某个具体的kafka topic partition时,因超时主动断开的:
图片
image
图片
image
图片
3.问题原因
3.1 kafka 对空闲连接检查和清理机制-producer 参数connections.max.idle.ms
为减轻对kafka broker 服务端的压力,kafka producer 有空闲连接的检查和清理机制:
- 当某个tcp连接长时间idle时,kafka producer就会主动关闭该空闲连接,并打印日志org.apache.kafka.clients. NetworkClient -[Producer clientId=producer-1] Node 2 disconnected;
- 判断空闲连接的空闲阈值,就是参数 connections.max.idle.ms,该参数默认 540000 即 9 分钟,这也与上述tcpdump抓包体现的,kafka producer 在540秒的空闲后,主动断开连接的现象一致:
图片
3.2 Kafka在key=null 时的分区策略- Sticky Partitioner vs Round Robin Partitioner
- Kafka producer 写ProducerRecord到kafka topic时, ProducerRecord对应的具体 partiton分区,是由Partitioner分区器决定的。当 ProducerRecord 的key=null 时,会采用默认的分区器,在Kafka 2.3 及kafka2.3以下版本,默认的分区策略是Round Robin Partitioner;而Kafka 2.4 及以上版本,默认的分区策略是Sticky Partitioner。
- 目前该微服务,写日志到 kafka 时,log4j2.xml中都没有配置 kafka record key,且采用的是kafka-clients-3.4.0.jar,所以此时会使用 Sticky Partitioner 而不是 Round Robin Partitioner,相邻的多个ProducerRecord都会属于同一个 record batch,都会 “sticky” 到同一个分区,所以在日志低峰期,可能长时间都不会发送日志到某些分区,如果该时间超过了producer 参数 connections.max.idle.ms(默认 540000 即 9 分钟),则该分区对应的 TCP 连接,就会被 PRODUCER 主动关闭,如果 kafka 分区较多比如100个,而微服务日志较少且 batch.size 和 linger.ms 较大,该现象会更加明显。
4 问题总结
- 为减轻对服务端kafka broker的压力,kafka producer 有空闲连接的检查和清理机制,当某个tcp连接的idle时长大于参数connections.max.idle.ms时(该参数默认 540000 即 9 分钟),kafka producer就会主动关闭该空闲连接,并打印日志org.apache.kafka.clients. NetworkClient -[Producer clientId=producer-1] Node 2 disconnected,此时所有的日志都会被正常落地,不会丢失日志数据;
- 当使用Kafka 2.4 及以上版本的 kafka producer时,如果没有指定ProducerRecord的key,当业务低峰期微服务日志较少且 batch.size 和 linger.ms 较大时,上述kafka producer主动关闭空闲连接的现象会更加明显,因为此时会使用默认的分区策略Sticky Partitioner,此时相邻的多个ProducerRecord都会属于同一个 record batch,都会 “sticky” 到同一个分区,所以可能长时间都不会发送日志到某些分区,如果该空闲时间超过了上述producer 参数 connections.max.idle.ms,则该分区对应的 TCP 连接,就会被 PRODUCER 主动关闭。
5.技术背景
5.1 KAFKA 客户端和服务端在版本上的双向兼容性
- KAFKA 客户端和服务端在版本上具有双向兼容性,即客户端和服务端的版本可以不同:Kafka has a "bidirectional" client compatibility policy. In other words, new clients can talk to old servers, and old clients can talk to new servers. This allows users to upgrade either clients or servers without experiencing any downtime.
- 在上述案例中,kafka 集群服务端,使用的是kafka_2.11-2.2.0.jar;
- 在上述案例中,该微服务,通过logj写日志到kafka时,使用的 kafka producer 版本是 kafka-clients-3.4.0.jar
- 目前 apache kafka 最新版是 Kafka 3.8 (截止 202409);
图片
5.2 kafka 对空闲连接检查和清理机制-producer 参数connections.max.idle.ms
为减轻对kafka broker 服务端的压力,kafka producer 有空闲连接的检查和清理机制:
- 当某个tcp连接长时间idle时,kafka producer就会主动关闭该空闲连接,并打印日志org.apache.kafka.clients. NetworkClient -[Producer clientId=producer-1] Node 2 disconnected;
- 判断空闲连接的空闲阈值,就是参数 connections.max.idle.ms,该参数默认 540000 即 9 分钟,这也与上述tcpdump抓包体现的,kafka producer 在540秒的空闲后,主动断开连接的现象一致;
5.3 Kafka在 key=null 时的分区策略- Sticky Partitioner vs Round Robin Partitioner
- Kafka producer 写ProducerRecord到kafka topic时, ProducerRecord对应的具体 partiton分区,是由Partitioner分区器决定的;
- 当 ProducerRecord 的key=null 时,会采用默认的分区器,在Kafka 2.3 及kafka2.3以下版本,默认的分区策略是Round Robin Partitioner;而Kafka 2.4 及以上版本,默认的分区策略是Sticky Partitioner。
- When key=null, the producer has a default partitioner that varies: Round Robin: for Kafka 2.3 and below;Sticky Partitioner: for Kafka 2.4 and above:
With Kafka producer <= v2.3: when there’s no partition and no key specified, the default partitioner sends data in a round-robin fashion. This results in more batches (one batch per partition) and smaller batches (imagine with 100 partitions). And this is a problem because smaller batches lead to more requests as well as higher latency.
With Kafka producer >= v2.4: Sticky Partitioner improves the performance of the producer especially with high throughput. It is a performance goal to have all the records sent to a single partition and not multiple partitions to improve batching. The producer sticky partitioner will “stick” to a partition until the batch is full or linger.ms has elapsed; After sending the batch, kafka producer will change the partition that is "sticky". This will lead to larger batches and reduced latency (because we have larger requests, and the batch.size is more likely to be reached). Over time, the records are still spread evenly across partitions, so the balance of the cluster is not affected.
5.4 相关源码与参考链接
-- 相关源码
org.apache.kafka.clients.producer.Partitioner
org.apache.kafka.clients.producer.internals.DefaultPartitioner
org.apache.kafka.clients.producer.UniformStickyPartitioner
org.apache.kafka.clients.producer.RoundRobinPartitioner
-- 参考连接:
KIP-480: Sticky Partitioner
KIP-794: Strictly Uniform Sticky Partitioner
https://cwiki.apache.org/confluence/display/KAFKA/KIP-794%3A+Strictly+Uniform+Sticky+Partitioner
https://cwiki.apache.org/confluence/display/KAFKA/KIP-480%3A+Sticky+Partitioner