关于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 异常 重写抛出异常限制
1 子类在重写父类抛出异常的方法时,要么不抛出异常,要么抛出与父类方法相同的异常或该异常的子类.如果被重写的父类方法只抛出受检异常,则子类重写的方法可以抛出非受检异常.例如,父类方法抛出了一个受检异常 ...
- Git删除本地修改
git如何删除本地所有未提交的更改 git checkout . && git clean -xdf 一般 git clean都是配合git reset 使用的 如果你有的修改已经加入 ...
- 3ds max学习笔记(七)-- 实例操作(桌子)
首先[自定义]/[单位设置],将公制和系统的单位都设置成mm(毫米) 若软件右侧无[标准基本体]那么可以选择[创建]/标准基本体/扩展基本体.... 1.创建一个长方体(作为桌面)完成后,点击菜单栏[ ...
- 通过echarts的demo
通过最近两天对echarts的了解,下面用echarts.js写了一个小效果: 效果的demo为: <!DOCTYPE html> <html lang="en" ...
- Python高级有关的题目
1,copy模块 from copy import deepcopy dic = {} list = [] for i in range(10): dic["num"] = i i ...
- C_关于递归算法的几个例子
1.递归算法的定义: 2.递归与迭代的优劣 eg1:斐波那契数列:斐波那契数列(Fibonacci sequence),又称黄金分割数列.因数学家列昂纳多·斐波那契(Leonardoda Fibona ...
- JAVA自学笔记05
JAVA自学笔记05 1.方法 1)方法就是完成特定功能的代码块,类似C语言中的函数. 2)格式: 修饰符 返回值类型 方法名(参数类型 参数名1,参数类型 参数名2,-){ 函数体; return ...
- vs 2017 community中文版下载地址
https://my.visualstudio.com/Downloads?pid=2190 SHA1: 109C6646A79844D8116DADB293A0B64754363C69 File n ...
- protobuf 动态创建
https://www.ibm.com/developerworks/cn/linux/l-cn-gpb/index.html https://originlee.com/2015/03/14/ana ...
- 像Excel一样使用python进行数据分析
Excel是数据分析中最常用的工具,本篇文章通过python与excel的功能对比介绍如何使用python通过函数式编程完成excel中的数据处理及分析工作.在Python中pandas库用于数据处理 ...