上篇文章介绍了springboot集成kafka并且成功消费消息。
kafka安装及使用---Kafka从入门到精通(二)
1、消息引擎范型
最常见的消息引擎范型是 消息队列模型 和 发布/订阅 模型。
消息队列(message queue)模型是基于队列提供消息传输服务的,多用于进程间的通信以及线程间的通信。该模式定义了消息队列queue,发送者sender,接收者receiver,提供了一种点对点的消息传递方式,即发送者发送每条消息到队列制定位置,接收者从指定位置获取消息,一旦消息被消费,会从队列移除,发送者和消费者都是点对点一一对应,不会被其他消费者处理。
发布/订阅模式(public/subscribe)与前面消息队列模式不同这里有一个主题(topic)的概念:一个topic可以理解为逻辑语义相近的消息容器,发布者吧消息发入指定的topic中,所有订阅了该topic的都可以消费该消息。
显然kafka必须要同时支持这两种消息模型,后面我们将深入探讨消息组(consumer group)概念来同事支持这两种模型。
2、kafka概要设计
Kafka设计的初衷就是为了处理大量数据的实施传输,为了实现这个目标,kafka在设计之初就考虑四个方面。1)吞吐量/延迟2)消息持久化3)负载均衡和故障转移4)伸缩性。
1、吞吐量/延迟:
对于任何消息引擎而言,吞吐量都是至关重要的性能指标。何为吞吐量,就是处理能力的最大值。对于kafka而言,他的吞吐量就是每秒处理消息的数量。
延迟也是一个性能指标,它衡量的是一段时间的间隔,可能是发出某个操作与接受操作响应之间的间隔,对于kafka而言,延迟可以表示客户端发起请求与服务端处理请求并且响应给客户端这一段时间,显而易见,延迟越低越好。
实际场景中,这两者其实是矛盾的,即调优的时候会使一个指标变差,当然他们的关系也不是等比的此消彼长,比如不可能牺牲百分之20的吞吐量就可以提高百分之20的延迟。假设kafka消费一个消息是2ms,这时候1000/2=500,则1秒能消费500条消息。但如果我们采取batching批量处理的方法,还是用上面的方式,则结果就大不一样了,这次我们不是一条一条的发消息,而是一小批小批(micro-batch)的发,假设我们发之前首先等待8ms,那么延迟就是10ms,但是如果这10ms里批量发了1000条消息,这时候500*100,则1秒则消费50000条数据。这就是kafka消费的设计思路。
好了,那么kafka而言是如何做到高吞吐量和低延迟的呢,首先,kafka的写入操作很快,这得益于对磁盘的使用方法不同,虽然kafka会持久化数据到磁盘上,但本质上每次写入操作都是吧数据写入磁盘操作系统的缓存页(page cache),然后由操作系统自行决定什么时候吧数据刷到磁盘上。这样主要有三个优势:
1)操作系统缓存页在内存中分配,所以消息写入的速度非常快。
2)Kafka不必与底层的文件系统打交道,频繁的I/O操作都交给操作系统。
3)Kafka写入操作都是顺序写入,避免了磁盘的随机I/O.
对于普通物理磁盘而言(非固态),I/O磁盘读写是非常慢的,但是磁盘的顺序读写其实不慢,他的速度甚至可以和内存的随机I/O比较。所以kafka依据这一原理,写入的时候都通过append方式,尾部写入法,并且不允许修改已经写入的数据,因此属于典型的顺序访问磁盘,在实际操作中可以很轻松的做到几万甚至几十万条数据。
那么kafka消费端如何做到高吞吐,低延迟的呢。前面说了kafka并不会参与I/O,直接给操作系统来做,他是先把数据写入系统缓存页,那么kafka在消费消息的时候,也是先从缓存页获取的,如果命中缓存,则直接把缓存页的数据发送到网络的socket上,这个过程是利用linux平台的sendfile系统调用的,而这种技术就是大名鼎鼎的零拷贝(zero copy)技术。
(zero copy和零拷贝是什么?传统的linux操作系统中I/O接口依托数据拷贝来实现的,但在零拷贝技术出来之前,一个I/O操作会将同一份数据进行多次拷贝,数据的传输过程中还设计内核态与用户态的上下文切换,cpu开销很大,零拷贝很好的改善了这个问题,首先内核驱动处理I/O数据时候,不再需要进行上下文切换,节省内核缓冲区与用户态应用之前的拷贝,故因此得名零拷贝)
Linux提供的sendFile系统调用实现这种零拷贝,而kafka消费机制用的就是sendFile。
所以总结下来:
1、kafka使用大量缓存页,而且命中率高,直接把消息发送网络socket。
2、Kafka不参与系统的I/O,这是交给系统。
3、就算存入磁盘,也是采用顺序I/O的方式。
4、使用sendFile的零拷贝技术加强网络传输效率。
2、消息持久化:
Kafka吧消息持久化到磁盘,好处如下:
1)解耦消息发送与消息消费:本质来说kafka核心功能提供生产和消费,他们互不干涉,只是简单的吧消息生产出来给kafka服务器保存即可,提高吞吐量。
2)实现灵活的消息处理:很多kafka下游子系统消费了消息之后,日后的某个节点可能又想消费之前的数据,即所谓的消息重推,消息持久化则可以方便这样的处理。
另外kafka消息持久化也有新颖之处,普通的系统在使用持久化的时候尽量先使用内存,当内存不足,才考虑持久化,kafka反其道行之,直接吧数据写入到持久化日志中,之后则直接返回客户端写入成功,这样做能够实时保存,又能够节省内存开销。
3、负载均衡和故障转移:
作为一个完成的分布式系统,kafka如果只是基本的消息引擎肯定不足以脱颖而出,一套完整的分布式系统必然有负载均衡(load balancing)和故障转移(fail-over)功能。
负载均衡就是把数据按一定规则负载均衡分配到所参与的工作服务器上,而最大的提升运行效率。具体到kafka来说,默认情况下kafka的每天服务器都有均等机会为kafka的客户提供服务,可以吧负载分散到集群的机器上,避免一台负载过高。
Kafka是通过partition leader flower election 来智能化实现负载,每个broken会存在不同的partition0 leader 和partition0 flower。
除了负载均衡外,还有故障迁移。当服务器意外终止的时候,整个集群会立刻检测到fail服务器,故障转移一般通过“心跳”“会话”机制来实现,只要注册到会话中心的会话超时,那么就默认主服务器无法正常运行,这时候就会启动某个备份来运行。Kafka是通过把服务注册到zookeeper中,一旦该服务器停止,则会选举另一个服务器来继续提供服务。
4、伸缩性:
所谓伸缩性,英文名是scalability,伸缩性表示分布式系统中增加额外的计算资源(比如cpu、内存、存储、宽带)时吞吐量提升的能力,对于计算密集型的,cpu消耗肯定巨大,这时候我们单一cpu感觉不够用,自然想有两个cpu,即线性的扩容计算能力,这种线性伸缩是最理想的,但实际中不可能达到,毕竟分布式系统中很多隐藏的‘单点’瓶颈限制了这种扩张。
阻碍线性扩张的常见因素就是状态保存,我们知道,不论哪个分布式系统,集群都会维护很多内部状态,如果由服务器自己来处理,则需要考虑状态一致性,单如果服务器是无状态的,所有的状态都由专门协调的服务来做(比如zokeeper),那么整个集群服务器就无须繁重的状态共享。
Kafka正是采用这样的思想,每台服务器的状态都是由zookeeper来存储,扩展只需要启动新的kafka就可以,会注入到zookeeper。这里要申明,kafka服务器并不是所有的状态都不保存,肯定会保存一些轻量级的状态,基本不影响效率,整个集群之间维护轻量级状态一致性消耗很低。