360SDN.COM

首页/Kafka/列表

Kafka — 高性能之王道

来源:OmniStack  2017-09-20 14:07:45    评论:0点击:

Kafka发源于LinkedIn公司,于2011年成为Apache的孵化项目,随后于2012年成为Apache的主要项目之一。

Kafka是基于Scala语言实现的,并没有遵循JMS相关规范,它是一个高效率的、扩展性良好的、吞吐量极高的(最新的数据是每天利用Kafka处理的消息超过1万亿条)、容错性能较优的分布式发布订阅消息系统。

 

Kafka具有内置分区、支持数据副本的特性,适合在大规模消息处理场景中使用。

 

实际场景中对Kafka的使用也逐步从日志系统逐步衍生到其他关键业务领域(如电商系统中的异步扣库存),尤其是超高吞吐量的特性,在互联网领域,使用越来越广泛。

 

同时,其设计思想也是其他消息中间件重要的设计参考,如阿里巴巴集团开源的消息中间件RocketMQ(基于Java语言实现)在实现时就借鉴了其相关设计思路。

那么Kafka是如何实现其高性能的,来看其整体架构设计:

 

Kafka整体架构以及其消息模式

 

(根据其整体框架画出)

 

Partition

 
 
 

从字面意思理解是分区,它是kafka中最小的物理单位,一方面,由于不同Partition可位于不同机器,因此可以充分利用集群优势,实现机器间的并行处理。

 

另一方面,由于Partition在物理上对应一个文件夹,即使多个Partition位于同一个节点,也可通过配置让同一节点上的不同Partition置于不同的Disk drive上,从而实现磁盘间的并行处理,充分发挥多磁盘的优势。

 

Topic

 
 
 

从字面意思来理解是主题,或者是具有某些相同特性的同一类消息,无论是发布消息还是订阅消息都必须指定Topic。

 

Topic是一个逻辑而非物理的概念,每个Topic都包含一个或者是多个Partition,不同的Partition可以位于不同的节点上。

 

Partition在物理上对应一个本地的文件夹,一个Partition包含一个或者或者是多个Segment,每个Segment包含一个数据文件和一个与之对应的索引文件,在逻辑上,可以把一个Partition当作一个非常长的数组,可通过这个“数组”的索引(Offset)去访问其数据。

 

(摘自官网)

 

Producer

 
 
 

从字面意思来看是生产者,即消息的发布者,生产者产生或者是推送消息,即把消息放入Topic中,一个生产者可以向多个Topic发送消息。

 

Broker

 
 
 

Kafka 集群包含一个或多个服务器,服务器节点称为Broker,Broker存储Topic的数据。如果某个Topic有N个Partition,集群有N个Broker,那么每个Broker存储该Topic的一个Partition。

 

如果某个Topic有N个Partition,集群有(N+M)个Broker,那么其中有N个Broker存储该Topic的一个Partition,剩下的M个Broker不存储该Topic的Partition数据。

 

如果某Topic有N个Partition,集群中Broker数目少于N个,那么一个Broker存储该Topic的一个或多个Partition。在实际生产环境中,尽量避免这种情况的发生,这种情况容易导致Kafka集群数据不均衡。

 

Consumer

 
 
 

从字面意思来看是消费者,即消息的订阅者或者是消费者,消费者从Topic中获取消息然后进行相关逻辑处理,一个消费者同样可以从多个Topic处接受消息,一个Topic可以被多个消费者消费。

 

Log

 
 
 

在Kafka中消息队列是以og文件的形式进行存储,每个log项都有一个offset来唯一的标记一条消息,offset的值为8个字节的数字,表示此消息在此partition中所处的起始位置,每个partition在物理存储层面,有多个log file组成(称为segment),segment file的命名为"最小offset".log。

 

例如"00000000000.log";其中"最小Offset"表示此Segment中起始消息的Offset。

 

 

