从零学习Kafka:调优

在这个系列的结尾篇,我们来聊一下 Kafka 有哪些调优策略。

调优目标

在开始之前,我们先明确一下调优的目标:对 Kafka 来说,通常是在高吞吐低延迟高可靠这三点之间找到一个平衡。

goal

吞吐量就是 TPS,指的是 Broker 端进程或者 Client 端程序每秒能处理的字节数或消息数。这个值是越大越好,也就是我们期望的高吞吐。

延迟通常是指消息从 Producer 端生产到 Consumer 端成功消费的总时长。这个值我们希望的越短越好,我就是低延迟。

可靠性从两个方面理解,一是集群整体的稳定性,也就是集群会不会出现不可用的情况,这点毋庸置疑,我们一定是希望稳定性越高越好的。另一方面是消息传输的稳定性,也就是会不会丢消息,在某些场景下,我们可能会舍弃一部分消息传输的可靠性来换取高性能。

调优分层

明确了目标之后,我们把调优的方法按照层级进行划分。

layer

我们分为 4 层,总的来说,越靠上的层级调优效果越好。

  • 应用程序层:指的是我们客户端的代码,即消息的生产和消费逻辑。这些逻辑的优化效果是非常可观的,通常也比较简单。

  • 框架层:在这一层我们通常会调整 Kafka 的各种参数,相对也比较简单。

  • JVM 层:指的是 Broker 端的 JVM 调优,针对 JVM 的调优也能收获不错的效果。

  • 操作系统层:通常就是 Broker 端部署的操作系统环境的优化。

接下来我们就看一下这 4 个层面分别可以做哪些优化。

应用程序层

我们先来看应用程序层,这部分优化可以认为是最简单,也是最有效的。但由于每个业务甚至每个开发者写出来的逻辑都不一样,对应的优化方法也不一样。因此我无法给出一个“银弹”。不过有一些最基本的准则需要遵循。

  1. 不要频繁创建 Producer 和 Consumer 实例。

  2. 用完及时关闭,Producer 和 Consumer 对象底层会使用很多物理资源,不及时关闭的话可能会造成资源泄漏。

  3. 合理使用多线程提升性能。

框架层

框架层可做的调优比较多,最基本的就是保持客户端与 Broker 端的版本一致。相同版本的客户端和 Broker 端之间通信可以享受 Zero Copy 的性能收益。

接着我们再分别看一下 Producer、Broker 和 Consumer 三部分还有哪些调优操作。

Producer

首先是生产者,它的瓶颈通常在于 CPU 或网络 IO。调优主要从批量发送、压缩算法和确认机制这三方面入手。

批量发送

批量发送是调优吞吐量的关键,主要是调整 batch.sizelinger.ms 这两个参数。其实在前面的文章中我们也有提到过。

如果想要高吞吐,可以适当调大这两个参数的值。例如 batch.size 可以从默认的 16KB 调大到 512KB 或者 1MB,linger.ms 可以调整到 10 到 100 毫秒。

如果想要低延迟,我们可以调小这两个参数,最极端的方式是 linger.ms 设置为 0,也就是数据不在 Producer 端停留,直接发出去。

压缩算法

压缩可以节省网络 IO,但会消耗 CPU 资源。这部分在从零学习Kafka:生产者压缩一文中有详细的介绍。

如果需要高吞吐,推荐使用 lz4 或 zstd 算法。

如果需要低延迟,就不要开启压缩。

确认机制

主要调整 acks 参数。这是性能与可靠性的平衡。

想要高性能,可以设置 acks=1,即 Leader 收到后就返回。

想要高可靠,可以设置 acks=all,确保所有副本都同步到消息。

Broker

再来看 Broker 端的调整。

首先是 num.replica.fetchers 参数,它用来控制 Follwer 副本用多少个线程来拉取消息。不论是高吞吐还是低延迟的场景,最好都把这个参数调大一点,但不要超过 CPU 核数。

然后是内存层面,Kafka 强依赖操作系统的页缓存,因此不要给 Broker 配置太大的 JVM 堆内存,通常配置 6 到 10 GB 足够用,剩下的留给页缓存。

接着到磁盘层面,log.dirs 可以配置多个磁盘路径,Kafka 会在这些路径间分布分区,这样可以有效利用多块磁盘的并非 IO 来提升性能。

最后到网络层面,num.network.threads 参数用来指定接收请求的线程数,num.io.threads 参数用来指定处理磁盘请求的线程数,这两个参数都可以适当调大。

Consumer

最后我们来看消费者端的调优。消费者端有两个核心参数:fetch.min.bytesfetch.max.wait.msfetch.min.bytes 代表单次抓取的最小数据量,fetch.max.wait.ms 代表如果数据量不够,最长的等待时长。

如果是高吞吐场景,可以调大这两个参数。相反,如果是低延迟场景,可以调小这两个参数。

我们通过一个表格来小结一下。

场景 核心参数 适用业务
高吞吐 调大 batch.sizelinger.ms,压缩算法使用 lz4 或 zstd 日志收集类场景
低延迟 linger.ms 设置为 0,acks 设置为 1 实时监控告警
高可靠 acks=allmin.insync.replicas 至少是 2 金融支付等场景

至此,框架层的调优方法就介绍完了,我们继续顺着漏斗往下看。

JVM 层

Kafka 的 JVM 层的调优和其他服务的 JVM 调优没什么区别。除了我们刚刚提到的不要设置太大的堆内存之外,就是要选择合适的垃圾回收器,最好使用 G1 和 ZGC 这样更先进的垃圾回收器,不光性能更好,调优方法也会简单很多。

操作系统层

操作系统层的调优可能大部分同学不会接触到,这里我们简单介绍几个方法。

第一是在挂载文件系统时执行下面的命令禁用掉 atime 更新,这一调整可以减少文件系统的写操作数。

1
mount -o noatime

第二是选择合适的文件系统,可以是 ext4 或 XFS。当然如果有性能和可靠性更好的文件系统也可以尝试。

总结

最后我们来总结一下本文的内容,我们首先明确了 Kafka 调优的目标,即在高吞吐、低延迟、高可靠三点中找到平衡。接着对调优方法进行了分层,从上到下分别是应用程序层、框架层、JVM 层和操作系统层,通常越靠上调优效果越好。这四层中我们重点介绍了框架层的调优方法。

到这里从零学习 Kafka 系列就完结了,感谢各位读者一路以来的支持。