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的讨论的更多相关文章

  1. Kafka设计解析(二十三)关于Kafka监控方案的讨论

    转载自 huxihx,原文链接 关于Kafka监控方案的讨论 目前Kafka监控方案看似很多,然而并没有一个“大而全”的通用解决方案.各家框架也是各有千秋,以下是我了解到的一些内容: 一.Kafka ...

  2. kafka broker 进入 conflicted ephemeral node 死循环

    转载请注明原创地址 http://www.cnblogs.com/dongxiao-yang/p/5621303.html 最近发现kafka一台服务器producer客户端写入时一直报错,查看该br ...

  3. Kafka broker配置介绍 (四)

    这部分内容对了解系统和提高软件性能都有很大的帮助,kafka官网上也给出了比较详细的配置详单,但是我们还是直接从代码来看broker到底有哪些配置需要我们去了解的,配置都有英文注释,所以每一部分是干什 ...

  4. Kafka 单节点多Kafka Broker集群

    Kafka 单节点多Kafka Broker集群 接前一篇文章,今天搭建一下单节点多Kafka Broker集群环境. 配置与启动服务 由于是在一个节点上启动多个 Kafka Broker实例,所以我 ...

  5. Kafka 单节点单Kafka Broker集群

    下载与安装 从 http://www.apache.org/dist/kafka/ 下载最新版本的 kafka,这里使用的是 kafka_2.12-0.10.2.1.tgz $ tar zxvf ka ...

  6. spark streaming 接收kafka消息之三 -- kafka broker 如何处理 fetch 请求

    首先看一下 KafkaServer 这个类的声明: Represents the lifecycle of a single Kafka broker. Handles all functionali ...

  7. kafka broker Leader -1引起spark Streaming不能消费的故障解决方法

    一.问题描述:Kafka生产集群中有一台机器cdh-003由于物理故障原因挂掉了,并且系统起不来了,使得线上的spark Streaming实时任务不能正常消费,重启实时任务都不行.查看kafka t ...

  8. Kafka Broker | 命令行选项和过程

    1.目标 在这个Apache Kafka教程中,我们将学习Kafka Broker.Kafka Broker管理主题中的消息存储.如果Apache Kafka有多个代理,那就是我们所说的Kafka集群 ...

  9. 关于Kafka high watermark的讨论2

    之前写过一篇关于Kafka High watermark的文章,引起的讨论不少:有赞扬之声,但更多的是针对文中的内容被challenge,于是下定决心找个晚上熬夜再看了一遍,昨晚挑灯通读了一遍确实发现 ...

随机推荐

  1. 3ds max 学习笔记(四)--创建物体

    添加物体: 1.初创建物体,从单视图进行创建,便于处于同一平面,在透视图观看效果.2.在基本对象处选择“长方体”:左键开始制作,松开左键此时控制的是长方形的高,然后点击左键完成:注:在max里点击右键 ...

  2. Vue(二)vue-devtools插件

    安装vue-devtools插件,便于在chrome中调试vue https://github.com/vuejs/vue-devtools 因为chrome要FQ,打不开,所以不能直接进去安装拓展程 ...

  3. Docker 常用命令(一)

    9. docker 删除镜像:  docker rmi    imageID    删除容器:    docker rm containName 8. docker repo 上传: 我们看到这里有个 ...

  4. javaScript系列 [05]-javaScript和JSON

    本文输出和JSON有关的以下内容❐ JSON和javaScript❐ JSON的语法介绍❐ JSON的数据类型❐ JSON和XMLHTTPRequest❐ JSON的序列化和反序列化处理 1.1 JS ...

  5. performSelector may cause a leak because its selector is unknown

    转自:http://www.jianshu.com/p/6517ab655be7 问题 我在 ARC 模式下编译出了这个 warning: "performSelector may caus ...

  6. Mac 删除/卸载 自己安装的python

    官网pkg安装的python版本 第一步:删除框架 sudo rm -rf /Library/Frameworks/Python.framework/Versions/2.7 1 第二步:删除应用目录 ...

  7. MFC更换窗口图标

      https://blog.csdn.net/deep_kang/article/details/72722692: MFC更换图标,图像默认为MFC,主要步骤有三步. 第一步 找到一张图片(坑点: ...

  8. Docker 下载镜像慢的问题解决方法

    让你火箭般的速度下载docker镜像! 的冷漠度 百家号17-11-1713:09 因为有墙的原因所以在国内下载docker镜像的速度非常慢,有时候是几kb每秒,那个蛋疼的等待,真是谁等谁知道!下面我 ...

  9. 在三台Centos或Windows中部署三台Zookeeper集群配置

    一.安装包 1.下载最新版(3.4.13):https://archive.apache.org/dist/zookeeper/  下载https://archive.apache.org/dist/ ...

  10. linux内核剖析(七)Linux进程间通信的几种方式总结

    进程间通信概述 进程通信的目的 数据传输 一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M字节之间 共享数据 多个进程想要操作共享数据,一个进程对共享数据 通知事 一个进程需要向另 ...