Kafka的消息基本上有两种模式

 
 
 

Queuing(队列模式), 在队列模式中,Consumer池从Server中读取消息,每个消息都会到达一个Consumer。

 

Publish-subscribe(发布-订阅模式) ,在发布-订阅模式中,消息被广播到所有的Consumer。

 

Kafka提供了Consumer group这个抽象概念来概括这两种模式。

 

每个Consumer隶属于一个Consumer group, 如果Consumer group订阅了某个Topic,那么它会接收到该Topic发布的每条消息,该消息只会被分配到一个Consumer上。

 

Consumer实例可以部署在不同的进程或机器上。如果所有的Consumer都具有相同的group,这种情况就和Queue模式很像,消息将会在Consumers之间负载均衡。

 

如果所有的Consumer都具有不同的Group,那这就是”发布-订阅”,消息将会广播给所有的消费者。

但实际情况中大多数Topic只有少量的逻辑上的订阅者 Consumer group,每个Group由许多的Consumer实例组成,以提高扩展性和容错性,这就是发布-订阅模式。相比于传统的消息系统,Kafka具有更强的序列保证。

传统的队列在server上保持有序,如果多个consumer从队列中消费,队列会按序弹出,然后消息被异步分配到consumer上,因此,消息到达consumer时可能会破坏顺序。

这意味着在并行处理过程中,消息处理是无序的。为了解决这个问题,消息系统的exclusive consumer机制只允许单进程从队列中消费消息,很显然这种设计损害了并行处理的能力。

 

Kafka具有更好的解决方案。通过Parallelism—The partition—Within the topics机制,Kafka能够提供良好的有序保证,使Consumer池能够负载均衡。

 

这是通过把Topic中的Partition分派给Consumer group中的Consumer来实现的。

 

因此,每个Partition由Group中一个确定的Consumer来消费。通过这种方式我们保证了Consumer是指定Partition的唯一Reader,并且按顺序消费数据。

 

由于有很多Partition,这种方式使得Consumer实例可以负载均衡。kafka只能保证一个Partition中的消息被某个Consumer消费时,消息是顺序的。

 

事实上,从Topic角度来说,消息仍不是有序的。如果你需要Topic范围内的有序,那么你可以只使用一个Partition,这也就是说,Group中也只有一个Consumer。

 

kafka高吞吐量之细节设计

  

 高效使用磁盘

 
 
 

 

Kafka的整个设计中,Partition类似于一个非常长的数组,而Broker接收到的所有消息都顺序的写入到这个大的数组中。同时Consumer顺序消费这些数据后不会删除已经消费的数据,这样就避免了随机写磁盘的过程。由于磁盘有限,不可能保存所有的数据,需要删除旧的数据。

Kafka的删除的过程,并不是通过使用“读-写”模式去修改文件,而是通过删除整个Segment文件的方式去删除Partition内的数据,这种删除旧数据的方式,也避免了对文件的随机写操作,删除整个物理文件也确保了删除的高效率。

通过如下Scala源代码可知,Kafka删除Segment的方式,是直接删除Segment对应的整个Log文件和整个Index文件而非删除文件中的部分内容。

 

(摘自Kafka源码)

 

充分利用Page Cache

 

使用Page Cache的好处如下

 

I/O Scheduler会将连续的小块写组装成大块的物理写从而提高性能

 

I/O Scheduler会尝试将一些写操作重新按顺序排好,从而减少磁盘头的时间。

 

充分利用所有空闲内存(非JVM内存)。如果使用应用层Cache(即JVM堆内存),会增加GC负担。

 

读操作可直接在Page Cache内进行。如果消费和生产速度相当,甚至不需要通过物理磁盘(直接通过Page Cache)交换数据。

 

如果进程重启,JVM内的Cache会失效,但Page Cache仍然可用。

 

Broker收到数据后,写磁盘时只是将数据写入Page Cache,并不保证数据一定完全写入磁盘。

 

