本文章内容皆出自作者阅读胡夕著Apache Kafka 实战一书的总结,可能有理解错误,仅作为参考。如有侵权,笔者将会删除它们。
注:可能很多人包括我自己都会把Kafka等一些消息系统称为消息中间件或者消息队列,而书中称他们为消息系统或者消息引擎。
1、Kafka设计
1.1、性能指标
吞吐量:对于Kafka我们可以单纯的认为,生产者单位时间内可以向Kafka发送多少条消息。
延时:生产者向Kafka发送消息到消息成功被创建经历的时间。
对于一个消息引擎而言,我们肯定希望他既能够有很高的吞吐量又有很低的时延,但是世界的万物都很难做到完美,它们是一个矛盾体,即当调优一个吞吐量时通常会使时延变大。但是通常情况下他们不是等比例增长或者减少。笔者当时看这里的时候也是不太理解,因为如果生产者生产一条消息的时延是2ms,那么一分钟的吞吐量就是500(1000/2),这不应该是一个必然的吗。但是书中的作者还是给出了例子,就是批处理。
假设我们将生产者比作快递员,如果他收到一个快递就去派件的话那么通常情况下,吞吐量和时延是等比例增长消退的的,但是如果快递员在物流站等待了10毫秒,快递堆积了100个那么是不是一秒钟的吞吐量大约我们可以认为是10000个,那么时延增加了4倍,但是吞吐量变成了20倍,但是这个例子我认为是不准确的,我们不知道10ms到底能堆积多少消息,这个例子只是想让读者知道他们的关系并不是等比例的。
1.2、巧妙使用操作系统的文件系统
以上两个性能指标我们姑且聊到这里,我们现在要探究Kafka是如何做到低延时,高吞吐的。
我们都知道Kafka的消息是保存在磁盘的,一说到磁盘我们自然会牵扯到IO,那么Kafka怎么做到突破磁盘IO性能瓶颈的呢,笔者印象中当我们Java程序进行磁盘写操作时不会直接写进磁盘而且先写入一个缓冲区中,这个缓冲区是内存空间的一部分,所以对于他的写入是很快的,同样Kafka也是利用了这一特点,当有消息需要写入时他会先写入缓冲区然后交由操作系统来决定什么时候刷入磁盘。这大大提高了Kafka的效率,与此同时我们新写入的消息是保存在内存中缓冲区的,所以访问速度也很快,所Kafka在读取消息时会先从缓冲区中找,这也一定的提高了我们读取的效率。
再一个Kafka利用了零拷贝来进一步提高了消息消费效率。下面是书中作者贴的图。
上面两个图清晰的表达了两个数据传输的区别,对于我们一条消息的消费,传统的传输模式应该是 硬盘(如果内存缓冲区未命中)—>内存缓冲区—>应用程序—>Socke—>和网卡,在此过程中经历了很多多余的数据拷贝,为了优化这一资源浪费Linux添加了一个零拷贝的系统调用,就变成了图1.7的模式。
另外一个方面就是对硬盘读取速度的优化,对于磁盘的读取来说更大一部分时间用来了磁盘寻址,所以Kafka为了优化速度采取了追加的模式来写消息,然后顺序读这很大的缩短了磁盘IO时间,下图为书中磁盘与内存读的对比。
图中我们可以看到磁盘的顺序访问甚至可以和内存掰掰手腕,所以绝对不要小看磁盘的速度。
1.3、负载均衡和故障转移
在现在分布式系统中,这两个名词似乎是必不可缺,天生支持分布式的Kafka也自然的支持了这两个功能。
Kafka内置了只能的负载均衡算法,他可以将所有消息均衡的分配在各个分区中,具体的算法我们后续就讲。
我们都知道Kafka集群是通过ZK来管理的(但是现在Kafka越来越想脱离ZK,笔者认为可能是因为这样使用起来更轻巧简单),所以各个节点不需要保存很多状态,统一由ZK来管理。ZK会定时检查心跳连接,如果某个节点的发生故障,集群会选举新的合适的follower节点来代替它,从而来避免一个节点出现故障导致整个集群无法提供服务。
1.4、伸缩性
伸缩性指的是当我们对分布式系统中增加新的计算机资源(CPU 内存和存储等)时系统吞吐量的提升。这个解释是书中作者给出的,但是笔者认为对于Kafka来说,我们倒不如说加一台机器对整个集群的吞吐量提升这样更容易理解。段落下面是书中原文作者的原话,以免笔者理解错误误导读者。举个例子,对于Kafka集群而言,假设我们有5台机器时他的吞吐量可以达到X(条/秒)。当我们从新添加了5台机器时我们希望他的吞吐量可以达到2X。这是最理想的状态。事实上并不能完全达到。
书中作者认为影响分布式系统的伸缩性的重要因素是集群各个节点需要保存集群的状态,这导致当集群节点变多时会带来更多的集群状态一致性的维护消耗,所以导致理想状态并不容易达到。
上文中我们说到Kafka集群是交由ZK来管理的,所以每个节点只需保存少一部分状态即可,这很大程度上使得Kafka的伸缩性更加优异,但是伸缩性提升的同时带来的是我们需要额外去维护ZK。
1.5、ISR
我们最后了解一个Kafka中很重要的一个机制ISR。
1.5.1、为什么会有这个集合
对于一个消息引擎保证消息的不丢失尤为重要,为什么会引入这么一个集合呢。Kafka是一个天生支持分布式的,而且上文中我们也说过了它的故障转移策略,所以每个leader必然会有多个follower,那么我们生产者生产一条消息时如果由于网络等问题导致follower中消息与leader中不一致时,恰巧leader又宕机了,又恰巧消息缺失的follower当选了leader,这一系列的恰好导致了数据不一致,这在生产中是不允许的。那么Kafka为了解决这个问题就引入了ISR机制,ISR集合中存储着Leader及他的可用的follower,具体什么决定这一个follower是不是可用的呢。Kafka是通过follower的消息与leader的消息是否一致来决定一个follower是否可用。如果消息不一致那么他就会被从ISR中剔除,后面如果又一至了同样会加入到ISR中,当leader宕机了新的leader会从ISR集合中选举,这样保证了消息的不丢失。而且这个是不需要人工干预的,Kafka会自己决定这件事,这也使得这个机制不会给技术人员带来使用上的麻烦。
1.5.2、参数选择
说到ISR,笔者就想到了ISR的参数选择,
总结
文章中简单总结了一下Kafka系统设计层面的部分优秀思想,优秀的思想是一个优秀程序的开始。