不少同窗私信问我Kafka在性能优化方面作了哪些举措,对于相关问题的答案其实我早就写过了,就是没有系统的整理一篇,最近思考着花点时间来整理一下,下次再有同窗问我相关的问题我就能够潇洒的甩个连接了。这个问题也是Kafka面试的时候的常见问题,面试官问你这个问题也不算刁难你。在网上也有不少相关的文章开讲解这个问题,好比以前各大公众号转载的《为何Kafka这么快?》,这些文章我看了,写的不错,问题在于只是罗列了部分的要领,没有所有的详述出来。本文所罗列的要领会比大家网上搜寻到的都多,若是你在看完本篇文章以后,在面试的时候遇到相关问题,相信你必定能让面试官眼前一亮。面试
PS: 本文章的全部要领在《深刻理解Kafka》一书中都有详细的描述,若是对相关知识点生疏的话,能够再去翻看一下。缓存
传统消息中间件的消息发送和消费总体上是针对单条的。对于生产者而言,它先发一条消息,而后broker返回ACK表示已接收,这里产生2次rpc;对于消费者而言,它先请求接受消息,而后broker返回消息,最后发送ACK表示已消费,这里产生了3次rpc(有些消息中间件会优化一下,broker返回的时候返回多条消息)。而Kafka采用了批量处理:生产者聚合了一批消息,而后再作2次rpc将消息存入broker,这本来是须要不少次的rpc才能完成的操做。假设须要发送1000条消息,每条消息大小1KB,那么传统的消息中间件须要2000次rpc,而Kafka可能会把这1000条消息包装成1个1MB的消息,采用2次rpc就完成了任务。这一改进举措一度被认为是一种“做弊”的行为,然而在微批次理念盛行的今日,其它消息中间件也开始纷纷效仿。安全
这里接着批量处理的概念继续来讲,新版生产者客户端摒弃了以往的单线程,而采用了双线程:主线程和Sender线程。主线程负责将消息置入客户端缓存,Sender线程负责从缓存中发送消息,而这个缓存会聚合多个消息为一个批次。有些消息中间件会把消息直接扔到broker。性能优化
Kafka从0.8版本开始日志格式历经了三次变革:v0、v一、v2。在以前发过的一篇文章《一文看懂Kafka消息格式的演变》中详细介绍了Kafka日志格式,Kafka的日志格式愈来愈利于批量消息的处理,有兴趣的同窗能够阅读一下这篇文章以做了解。 微信
若是了解了Kafka具体的日志格式(能够参考上图),那么你应该了解日志(Record,或者称之为消息)自己除了基本的key和value以外,还有一些其它的字段,本来这些附加字段按照固定的大小占用必定的篇幅(参考上图左),而Kafka最新的版本中采用了变成字段Varints和ZigZag编码,有效地下降了这些附加字段的占用大小。日志(消息)尽量变小了,那么网络传输的效率也会变高,日志存盘的效率也会提高,从而整理的性能也会有所提高。网络
Kafka支持多种消息压缩方式(gzip、snappy、lz4)。对消息进行压缩能够极大地减小网络传输 量、下降网络 I/O,从而提升总体的性能。消息压缩是一种使用时间换空间的优化方式,若是对 时延有必定的要求,则不推荐对消息进行压缩。app
每一个日志分段文件对应了两个索引文件,主要用来提升查找消息的效率,这也是提高性能的一种方式。(具体的内容在书中的第5章有详细的讲解,公众号里好像忘记发表了,找了一圈没找到)分布式
不少人会忽略掉这个因素,其实分区也是提高性能的一种很是有效的方式,这种方式所带来的效果会比前面所说的日志编码、消息压缩等更加的明显。分区在其余分布式组件中也有大量涉及,至于为何分区可以提高性能这种基本知识在这里就不在赘述了。不过须要注意,一昧地增长分区并不能一直带来性能的提高,有兴趣的同窗能够看一下这篇《Kafka主题中的分区数越多吞吐量就越高?》。性能
绝大多数的资料在讲述Kafka性能优化的举措之时是不会说起一致性的东西的。咱们所了解的通用的一致性协议如Paxos、Raft、Gossip等,而Kafka另辟蹊径采用相似PacificA的作法不是“拍大腿”拍出来的,采用这种模型会提高整理的效率。具体的细节后面会整理一篇,相似《在Kafka中使用Raft替换PacificA的可行性分析及优缺点》。优化
操做系统能够针对线性读写作深层次的优化,好比预读(read-ahead,提早将一个比较大的磁盘块读入内存) 和后写(write-behind,将不少小的逻辑写操做合并起来组成一个大的物理写操做)技术。Kafka 在设计时采用了文件追加的方式来写入消息,即只能在日志文件的尾部追加新的消 息,而且也不容许修改已写入的消息,这种方式属于典型的顺序写盘的操做,因此就算 Kafka 使用磁盘做为存储介质,它所能承载的吞吐量也不容小觑。
为何Kafka性能这么高?当遇到这个问题的时候不少人都会想到上面的顺序写盘这一点。其实在顺序斜盘前面还有页缓存(PageCache)这一层的优化。
页缓存是操做系统实现的一种主要的磁盘缓存,以此用来减小对磁盘 I/O 的操做。具体 来讲,就是把磁盘中的数据缓存到内存中,把对磁盘的访问变为对内存的访问。为了弥补性 能上的差别,现代操做系统愈来愈“激进地”将内存做为磁盘缓存,甚至会很是乐意将全部 可用的内存用做磁盘缓存,这样当内存回收时也几乎没有性能损失,全部对于磁盘的读写也 将经由统一的缓存。
当一个进程准备读取磁盘上的文件内容时,操做系统会先查看待读取的数据所在的页 (page)是否在页缓存(pagecache)中,若是存在(命中)则直接返回数据,从而避免了对物 理磁盘的 I/O 操做;若是没有命中,则操做系统会向磁盘发起读取请求并将读取的数据页存入 页缓存,以后再将数据返回给进程。一样,若是一个进程须要将数据写入磁盘,那么操做系统也会检测数据对应的页是否在页缓存中,若是不存在,则会先在页缓存中添加相应的页,最后将数据写入对应的页。被修改事后的页也就变成了脏页,操做系统会在合适的时间把脏页中的 数据写入磁盘,以保持数据的一致性。
对一个进程而言,它会在进程内部缓存处理所需的数据,然而这些数据有可能还缓存在操 做系统的页缓存中,所以同一份数据有可能被缓存了两次。而且,除非使用 Direct I/O 的方式, 不然页缓存很难被禁止。此外,用过 Java 的人通常都知道两点事实:对象的内存开销很是大, 一般会是真实数据大小的几倍甚至更多,空间使用率低下;Java 的垃圾回收会随着堆内数据的 增多而变得愈来愈慢。基于这些因素,使用文件系统并依赖于页缓存的作法明显要优于维护一 个进程内缓存或其余结构,至少咱们能够省去了一份进程内部的缓存消耗,同时还能够经过结构紧凑的字节码来替代使用对象的方式以节省更多的空间。如此,咱们能够在 32GB 的机器上使用 28GB 至 30GB 的内存而不用担忧 GC 所带来的性能问题。此外,即便 Kafka 服务重启, 页缓存仍是会保持有效,然而进程内的缓存却须要重建。这样也极大地简化了代码逻辑,由于 维护页缓存和文件之间的一致性交由操做系统来负责,这样会比进程内维护更加安全有效。
Kafka 中大量使用了页缓存,这是 Kafka 实现高吞吐的重要因素之一。虽然消息都是先被写入页缓存,而后由操做系统负责具体的刷盘任务的。
我在好久以前就以前就发过一篇《什么是Zero Copy》,若是对Zero Copy不了解的同窗能够翻阅一下。Kafka使用了Zero Copy技术提高了消费的效率。前面所说的Kafka将消息先写入页缓存,若是消费者在读取消息的时候若是在页缓存中能够命中,那么能够直接从页缓存中读取,这样又节省了一次从磁盘到页缓存的copy开销。另外对于读写的概念能够进一步了解一下什么是写放大和读放大。
一个磁盘IO流程能够参考下图:
本文罗列了一些Kafka的在性能优化方面的要领。本文中的全部内容都在《深刻理解Kafka》一书中有讲解,只是散落在各处而已,按照既定的顺序编排,力求从易入难。若是在书中再采用篇幅去罗列相似主题的话,会出现知识讲解的冗余,故没有在书中再次整理赘述,不过这些内容会在公众号里发表出来,前面已经按照其它维度整理过好几篇了。若是须要新的维度内容,能够在公众号里留言,诉求很大的话我会对此整理一篇的,这篇文章就是这么来的。
欢迎支持笔者小册:《图解Kafka之实战指南》和《图解Kafka之核心原理》
欢迎支持笔者新做:《深刻理解Kafka:核心设计与实践原理》和《RabbitMQ实战指南》,同时欢迎关注笔者的微信公众号:朱小厮的博客。