从这一点看,可能会造成当机器Down机时,Page Cache内的数据未全部写入磁盘从而造成数据丢失,但是这种丢失只发生在机器断电等偶然性原因造成操作系统不工作的场景,而这种场景是完全可以由Kafka层面的Replication机制来解决。

 

如果为了保证数据不丢失而强制将Page Cache中的数据强制Flush到磁盘,则会降低性能。

 

如果数据消费速度与生产速度相当,甚至可以不需要通过物理磁盘交换数据,而是直接通过Page Cache交换数据即可完成消息传递,同时,Follower从Leader获取数据时,也可通过Page Cache完成。

 

支持多Disk Drive

 
 
 

Broker的log.dirs配置项,允许配置多个文件夹。如果机器上有多个Disk Drive,可将不同的Disk挂载到不同的目录,然后将这些目录都配置到log.dirs里。Kafka会尽可能将不同的Partition分配到不同的目录,也即不同的Disk上,从而充分利用了多Disk的优势。

 

零拷贝机制

 
 
 

Kafka中存在大量的网络数据持久化到磁盘(Producer到Broker)和磁盘文件通过网络发送(Broker到Consumer)的过程。这一过程的性能直接影响Kafka的整体吞吐量。

 

先来看传统模式下的四次拷贝与四次上下文切换。

 

以将磁盘文件通过网络发送为例。传统模式下,一般使用如下伪代码所示的方法先将文件数据读入内存,然后通过Socket将内存中的数据发送出去。

 

 

从上图可知以上过程一起发生了四次数据拷贝,即将文件数据读入到内核态buffer(DMA拷贝),然后将内存态buffer数据读入到用户态Buffer(CPU拷贝),接着用户程序通过Socket发送数据时将用户态Buffer数据拷贝到内核态Buffer(CPU拷贝),最后将数据拷贝到NIC Buffer(DMA拷贝)。

 

发生四次拷贝的同时也伴随着四次上下文的切换。数据的拷贝,传输和上下文的切换都是耗时操作,可见传统的四次拷贝与四次上下文切换的效率比较低。

 

Kafak通过通过Java NIO(Non-Blocked IO)的FileChannel中的相关方法实现零拷贝和仅仅两次上下文切换。

 

从具体实现来看,Kafka的数据传输通过TransportLayer来完成,其子类PlaintextTransportLayer通过Java NIO的FileChannel的transferTo和transferFrom方法实现零拷贝,如下所示。

 

(摘自java源码)

 

常规文件传输和zeroCopy方式的性能对比:

 

(摘自网络数据)

 

减少网络开销

 

采用批处理方式提高数据传输性能和读写性能。

通过数据压缩降低网络负载,增强传输效率,提升吞吐量。

 

使用高效的序列化方式来减少实际网络传输和磁盘存储的数据规模。

 

Kafka和ActiveMQ对比

 

(摘自网络数据)

 

Kafka实际性能测试结果

 

测试环境:

三台虚拟机

系统: CentOS

CPU:4 core

内存: 4G

硬盘:80GB

测试结果如下图所示:

 

作者  吴军

 

现就职于上海秦苍(买单侠)信息科技有限公司研发一部资金源部,任职架构师,主要从事资金源架构的设计,优化与维护工作,同时负责快速接入外部资金源。在加入秦苍科技之前曾供职于朗讯,泰克,飞牛网,阿里千寻等互联网技术公司,对消息队列,互联网后端框架设计与实现有着丰富的经验,对于各种分布式RPC框架有着很深入的研究,且擅长解决各类线上突发问题。

 

 
 

OmniStack

秦苍Geek的聚集地

长按二维码关注我们

阅读原文

为您推荐

友情链接 |九搜汽车网 |手机ok生活信息网|ok生活信息网|ok微生活
 Powered by www.360SDN.COM   京ICP备11022651号-4 © 2012-2016 版权