吞吐量/延时
对于任何一个消息引擎而言,吞吐量都是至关重要的性能指标。何为吞吐量?吞吐量是某种处理能力的最大值。对于kafka而言,它的吞吐量就是每秒能处理的消息数或者每秒能处理的字节数。很显然,我们希望消息引擎的吞吐量越大越好。
消息引擎还有一个名为延时的性能指标。他衡量的是一段时间间隔,可能是发出某个操作与接受操作响应之间的时间。或者是系统中导致某些物理变更的起始时刻与变更正式生效时刻之间的间隔。对于Kafka而言,延时可以表示客户端发起请求与服务器端处理请求并发送响应给客户端之间的这一段时间。显而易见,延时间隔越短越好。
那么对于Kafka而言,他是怎么做到高吞吐低延时呢?首先Kafka的写入操作是很快的,这主要得益于他对磁盘的使用方法不同。虽然kafka会持久化所有数据到磁盘,但本质上每次写入操作其实都只是把数据写入到操作系统的页缓存,然后由操作系统自行决定什么时候把页缓存的数据写回到磁盘。这样设计有三个优势。
- 操作系统缓存页是在内存中分配的,所以消息写入的速度非常快。
- kafka不必与底层的文件系统打交道。所有烦恼的I/O操作都交给操作系统来处理。
- kafka写入操作采用追加的方式,避免了磁盘随机写操作。(顺序读写是非常快的)
鉴于这个事实,kafka在设计时采用追加写入消息的方式,即只能在日志文件末未追加写入新的消息,且不允许修改已写入的消息,因此他属于典型的磁盘顺序访问型操作,所以kafka的吞吐量是很高的。实际使用中可以轻松做到每秒写入几万甚至几十万的消息。
下面我们看一下kafka的消费端是怎么做到高吞吐量低延时的。之前我们提到kafka是把消息写入操作系统的页缓存中的,那么同样的,kafka在读取消息的时候会先尝试在OS的页缓存中读取,如果命中便把消息经页缓存直接发送到网络的socket上。这个过程就是大名鼎鼎的零拷贝技术。
除了零拷贝技术,kafka由于大量使用页缓存,故读取消息时大部分消息很可能依然保存在页缓存中,因此可以直接命中缓存,不用穿透到底层的物理磁盘上获取消息,从而极大的提升了消息读取的吞吐量。事实上,如果我们监控一个经过良好调优的kafka生产集群便可以发现,即使是那些有负载均衡的kafka服务器,其磁盘读取操作也非常少,这是因为大部分的消息读取操作会直接命中缓存。
kafka提供了两种flush数据到磁盘的策略:
- log.flush.interval.messages=100该值表明当内存中消息数据达到了100条数,触发写入磁盘动作。
- log.flush.interval.ms=1000该值表明没经过1000ms即1s时触发写入磁盘动作。
所以当内存中的数据还没flush到磁盘,但是broker挂掉了,此时会丢失内存的数据。解决方案,结合生产环境设置上述两值并且对于每个topic设定大于1的replica(副本),并配合request.required.acks=-1,保证即使该broker挂掉内存数据丢失,但其它broker中仍然存在该批数据
总结一下,kafka就是依靠下列四点达到了高吞吐量、低延时的设计目标的。
- 大量的使用操作系统页缓存,内存操作速度快且命中高。
- kafka不直接参与物理层I/O操作,而是交给操作系统自己来完成。
- 采用追加的写入方式,摒弃了缓慢的磁盘随机I/O。
- 使用以sendfile为代表的零拷贝技术加强网络间的数据传输效率。
消息持久化
kafka是要持久化消息的,而且要把消息持久化到磁盘,这样做有以下几个好处。
- 解耦消息发送与消息消费:本质上讲,kafka最核心的功能就是提供了生产者-消费者模式的完整解决方案。通过将消息持久化使得生产者方不需要直接和消费者方耦合,他只是简单的把消息生产出来并交由kafka服务器保存即可,因此提高了吞吐量。
- 实现灵活的消息处理:很多kafka的下游子系统都有这样的需求——对于已经处理过的消息可能在未来的某个时间点重新处理一次,即所谓的消息重演,消息持久化便可以很方便的实现这样的需求。
kafka在实现持久化的设计也有新颖之处。普通的系统在实现持久化时可能会尽量使用内存,当内存资源耗尽,再一次性把数据刷盘;而kafka则反其道而行之,所有数据都会立即写入文件系统的持久化日志中,之后kafka从服务器才会返回结果给客户端通知他们消息已经被成功写入。这样做即实时保存了数据,又减少了kafka程序对于内存的消耗,从而将节省出来的内存给页缓存使用,更进一步提升了性能。
负载均衡和故障转移
作为一个功能完备的分布式系统,kafka如果只提供了最基本的消息引擎功能肯定不足以让他脱颖而出。一套完整的消息引擎解决方案必然要提供负载均衡和故障转移的功能。
我们先讨论负载均衡,kafka实现负载均衡实际上是通过智能化的分区领导者选举来实现的,可以在集群的所有机器上以均等机会分散给各个partion的leader,从而整体上实现了负载均衡。
除了负载均衡,完备的分布式系统还需要支持故障转移。所谓故障转移,是指当服务器意外中止时,整个集群可以快速地检测到该失效,并立即将该服务器上的应用或服务自动转移到其他服务器上。故障转移通常是以心跳或者会话机制来实现的,即只要主服务器与备份服务器之间的心跳无法维持或主服务器已无法正常运行,集群会自动重启某个备份服务器来替代服务器的工作。
kafka服务器支持故障转移的方式就是使用会话机制。每台kafka服务器启动后会以会话的形式把自己注册到zookeeper服务器上,一旦该服务器运转出现问题,与zookeeper的会话便不能维持从而超时失效,此时kafka集群会选举出另一台服务器来完全替代这台服务器继续提供服务。
伸缩性
有了消息的持久化,kafka实现了高可靠性;有了负载均衡和使用文件系统的独特设计,kafka实现了高吞吐量;有了故障转移,kafka实现了高可用性。name作为分布式系统中的高伸缩性,kafka是怎么做到的呢?
伸缩性表示向分布式系统中增加额外的计算机资源时吞吐量的提升能力。
kafka集群把每台kafka服务器的状态统一交给zookeeper保管,扩展kafka集群也只需要一步:启动新的kafka服务器即可。当然这里需要言明得是,在kafka服务器并不是所有状态都不保存,他只保存很轻量级的内部状态,因为在整个集群间维护一致性是代价很低的。