共计 7577 个字符,预计需要花费 19 分钟才能阅读完成。
『码哥』的 Redis 系列文章有一篇讲透了 Redis 的性能优化 ——《Redis 外围篇:唯快不破的机密》。深刻地从 IO、线程、数据结构、编码等方面分析了 Redis“快”的外部机密。65 哥深受启发,在学习 Kafka 的过程中,发现 Kafka 也是一个性能非常优良的中间件,遂要求『码哥』讲一讲 Kafka 性能优化方面的常识,所以『码哥』决定将这篇性能方面的博文作为 Kafka 系列的开篇之作。
先预报一下 Kafka 系列文章,大家敬请期待哦:
以解说性能作为 Kafka 之旅的开篇之作,让咱们一起来深刻理解 Kafka“快”的外部机密。你不仅能够学习到 Kafka 性能优化的各种伎俩,也能够提炼出各种性能优化的方法论,这些方法论也能够利用到咱们本人的我的项目之中,助力咱们写出高性能的我的项目。
关公战秦琼
65: Redis 和 Kafka 齐全是不同作用的中间件,有比拟性吗?
是的,所以此文讲的不是《分布式缓存的选型》
,也不是《分布式中间件比照》
。咱们聚焦于这两个不同畛域的我的项目对性能的优化,看一看优良我的项目对性能优化的通用伎俩,以及在针对不同场景下的特色的优化形式。
很多人学习了很多货色,理解了很多框架,但在遇到理论问题时,却经常会感觉到常识有余。这就是没有将学习到的常识体系化,没有从具体的实现中形象出能够卓有成效的 方法论
。
学习开源我的项目很重要的一点就是 演绎
,将不同我的项目的优良实现总结出方法论,而后 演绎
到自我的实际中去。
码哥寄语
感性、主观、审慎是程序员的特点,也是长处,然而很多时候咱们也须要带一点理性,带一点激动,这个时候能够帮忙咱们更快的做决策。「悲观者正确、乐观者胜利。」心愿大家都是一个乐观地解决问题的人。
Kafka 性能全景
从高度形象的角度来看,性能问题逃不出上面三个方面:
- 网络
- 磁盘
- 复杂度
对于 Kafka 这种网络分布式队列来说,网络和磁盘更是优化的重中之重。针对于下面提出的形象问题,解决方案高度形象进去也很简略:
- 并发
- 压缩
- 批量
- 缓存
- 算法
晓得了问题和思路,咱们再来看看,在 Kafka 中,有哪些角色,而这些角色就是能够优化的点:
- Producer
- Broker
- Consumer
是的,所有的问题,思路,优化点都曾经列出来了,咱们能够尽可能的细化,三个方向都能够细化,如此,所有的实现便高深莫测,即便不看 Kafka 的实现,咱们本人也能够想到一二点能够优化的中央。
这就是思考形式。提出问题
> 列出问题点
> 列出优化办法
> 列出具体可切入的点
> tradeoff 和细化实现
。
当初,你也能够尝试本人想一想优化的点和办法,不必尽如人意,不必管好不好实现,想一点是一点。
65 哥:不行啊,我很笨,也很懒,你还是间接和我说吧,我白嫖比拟行。
程序写
65 哥:人家 Redis 是基于纯内存的零碎,你 kafka 还要读写磁盘,能比?
为什么说写磁盘慢?
咱们不能只晓得论断,而不知其所以然。要答复这个问题,就得回到在校时咱们学的操作系统课程了。65 哥还留着课本吗?来,翻到讲磁盘的章节,让咱们回顾一下磁盘的运行原理。
65 哥:鬼还留着哦,课程还没上到一半书就没了。要不是考试俺眼神好,预计当初还没毕业。
看经典大图:
实现一次磁盘 IO,须要通过 寻道
、 旋转
和数据传输
三个步骤。
影响磁盘 IO 性能的因素也就产生在下面三个步骤上,因而次要破费的工夫就是:
- 寻道工夫:Tseek 是指将读写磁头挪动至正确的磁道上所须要的工夫。寻道工夫越短,I/O 操作越快,目前磁盘的均匀寻道工夫个别在 3-15ms。
- 旋转提早:Trotation 是指盘片旋转将申请数据所在的扇区挪动到读写磁盘下方所须要的工夫。旋转提早取决于磁盘转速,通常用磁盘旋转一周所需工夫的 1/2 示意。比方:7200rpm 的磁盘均匀旋转提早大概为 60*1000/7200/2 = 4.17ms,而转速为 15000rpm 的磁盘其均匀旋转提早为 2ms。
- 数据传输工夫:Ttransfer 是指实现传输所申请的数据所须要的工夫,它取决于数据传输率,其值等于数据大小除以数据传输率。目前 IDE/ATA 能达到 133MB/s,SATA II 可达到 300MB/s 的接口数据传输率,数据传输工夫通常远小于前两局部耗费工夫。简略计算时可疏忽。
因而,如果在写磁盘的时候省去 寻道
、 旋转
能够极大地提高磁盘读写的性能。
Kafka 采纳 程序写
文件的形式来进步磁盘写入性能。程序写
文件,根本缩小了磁盘 寻道
和旋转
的次数。磁头再也不必在磁道上乱舞了,而是一路向前飞速前行。
Kafka 中每个分区是一个有序的,不可变的音讯序列,新的音讯一直追加到 Partition 的开端,在 Kafka 中 Partition 只是一个逻辑概念,Kafka 将 Partition 划分为多个 Segment,每个 Segment 对应一个物理文件,Kafka 对 segment 文件追加写,这就是程序写文件。
65 哥:为什么 Kafka 能够应用追加写的形式呢?
这和 Kafka 的性质无关,咱们来看看 Kafka 和 Redis,说白了,Kafka 就是一个 Queue
,而 Redis 就是一个HashMap
。Queue
和Map
的区别是什么?
Queue
是 FIFO 的,数据是有序的;HashMap
数据是无序的,是随机读写的。Kafka 的不可变性,有序性使得 Kafka 能够应用追加写的形式写文件。
其实很多合乎以上个性的数据系统,都能够采纳追加写的形式来优化磁盘性能。典型的有 Redis
的 AOF 文件,各种数据库的 WAL(Write ahead log)
机制等等。
所以分明明确本身业务的特点,就能够针对性地做出优化。
零拷贝
65 哥:哈哈,这个我面试被问到过。惋惜答得一般般,唉。
什么是零拷贝?
咱们从 Kafka 的场景来看,Kafka Consumer 生产存储在 Broker 磁盘的数据,从读取 Broker 磁盘到网络传输给 Consumer,期间波及哪些零碎交互。Kafka Consumer 从 Broker 生产数据,Broker 读取 Log,就应用了 sendfile。如果应用传统的 IO 模型,伪代码逻辑就如下所示:
readFile(buffer)
send(buffer)
如图,如果采纳传统的 IO 流程,先读取网络 IO,再写入磁盘 IO,理论须要将数据 Copy 四次。
- 第一次:读取磁盘文件到操作系统内核缓冲区;
- 第二次:将内核缓冲区的数据,copy 到应用程序的 buffer;
- 第三步:将应用程序 buffer 中的数据,copy 到 socket 网络发送缓冲区;
- 第四次:将 socket buffer 的数据,copy 到网卡,由网卡进行网络传输。
65 哥:啊,操作系统这么傻吗?copy 来 copy 去的。
并不是操作系统傻,操作系统的设计就是每个应用程序都有本人的用户内存,用户内存和内核内存隔离,这是为了程序和系统安全思考,否则的话每个应用程序内存满天飞,随便读写那还得了。
不过,还有 零拷贝
技术,英文——Zero-Copy
。零拷贝
就是尽量去缩小下面数据的拷贝次数,从而缩小拷贝的 CPU 开销,缩小用户态内核态的上下文切换次数,从而优化数据传输的性能。
常见的零拷贝思路次要有三种:
- 间接 I/O:数据间接跨过内核,在用户地址空间与 I/O 设施之间传递,内核只是进行必要的虚拟存储配置等辅助工作;
- 防止内核和用户空间之间的数据拷贝:当应用程序不须要对数据进行拜访时,则能够防止将数据从内核空间拷贝到用户空间;
- 写时复制:数据不须要提前拷贝,而是当须要批改的时候再进行局部拷贝。
Kafka 应用到了 mmap
和 sendfile
的形式来实现 零拷贝
。别离对应 Java 的 MappedByteBuffer
和 FileChannel.transferTo
。
应用 Java NIO 实现 零拷贝
,如下:
FileChannel.transferTo()
在此模型下,上下文切换的数量缩小到一个。具体而言,transferTo()
办法批示块设施通过 DMA 引擎将数据读取到读取缓冲区中。而后,将该缓冲区复制到另一个内核缓冲区以暂存到套接字。最初,套接字缓冲区通过 DMA 复制到 NIC 缓冲区。
咱们将正本数从四缩小到三,并且这些正本中只有一个波及 CPU。咱们还将上下文切换的数量从四个缩小到了两个。这是一个很大的改良,然而还没有查问零正本。当运行 Linux 内核 2.4 及更高版本以及反对收集操作的网络接口卡时,后者能够作为进一步的优化来实现。如下所示。
依据后面的示例,调用 transferTo()
办法会使设施通过 DMA 引擎将数据读取到内核读取缓冲区中。然而,应用 gather
操作时,读取缓冲区和套接字缓冲区之间没有复制。取而代之的是,给 NIC 一个指向读取缓冲区的指针以及偏移量和长度,该偏移量和长度由 DMA 革除。CPU 相对不参加复制缓冲区。
对于 零拷贝
详情,能够详读这篇文章零拷贝 (Zero-copy) 浅析及其利用。
PageCache
producer 生产音讯到 Broker 时,Broker 会应用 pwrite() 零碎调用【对应到 Java NIO 的 FileChannel.write() API】按偏移量写入数据,此时数据都会先写入page cache
。consumer 生产音讯时,Broker 应用 sendfile() 零碎调用【对应 FileChannel.transferTo() API】,零拷贝地将数据从 page cache 传输到 broker 的 Socket buffer,再通过网络传输。
leader 与 follower 之间的同步,与下面 consumer 生产数据的过程是同理的。
page cache
中的数据会随着内核中 flusher 线程的调度以及对 sync()/fsync() 的调用写回到磁盘,就算过程解体,也不必放心数据失落。另外,如果 consumer 要生产的音讯不在 page cache
里,才会去磁盘读取,并且会顺便预读出一些相邻的块放入 page cache,以不便下一次读取。
因而如果 Kafka producer 的生产速率与 consumer 的生产速率相差不大,那么就能简直只靠对 broker page cache 的读写实现整个生产 – 生产过程,磁盘拜访非常少。
网络模型
65 哥:网络嘛,作为 Java 程序员,天然是 Netty
是的,Netty 是 JVM 畛域一个优良的网络框架,提供了高性能的网络服务。大多数 Java 程序员提到网络框架,首先想到的就是 Netty。Dubbo、Avro-RPC 等等优良的框架都应用 Netty 作为底层的网络通信框架。
Kafka 本人实现了网络模型做 RPC。底层基于 Java NIO,采纳和 Netty 一样的 Reactor 线程模型。
Reacotr 模型次要分为三个角色
- Reactor:把 IO 事件调配给对应的 handler 解决
- Acceptor:解决客户端连贯事件
- Handler:解决非阻塞的工作
在传统阻塞 IO 模型中,每个连贯都须要独立线程解决,当并发数大时,创立线程数多,占用资源;采纳阻塞 IO 模型,连贯建设后,若以后线程没有数据可读,线程会阻塞在读操作上,造成资源节约
针对传统阻塞 IO 模型的两个问题,Reactor 模型基于池化思维,防止为每个连贯创立线程,连贯实现后将业务解决交给线程池解决;基于 IO 复用模型,多个连贯共用同一个阻塞对象,不必期待所有的连贯。遍历到有新数据能够解决时,操作系统会告诉程序,线程跳出阻塞状态,进行业务逻辑解决
Kafka 即基于 Reactor 模型实现了多路复用和解决线程池。其设计如下:
其中蕴含了一个 Acceptor
线程,用于解决新的连贯,Acceptor
有 N 个 Processor
线程 select 和 read socket 申请,N 个 Handler
线程解决申请并相应,即解决业务逻辑。
I/O 多路复用能够通过把多个 I/O 的阻塞复用到同一个 select 的阻塞上,从而使得零碎在单线程的状况下能够同时解决多个客户端申请。它的最大劣势是零碎开销小,并且不须要创立新的过程或者线程,升高了零碎的资源开销。
总结: Kafka Broker 的 KafkaServer
设计是一个优良的网络架构,有想理解 Java 网络编程,或须要应用到这方面技术的同学无妨去读一读源码。后续『码哥』的 Kafka 系列文章也将波及这块源码的解读。
批量与压缩
Kafka Producer 向 Broker 发送音讯不是一条音讯一条音讯的发送。应用过 Kafka 的同学应该晓得,Producer 有两个重要的参数:batch.size
和linger.ms
。这两个参数就和 Producer 的批量发送无关。
Kafka Producer 的执行流程如下图所示:
发送音讯顺次通过以下处理器:
- Serialize:键和值都依据传递的序列化器进行序列化。优良的序列化形式能够进步网络传输的效率。
- Partition:决定将音讯写入主题的哪个分区,默认状况下遵循 murmur2 算法。自定义分区程序也能够传递给生产者,以管制应将音讯写入哪个分区。
- Compress:默认状况下,在 Kafka 生产者中不启用压缩.Compression 不仅能够更快地从生产者传输到代理,还能够在复制过程中进行更快的传输。压缩有助于进步吞吐量,升高提早并进步磁盘利用率。
- Accumulate:
Accumulate
顾名思义,就是一个音讯累计器。其外部为每个 Partition 保护一个Deque
双端队列,队列保留将要发送的批次数据,Accumulate
将数据累计到肯定数量,或者在肯定过期工夫内,便将数据以批次的形式发送进来。记录被累积在主题每个分区的缓冲区中。依据生产者批次大小属性将记录分组。主题中的每个分区都有一个独自的累加器 / 缓冲区。 - Group Send:记录累积器中分区的批次按将它们发送到的代理分组。批处理中的记录基于 batch.size 和 linger.ms 属性发送到代理。记录由生产者依据两个条件发送。当达到定义的批次大小或达到定义的延迟时间时。
Kafka 反对多种压缩算法:lz4、snappy、gzip。Kafka 2.1.0 正式反对 ZStandard —— ZStandard 是 Facebook 开源的压缩算法,旨在提供超高的压缩比 (compression ratio),具体细节参见 zstd。
Producer、Broker 和 Consumer 应用雷同的压缩算法,在 producer 向 Broker 写入数据,Consumer 向 Broker 读取数据时甚至能够不必解压缩,最终在 Consumer Poll 到音讯时才解压,这样节俭了大量的网络和磁盘开销。
分区并发
Kafka 的 Topic 能够分成多个 Partition,每个 Paritition 相似于一个队列,保证数据有序。同一个 Group 下的不同 Consumer 并发生产 Paritition,分区实际上是调优 Kafka 并行度的最小单元,因而,能够说,每减少一个 Paritition 就减少了一个生产并发。
Kafka 具备优良的分区调配算法——StickyAssignor,能够保障分区的调配尽量地平衡,且每一次重调配的后果尽量与上一次调配后果保持一致。这样,整个集群的分区尽量地平衡,各个 Broker 和 Consumer 的解决不至于呈现太大的歪斜。
65 哥:那是不是分区数越多越好呢?
当然不是。
越多的分区须要关上更多的文件句柄
在 kafka 的 broker 中,每个分区都会对照着文件系统的一个目录。在 kafka 的数据日志文件目录中,每个日志数据段都会调配两个文件,一个索引文件和一个数据文件。因而,随着 partition 的增多,须要的文件句柄数急剧减少,必要时须要调整操作系统容许关上的文件句柄数。
客户端 / 服务器端须要应用的内存就越多
客户端 producer 有个参数 batch.size,默认是 16KB。它会为每个分区缓存音讯,一旦满了就打包将音讯批量收回。看上去这是个可能晋升性能的设计。不过很显然,因为这个参数是分区级别的,如果分区数越多,这部分缓存所需的内存占用也会更多。
升高高可用性
分区越多,每个 Broker 上调配的分区也就越多,当一个产生 Broker 宕机,那么复原工夫将很长。
文件构造
Kafka 音讯是以 Topic 为单位进行归类,各个 Topic 之间是彼此独立的,互不影响。每个 Topic 又能够分为一个或多个分区。每个分区各自存在一个记录音讯数据的日志文件。
Kafka 每个分区日志在物理上理论按大小被分成多个 Segment。
- segment file 组成:由 2 大部分组成,别离为 index file 和 data file,此 2 个文件一一对应,成对呈现,后缀”.index”和“.log”别离示意为 segment 索引文件、数据文件。
- segment 文件命名规定:partion 全局的第一个 segment 从 0 开始,后续每个 segment 文件名为上一个 segment 文件最初一条音讯的 offset 值。数值最大为 64 位 long 大小,19 位数字字符长度,没有数字用 0 填充。
index 采纳稠密索引,这样每个 index 文件大小无限,Kafka 采纳 mmap
的形式,间接将 index 文件映射到内存,这样对 index 的操作就不须要操作磁盘 IO。mmap
的 Java 实现对应 MappedByteBuffer
。
65 哥笔记:mmap 是一种内存映射文件的办法。行将一个文件或者其它对象映射到过程的地址空间,实现文件磁盘地址和过程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,过程就能够采纳指针的形式读写操作这一段内存,而零碎会主动回写脏页面到对应的文件磁盘上,即实现了对文件的操作而不用再调用 read,write 等零碎调用函数。相同,内核空间对这段区域的批改也间接反映用户空间,从而能够实现不同过程间的文件共享。
Kafka 充分利用二分法来查找对应 offset 的音讯地位:
- 依照二分法找到小于 offset 的 segment 的.log 和.index
- 用指标 offset 减去文件名中的 offset 失去音讯在这个 segment 中的偏移量。
- 再次用二分法在 index 文件中找到对应的索引。
- 到 log 文件中,程序查找,直到找到 offset 对应的音讯。
总结
Kafka 是一个优良的开源我的项目。其在性能下面的优化做的酣畅淋漓,是很值得咱们深刻学习的一个我的项目。无论是思维还是实现,咱们都应该认真的去看一看,想一想。
Kafka 性能优化:
- 零拷贝网络和磁盘
- 优良的网络模型,基于 Java NIO
- 高效的文件数据结构设计
- Parition 并行和可扩大
- 数据批量传输
- 数据压缩
- 程序读写磁盘
- 无锁轻量级 offset
往期回顾
- 图解 | 搞定分布式,程序员进阶之路
- 从面试角度一文学完 Kafka
- 不可不知的软件架构模式
- Redis 日志篇:无畏宕机疾速复原的杀手锏
文章如有谬误,感激斧正,关注我,获取真正的硬核知识点。另外技术读者群也开明了,后盾回复「加群」获取「码哥字节」作者微信,一起成长交换。
以上就是 Kafka“快”的机密,感觉不错请点赞、分享,「码哥字节」感激不尽。