现象:
kafka版本是0.10.0.1,每次启动任务每次拉取各个分区partition数据量都不超过1000条,已经设置了max.poll.records:The maximum number of records returned in a single call to poll()。但是不管如何设置大这个参数,都不能超过该参数。后面启动了多个任务后,多个任务都每秒都总和感觉也没有超过3000都样子,就是没有partitions 每秒没有拉得的数据没有超过1000。
问题分析:
虽然,增加partitions分区数,每个任务增加每次消费数据总量会增加,也就是 越多的partition可以提供更高的吞吐量,最终会增加每秒总的消费数据条数。但是越多partitions会带来以下问题:
- 越多的分区需要打开更多的文件句柄
在kafka的broker中,每个分区都会对照着文件系统的一个目录。
在kafka的数据日志文件目录中,每个日志数据段都会分配两个文件,一个索引文件和一个数据文件。因此,随着partition的增多,需要的文件句柄数急剧增加,必要时需要调整操作系统允许打开的文件句柄数。
- 更多的分区会导致端对端的延迟
kafka端对端的延迟为producer端发布消息到consumer端消费消息所需的时间,即consumer接收消息的时间减去produce发布消息的时间。kafka在消息正确接收后才会暴露给消费者,即在保证in-sync副本复制成功之后才会暴露,瓶颈则来自于此。在一个broker上的副本从其他broker的leader上复制数据的时候只会开启一个线程,假设partition数量为n,每个副本同步的时间为1ms,那in-sync操作完成所需的时间即n*1ms,若n为10000,则需要10秒才能返回同步状态,数据才能暴露给消费者,这就导致了较大的端对端的延迟。
- 越多的partition意味着需要更多的内存
在新版本的kafka中可以支持批量提交和批量消费,而设置了批量提交和批量消费后,每个partition都会需要一定的内存空间。假设为100k,当partition为100时,producer端和consumer端都需要10M的内存;当partition为100000时,producer端和consumer端则都需要10G内存。无限的partition数量很快就会占据大量的内存,造成性能瓶颈。
- 越多的partition会导致更长时间的恢复期
kafka通过多副本复制技术,实现kafka的高可用性和稳定性。每个partition都会有多个副本存在于多个broker中,其中一个副本为leader,其余的为follower。当kafka集群其中一个broker出现故障时,在这个broker上的leader会需要在其他broker上重新选择一个副本启动为leader,这个过程由kafka controller来完成,主要是从Zookeeper读取和修改受影响partition的一些元数据信息。
通常情况下,当一个broker有计划的停机上,该broker上的partition leader会在broker停机前有次序的一一移走,假设移走一个需要1ms,10个partition leader则需要10ms,这影响很小,并且在移动其中一个leader的时候,其他九个leader是可用的,因此实际上每个partition leader的不可用时间为1ms。但是在宕机情况下,所有的10个partition
leader同时无法使用,需要依次移走,最长的leader则需要10ms的不可用时间窗口,平均不可用时间窗口为5.5ms,假设有10000个leader在此宕机的broker上,平均的不可用时间窗口则为5.5s。
更极端的情况是,当时的broker是kafka controller所在的节点,那需要等待新的kafka leader节点在投票中产生并启用,之后新启动的kafka leader还需要从zookeeper中读取每一个partition的元数据信息用于初始化数据。在这之前partition leader的迁移一直处于等待状态。
所以这种方法可以一定程度上提高吞吐量,但是治标不治本。后来一次偶然的机会,在一个机器环境比现在环境好(内存128G,CPU64核),测出每秒1W左右,可是但是设置的是3W左右。怀疑与机器有关。
实验
对老环境做压力测试,测试出来
可以看到每秒消费速度达到了33w每秒,所以排查了环境因素。那么就只有可能是代码因素。测试很多配置参数,发现max.partition.fetch.bytes 改产生生效了
max.partition.fetch.bytes
The maximum amount of data per-partition the server will return. The maximum total memory used for a request will be <code>#partitions * max.partition.fetch.bytes</code>. This size must be at least as large as the maximum message size the server allows or else it is possible for the producer to send messages larger than the consumer can fetch. If that happens, the consumer can get stuck trying to fetch a large message on a certain partition.
问题得到解决。
参考: