背景
在公司做的数据同步过程中,大部分是需要使用到Kafka做消息中间件,来实时做同步的,以及最近在做的实时消息推送给数仓,数仓需要准实时拿到数据进行分析,这些都离不开Kafka,但为什么我们第一时间就是想到了Kafka了,这么好用的原因是什么呢?我们就来分析一下吧,我自己也算做一个归纳总结。
优势1. 顺序读写
首先要明确一点,Kafka的数据是持久化保存在本地磁盘的,有的人一想?咦?持久化本地磁盘,读取起来不是很慢的吗???为什么Kafka还这么的快!问题的原因就在于Kafka是顺序读取磁盘的,我们都知道计算机读取信息的地方有两个,一个是内存,另外一个就是本地化磁盘,而读取的方式又有顺序读取和随机读取,这里我总结一下其效率:内存顺序读取 > 磁盘顺序读取 > 内存随机读取 > 磁盘随机读取,这里就可以知道 他磁盘顺序读取的效率是比内存随机读取要高的。至于高多少,具体我本人是没有亲自测试过的,但有些书籍上是写到二到三个数量级。
磁盘的顺序读写是磁盘使用模式中最有规律的,并且操作系统也对这种模式做了大量优化,Kafka就是使用了磁盘顺序读写来提升的性能。Kafka的message是不断追加到本地磁盘文件末尾的,而不是随机的写入,这使得Kafka写入吞吐量得到了显著提升 。
这里就会有一个问题,文件的数据是不断往后添加的,那如果要删数据呢?嘿, 是不可能删除数据的,因为Kafka不支持删数据,它会把所有的数据都保存下来,然后每个消费者(Consumer)对每个Topic都有一个offset用来表示 读取到了第几条数据。比如说A消费者订阅了topic1和topic2 那么,消费者A 就会在topic1和topic2中各有一个offset下标来表明,他消费到了topic1和topic2中的哪一个数据,还有哪些数据是没有消费到的
这个offset是由客户端SDK负责保存的,Kafka的Broker完全无视这个东西的存在;一般情况下SDK会把它保存到zookeeper里面。(所以需要给Consumer提供zookeeper的地址)。
当然我们往极限的情况去思考,既然消费者没有办法去删数据,就是跳过数据,那又是磁盘本地化的存储数据,如果硬盘被存满了怎么办?是的,会有这样的情况,所有Kafka提供了两种策略去删除本地化的数据,一个是基于时间的,一个是基于文件大小的。
优势2. 页缓存
为了优化读写性能,Kafka利用了操作系统本身的Page Cache,就是利用操作系统自身的内存而不是JVM空间内存。这样做的好处有:
- 避免Object消耗:如果是使用 Java 堆,Java对象的内存消耗比较大,通常是所存储数据的两倍甚至更多。
- 避免GC问题:随着JVM中数据不断增多,垃圾回收将会变得复杂与缓慢,使用系统缓存就不会存在GC问题
相比于使用JVM或in-memory cache等数据结构,利用操作系统的Page Cache更加简单可靠。首先,操作系统层面的缓存利用率会更高,因为存储的都是紧凑的字节结构而不是独立的对象。其次,操作系统本身也对于Page Cache做了大量优化,提供了 write-behind、read-ahead以及flush等多种机制。再者,即使服务进程重启,系统缓存依然不会消失,避免了in-process cache重建缓存的过程。
通过操作系统的Page Cache,Kafka的读写操作基本上是基于内存的,读写速度得到了极大的提升。
优势3. 分区分段+索引
Kafka的message是按topic分类存储的,topic中的数据又是按照一个一个的partition即分区存储到不同broker节点。每个partition对应了操作系统上的一个文件夹,partition实际上又是按照segment分段存储的。这也非常符合分布式系统分区分桶的设计思想。
通过这种分区分段的设计,Kafka的message消息实际上是分布式存储在一个一个小的segment中的,每次文件操作也是直接操作的segment。为了进一步的查询优化,Kafka又默认为分段后的数据文件建立了索引文件,就是文件系统上的.index文件。这种分区分段+索引的设计,不仅提升了数据读取的效率,同时也提高了数据操作的并行度。
优势4. 批量读写
一些Kafka底层设计的说完,就到了上一层,使用的层面,使用的时候,Kafka也是批量进行读写的,而非单条单条进行读写,这样进一步的增加了Kafka的吞吐速度。
在向Kafka写入数据时,可以启用批次写入,这样可以避免在网络上频繁传输单个消息带来的延迟和带宽开销。假设网络带宽为10MB/S,一次性传输10MB的消息比传输1KB的消息10000万次显然要快得多。
优势5. 批量压缩
在很多情况下,系统的瓶颈不是CPU或磁盘,而是网络IO,对于需要在广域网上的数据中心之间发送消息的数据流水线尤其如此。比如A应用和B应用要数据实时同步,应用A在上海,应用B在深圳,那么这么距离的网络肯定会收到限制,进行数据压缩会消耗少量的CPU资源,不过对于kafka而言,网络IO更应该需要考虑。所以就要进行压缩需要传输的数据。
-
如果每个消息都压缩,Kafka的压缩率相对是很低,所以Kafka使用了批量压缩,即将多个消息一起压缩而不是单个消息压缩,这里的思想与批量读写一致
-
Kafka允许使用递归的消息集合,批量的消息可以通过压缩的形式传输并且在日志中也可以保持压缩格式,直到被消费者解压缩
-
Kafka支持多种压缩协议,包括Gzip和Snappy压缩协议
总结
Kafka速度的秘诀在于,它把所有的消息都变成一个批量的文件,并且进行合理的批量压缩,减少网络IO损耗,通过mmap提高I/O速度,写入数据的时候由于单个Partion是末尾添加所以速度最优;读取数据的时候配合sendfile直接暴力输出。
参考文献:Kafka相关书籍以及一些技术公众号