文章目录
前言
在前面的文章《在hadoopHA节点上部署kafka集群组件》,介绍大数据实时分析平台生态圈组件——kafka,前向用于连接flume,后向连接spark streaming。在研究Kafka过程中,发现该中间件的设计很巧妙,因此专设一篇文章用于深入理解Kafka核心知识。Kafka已经纳入个人目前最欣赏的中间件list:redis,zookeeper,kafka
1、kafka集群架构图
以下为kafka集群一种经典的架构图,该图以《在hadoopHA节点上部署kafka集群组件》文章的kafka集群以及sparkapp topic作为示例绘成,本文的内容将以该图为标准作为说明。
图1 kafka集群架构图
2、kafka 高性能读写的设计
2.1、利用read-ahead 和 write-behind提升写性能
kafka底层设计高度依赖现代磁盘优化技术和文件系统的优化技术。在kafka官方文档的:don’t fear the filesystem章节说明了kafka是如何利用磁盘已有的高性能读写技术:read-ahead 和 write-behind 实现日志在磁盘山高性能顺序写。
read-ahead 是以大的 data block 为单位预先读取数据。write-behind(后写) 是将多个小型的逻辑写合并成一次大型的物理磁盘写入,producer向kafka写入消息日志时,因为消息是一条一条的过来,而且消息本身payload很小,如果每条消息进来立刻执行写入磁盘,显然IO非常高,因此需要将进来的消息先缓存,然后到一定数量或者到一定容量时再触发写入磁盘,kafka用了pagecache实现write-behind而不是通过内存。
官方举例说明用廉价的RAID-5模式sata硬盘可以去到600MB/秒,但随机写入的性能仅约为100k/秒,相差6000倍以上。
2.2、使用pagecache缓存程序数据提升读写性能
同样,在kafka官方文档的:don’t fear the filesystem章节还提到另外一个技术:pagecache。kafka利用了现代操作系统主动将所有空闲内存用作磁盘caching这一机制(代价是在内存回收时性能会有所降低),再次提升基于filesystem的读写性能的效果。
kafka 跑在 jvm之上,那么jvm一定会有复杂的GC情况:
- 对象的内存开销非常高,通常是所存储的数据的两倍(甚至更多)。
- 随着堆中数据的增加,Java 的垃圾回收变得越来越复杂和缓慢。
受这些因素影响, 维护in-memory cache就会显得很复杂,而kafka通过文件系统方式和 pagecache 读写消息反而显得更有优势(避免复杂低效率的GC),通过自动访问所有空闲内存将可用缓存的容量至少翻倍,并且通过存储紧凑的字节结构而不是独立的对象,有望将缓存容量再翻一番,例如32GB内存的服务器,它的 pagecache缓存容量可以达到28-30GB,并且不会产生额外的 GC 负担。kafka自己也说还有重要一点:简化核心代码。
为何这么设计?
kafka自己这么解释:因为相比于维护尽可能多的 in-memory cache,并且在空间不足的时候匆忙将消息数据 flush 到文件系统的,kafka写过程把这个过程倒过来:所有消息数据一开始就被写入(write-behind)到文件系统的持久化日志中,而不用在in-memory cache 空间不足的时候 flush 到磁盘。实际上,是先把数据被转移到了内核的 pagecache 中。
这里可以联想到Hbase的MemStore设计:MemStore基于in-memory cache,MemStore 在内存中存在,保存修改key-value数据,当MemStore的大小达到一个阀值(默认64MB)时,MemStore里面的数据会被flush到Hfile文件上,也就是flush到磁盘上。
为何page cache 会加速读过程?
linux的文件cache分为两层,一个是page cache,另一个是buffer cache;每一个page cache包含若干个buffer cache,结构图如下图所示:
page cache:文件系统层级的缓存,从磁盘里读取数据缓存到page cache(属于内核空间,而不是应用用户的空间),这样应用读磁盘数据会被加速,例如使用find等命令查找文件时,第一次会慢很多,第二次查找相同文件时会瞬间读取到。如果page cache的数据被修改过后,也即脏数据,等到写入磁盘时机到来时,会把数据转移到buffer cache 而不是直接写入到磁盘。
buffer cache:磁盘等块设备的缓冲。
大致流程:
page cache其优化读的工作过程如下:
A、文件的第一次读请求
系统读入所请求的page页并读入紧随其后的的少数几个页面,这种读取方式称为同步预读。
B、文件的第二次读请求:
如果page页不在第一次的cache中,说明不是顺序读,所以又会重新继续第一次那种同步预读过程。
如果page页面在cache中,说明是顺序读,Linux会将预读group扩大一倍,继续把不在首次cache中的文件数据读进来,此为异步预读。kafka之所以设计按顺序读写,完全就是按照底层page cahe的这种预读机制来设计,所以在文件系统底层就已经有不错的性能了。
2.3 通过sendfile(零拷贝机制)提高消费者端的读吞吐量
在kafka官方文档的Efficiency章节解释了kafka通过使用sendfile (零拷贝技术)继续提高消费者端的读性能。
前面2.1和2.2解释了kafka里利用相关底层机制,解决了磁盘访问模式不佳的情况。接下来,还需要解决以下两个影响kafka性能的情况:
too many small I/O operations, and excessive byte copying
(大量的小型 I/O 操作以及过多的字节拷贝 )
-
A、 The small I/O problem happens both between the client and the server and in the server’s own persistent operations.
(大量小型的 I/O 操作表现在client和broker之间以及broker服务端自身持久化操作中)
解决方式:kafka用一个称为 “消息块” 的抽象基础上,合理将消息分组。 这使得网络请求将多个消息打包成一组,而不是每次发送一条消息,从而使整组消息分担网络中往返的开销。consumer 每次获取多个大型有序的消息块,并由服务端依次将消息块一次加载到它的日志中。
这个简单的优化对速度有着数量级的提升。批处理允许更大的网络数据包,更大的顺序读写磁盘操作,连续的内存块等等 -
B、excessive byte copying
另一个低效率的操作是字节拷贝,在消息量少时,这不是什么问题,但是在高负载的情况下,影响就不容忽视。为了避免这种情况,kafka在producer、broker 和 consumer 都是用相同标准化的二进制消息格式,这样数据块不用修改就能在他们之间传递。
broker 维护的消息日志本身就是一个文件目录,每个segment文件都由一系列以相同格式消息组成,保持