关于Kafka broker IO的讨论
Apache Kafka是大量使用磁盘和页缓存(page cache)的,特别是对page cache的应用被视为是Kafka实现高吞吐量的重要因素之一。实际场景中用户调整page cache的手段并不太多,更多的还是通过管理好broker端的IO来间接影响page cache从而实现高吞吐量。我们今天就来讨论一下broker端的各种IO操作。
开始之前,还是简单介绍一下page cache:page cache是内核使用的最主要的磁盘缓存(disk cache)之一——实际上Linux中还有其他类型的磁盘缓存,如dentry cache、inode cache等。通常情况下Linux内核在读写磁盘时都会访问page cache。当用户进程打算读取磁盘上文件的数据时,内核会首先查看待读取数据所在的page是否在page cache中,如果存在自然命中page cache,直接返回数据即可,避免了物理磁盘读操作;反之内核会向page cache添加一个新的page并发起物理磁盘读操作将数据从磁盘读取到新加page中,之后再返回给用户进程。Linux内核总是会将系统中所有的空闲内存全部当做page cache来用,而page cache中的所有page数据将一直保存在page cache中直到内核根据特定的算法替换掉它们中的某些page——一个比较朴素的算法就是LRU。同样地,在写入page数据到磁盘之前,内核也会检查对应的page是否在page cache中,如果不存在则添加新page并将待写入数据填充到该page中,此时真正的磁盘写还尚未开始,通常都是隔几秒之后才真正写入到底层块设备上——即这是一个延迟写入操作。理论上来说,在这几秒之内的间隔中,用户进程甚至还允许修改这些待写入的数据——当然对于Kafka而言,它的写入操作本质上是append-only的,故没有这样的使用场景。
针对Kafka而言,我平时看到对page cache的调优主要集中在下面这3种上:
- 设置合理(主要是偏小)的Java Heap size:很多文章都提到了这种调优方法。正常情况下(如没有downConvert的情形)Kafka对于JVM堆的需求并不是特别大。设置过大的堆完全是一种浪费甚至是“拖后腿”。业界对该值的设定有比较一致的共识,即6~10GB大小的JVM堆是一个比较合理的数值。鉴于目前服务器的硬件配置都非常好,内存动辄都是32GB甚至是64、128GB的,这样的JVM设置可以为内核预留出一个非常大的page cache空间。这对于改善broker端的IO性能非常有帮助
- 调节内核的文件预取(prefetch):文件预取是指将数据从磁盘读取到page cache中,防止出现缺页中断(page fault)而阻塞。调节预取的命令是blockdev --setra XXX
- 设置“脏页”落盘频率(vm.dirty_ratio):主要控制"脏页“被冲刷(flush)到磁盘的频率——当然还有个dirty_background_ratio,大家可以google它们的区别。在我看来,前者类似是一个hard limit,而后者更像个soft limit
除了上面这几种,我更想讨论一下broker端自己的使用场景会对page cache造成什么影响进而反过来影响broker性能。目前broker端的IO主要集中在以下几种:
- Producer发送的PRODUCE请求
- ISR副本/非滞后consumer发送的FETCH请求
- 滞后consumer发送的FECTH请求
- 老版本consumer发送的FETCH请求
- Broker端的log compaction操作
一、Producer发送的PRODUCE请求
Producer发送消息给broker,broker端写入到底层物理磁盘,这是Kafka broker端最主要的磁盘写操作了。真正的写入操作是异步的,就像之前说的,broker只是将数据直接写入到page cache中。何时写回到磁盘由操作系统决定,Kafka不关心。显然,当prodcuer持续发送数据时,page cache中会不断缓存当前发送的消息序列。这些数据何时会被访问呢?有三个可能的时机:
- ISR副本拉取:当leader broker成功地写入了一条消息后,follower broker会从leader处拉取该条消息,如果是ISR的follower副本,通常能够很快速地拉取这条新写入消息,那么此时这条消息依然保存在leader broker页缓存的概率就会很大,可以保证直接命中。再结合sendfile系统调用提供的Zero Copy特性内核就能直接将该数据从page cache中输送到Socket buffer上从而快速地发送给follower,避免不必要的数据拷贝
- Broker端compaction:当写入消息成功一段时间后log cleaner可能会立即开启工作,故compaction也有可能会触碰到这条消息。当然这种几率比较小,因为log cleaner不会对active日志段进行操作,而写入的消息有较大几率依然保存在active日志段上
- Consumer读取:对于非滞后consumer(nonlagging consumer)而言,它们会立即读取到这条消息。和ISR副本拉取情况相同,这些consumer的性能也会比较好,因为可以直接命中page cache
二、ISR副本/非滞后consumer发送的FETCH请求
Broker端最重要的读操作! ISR副本和非滞后consumer都几乎是“实时”地读取page cache中的数据,而不需要发起缓慢的物理磁盘读操作。再加上上面说的Zero Copy技术既实现了快速的数据读取,也避免了对磁盘的访问,从而将磁盘资源保存下来用于写入操作。由此可见,这是最理想的情况,在实际使用过程中我们应该尽量让所有consumer都变成non-lagging的。对于这种Broker IO模式而言,此时的Kafka已经有点类似于Redis了。
三、滞后consumer发送的FECTH请求
真实场景下这种consumer一定存在的,不管是从头开始读取的consumer还是长时间追不上producer进度的consumer,它们都属于这类消费者。它们要读取的数据有大概率是不在page cache中的,所以broker端所在机器的内核必然要首先从磁盘读取数据加载到page cache中之后才能将结果返还给consumer。这还不是最重要的,最重要的是这种consumer的存在“污染”了当前broker所在机器的page cache,而且本来可以服务于写操作的磁盘现在要读取数据了。平时应用中,我们经常发现我们的consumer无缘无故地性能变差了,除了查找自己应用的问题之外,有时候诊断一下有没有lagging consumer间接“捣乱”也是必要的。
四、老版本consumer发送的FETCH请求
老版本consumer识别的数据格式与broker端不同,因此和走Zero Copy路径的consumer不同的是,此时broker不能直接将数据(可能命中page cache也可能从磁盘中读取)直接返回给consumer,而是必须要先进行数据格式转换,即所谓的downConvert——一旦需要执行downConvert,此broker就失去了Zero Copy的好处,因为broker需要将数据从磁盘或page cache拷贝到JVM的堆上进行处理。显然这势必推高堆占用率从而间接地减少了page cache的可用空间。更糟的是,当broker JVM线程处理完downConvert之后,还需要把处理后的数据拷贝到内核空间(不是page cache。因为失去了Zero Copy,所以必须先拷贝到内核空间然后才能发送给Socket buffer),再一次地压缩了page cache的空间。如果数据量很大,那么这种场景极易造成JVM堆溢出(OOM)。值得一提的是,KIP-283解决了容易出现OOM的问题,但依然不能使得downConvert场景继续“享受”Zero Copy。
五、Broker端的log compaction操作
Compaction操作定期处理日志段上的数据,执行基于key的压实操作。在compact期间,broker需要读取整个日志段,在JVM堆上构建映射表,因此也会挤占page cache的空间,另外compact会将处理结果写回到日志段中。Compaction是定时运行的操作,在频率上并不如上面4个来的频繁。
综合比较上面这5种IO,我们希望broker端的磁盘尽量为producer写入服务,而page cache尽量为non-lagging consumer服务——这应该是能获取clients端最大吞吐量的必要条件。但在实际应用中,我们的确也观测到了因为lagging consumer或downConvert甚至是compaction导致其他clients被影响的实例。究其原因就是因为所有IO对page cache的影响是无差别的。producer持续写入保证了page cache中不断充满最新的数据,但只有存在一个auto.offset.reset=earliest的consumer,就有可能瞬间把page cache修改得面目全非,即使这个consumer只是一个一次性的测试consumer。从根本上来说,我们应该区分不同clients或进程对page cache访问的优先级。
实际上,Linux的open系统调用提供了O_DIRECT的方式来禁用page cache,因此我在想能否为Kafka clients提供这样的选择,即可以指定某些clients或Kafka线程以O_DIRECT方式来访问Linux的VFS。如果支持这个功能的话,那么像这种一次性的auto.offset.reset=earliest的consumer抑或是阶段性的compact完全可以采用这种方式从而完全避免对page cache的“污染”。不过令人遗憾的是,Java直到Java 10才加入了对O_DIRECT的支持(https://bugs.openjdk.java.net/browse/JDK-8164900)。也许在未来Kafka不再支持Java 9时这会是一个不错的KIP提议吧。
关于Kafka broker IO的讨论的更多相关文章
- Kafka设计解析(二十三)关于Kafka监控方案的讨论
转载自 huxihx,原文链接 关于Kafka监控方案的讨论 目前Kafka监控方案看似很多,然而并没有一个“大而全”的通用解决方案.各家框架也是各有千秋,以下是我了解到的一些内容: 一.Kafka ...
- kafka broker 进入 conflicted ephemeral node 死循环
转载请注明原创地址 http://www.cnblogs.com/dongxiao-yang/p/5621303.html 最近发现kafka一台服务器producer客户端写入时一直报错,查看该br ...
- Kafka broker配置介绍 (四)
这部分内容对了解系统和提高软件性能都有很大的帮助,kafka官网上也给出了比较详细的配置详单,但是我们还是直接从代码来看broker到底有哪些配置需要我们去了解的,配置都有英文注释,所以每一部分是干什 ...
- Kafka 单节点多Kafka Broker集群
Kafka 单节点多Kafka Broker集群 接前一篇文章,今天搭建一下单节点多Kafka Broker集群环境. 配置与启动服务 由于是在一个节点上启动多个 Kafka Broker实例,所以我 ...
- Kafka 单节点单Kafka Broker集群
下载与安装 从 http://www.apache.org/dist/kafka/ 下载最新版本的 kafka,这里使用的是 kafka_2.12-0.10.2.1.tgz $ tar zxvf ka ...
- spark streaming 接收kafka消息之三 -- kafka broker 如何处理 fetch 请求
首先看一下 KafkaServer 这个类的声明: Represents the lifecycle of a single Kafka broker. Handles all functionali ...
- kafka broker Leader -1引起spark Streaming不能消费的故障解决方法
一.问题描述:Kafka生产集群中有一台机器cdh-003由于物理故障原因挂掉了,并且系统起不来了,使得线上的spark Streaming实时任务不能正常消费,重启实时任务都不行.查看kafka t ...
- Kafka Broker | 命令行选项和过程
1.目标 在这个Apache Kafka教程中,我们将学习Kafka Broker.Kafka Broker管理主题中的消息存储.如果Apache Kafka有多个代理,那就是我们所说的Kafka集群 ...
- 关于Kafka high watermark的讨论2
之前写过一篇关于Kafka High watermark的文章,引起的讨论不少:有赞扬之声,但更多的是针对文中的内容被challenge,于是下定决心找个晚上熬夜再看了一遍,昨晚挑灯通读了一遍确实发现 ...
随机推荐
- java代码发送邮箱验证码与qq邮箱smtp服务
发送邮箱的类封装,在此之前需要一个jar包 javax.mail.jar 下载链接https://github.com/javaee/javamail/releases/download/JAVAM ...
- numpy中的广播机制
广播的引出 numpy两个数组的相加.相减以及相乘都是对应元素之间的操作. import numpy as np x = np.array([[2,2,3],[1,2,3]]) y = np.arra ...
- idea其他人把jar更新之后更新不到
昨天下午开始就发现这个问题,其他同事把jar更新了之后,我一直获取不到更新之后的内容.尝试了很多方法,删除具体的更新不到的jar,一直不停的mvn clean install -U -Dmaven.t ...
- web项目上传图片需要刷新文件夹才能显示
问题:上传图片到指定的目录,比如上传到项目文件夹中E:\briup\workspace\Buddhism\WebContent\userImage,但是上传呢完毕之后需要手动刷新该文件夹,网页上才能正 ...
- hdu1003 Max Sum(最大子串)
https://vjudge.net/problem/HDU-1003 注意考虑如果全为负的情况,特判. 还有输出格式,最后一个输出不用再空行. #include<iostream> #i ...
- Mac 10.12下安装python3环境
python3感觉用虚拟环境会比较好操作一些,也不用直接卸载python2. 一.基于brew快速安装 # 安装python3 brew install python3 # 安装pip(好像3自带pi ...
- pygame-KidsCanCode系列jumpy-part15-PowerUp加速器
这一节我们给游戏增加点额外的奖励,大多数游戏中都会有金币.装备啥的来激励玩家,在jumpy这个游戏中,我们也可以增加类似的道具:加速器.效果图如下: 档板上会随机出现一些加速器(power up),小 ...
- Sqoop: ERROR manager.SqlManager: Error reading from database: java.sql.SQLException:
sqoop import --connect jdbc:mysql://122.206.79.212:3306/dating --username root --password 123456 --t ...
- Spring 拦截器postHandle无法修改Response的原因
如果controller跳转至页面,postHandle是没问题的. 如果@ResponseBody注释 或者返回 ResponseEntity,在postHandle拦截器中修改请求头,是无效的. ...
- 网页出现400 Bad Request Request Header Or Cookie Too Large错误的解决方法
在开发项目过程中,突然遇到400 Bad Request Request Header Or Cookie Too Large的报错,我也是第一次出现这样的错误,感觉还是挺新奇的. 分析下出现错误的原 ...