Kafka 简介

来源:互联网 发布:上虞宇石网络 编辑:程序博客网 时间:2024/04/26 09:11

Kafka

1 简介

Kafka是一个分布式发布-订阅publish-subscribe消息传递系统,设计目标是快速、可伸缩和耐用冗余。它是一个分布式的,可划分的,冗余备份的持久性的日志服务。它主要用于处理活跃的流式数据。Kafka可以起到两个作用:

  1.降低系统组网复杂度。

  2.降低编程复杂度,各个子系统不在是相互协商接口,各个子系统类似插口插在插座上,Kafka承担高速数据总线的作用。

2 Kafka架构

Kafka的整体架构非常简单,是显式分布式架构,producer、broker(kafka)和consumer都可以有多个。Producer,consumer实现Kafka注册的接口,数据从producer发送到broker,broker承担一个中间缓存和分发的作用。broker分发注册到系统中的consumer。broker的作用类似于缓存,即活跃的数据和离线处理系统之间的缓存。客户端和服务器端的通信,是基于简单,高性能,且与编程语言无关的TCP协议。Kafka提供了Java客户端,并且对多种语言都提供了支持。

3 消息发送流程

1)  Topic:特指Kafka处理的消息源(feeds of messages)的不同分类。

2)  Partition:Topic物理上的分组,一个topic可以分为多个partition,每个partition是一个有序的队列。partition中的每条消息都会被分配一个有序的id(offset)。

3)  Message:消息,是通信的基本单位,每个producer可以向一个topic(主题)发布一些消息。

4)  Producers:消息和数据生产者,向Kafka的一个topic发布消息的过程叫做producers。

5)  Consumers:消息和数据消费者,订阅topics并处理其发布的消息的过程叫做consumers。

6)  Broker:缓存代理,Kafa集群中的一台或多台服务器统称为broker。

过程:

(1)   Producer根据指定的partition方法(round-robin、hash等),将消息发布到指定topic的partition里面。

(2)   kafka集群接收到Producer发过来的消息后,将其持久化到硬盘,并保留消息指定时长(可配置),而不关注消息是否被消费。

(3)   Consumer从kafka集群pull数据,并控制获取消息的offset。

 

4 性能

Kafka在提高效率方面做了很大努力。Kafka的一个主要使用场景是处理网站活动日志,吞吐量是非常大的,每个页面都会产生好多次写操作。读方面,假设每个消息只被消费一次,读的量的也是很大的,Kafka也尽量使读的操作更轻量化。

线性读写的情况下影响磁盘性能问题大约有两个方面:太多的琐碎的I/O操作和太多的字节拷贝。I/O问题发生在客户端和服务端之间,也发生在服务端内部的持久化的操作中。

 

消息集(message set)

为了避免这些问题,Kafka建立了“消息集(message set)”的概念,将消息组织到一起,作为处理的单位。以消息集为单位处理消息,比以单个的消息为单位处理,会提升不少性能。Producer把消息集一块发送给服务端,而不是一条条的发送;服务端把消息集一次性的追加到日志文件中,这样减少了琐碎的I/O操作。consumer也可以一次性的请求一个消息集。

另外一个性能优化是在字节拷贝方面。在低负载的情况下这不是问题,但是在高负载的情况下它的影响还是很大的。为了避免这个问题,Kafka使用了标准的二进制消息格式,这个格式可以在producer,broker和producer之间共享而无需做任何改动。

 

zerocopy

Broker维护的消息日志仅仅是一些目录文件,消息集以固定队的格式写入到日志文件中,这个格式producer和consumer是共享的,这使得Kafka可以一个很重要的点进行优化:消息在网络上的传递。现代的unix操作系统提供了高性能的将数据从页面缓存发送到socket的系统函数,在linux中,这个函数是sendfile.

为了更好的理解sendfile的好处,我们先来看下一般将数据从文件发送到socket的数据流向:

(1)操作系统把数据从文件拷贝内核中的页缓存中

(2)应用程序从页缓存从把数据拷贝自己的内存缓存中

(3)应用程序将数据写入到内核中socket缓存中

(4)操作系统把数据从socket缓存中拷贝到网卡接口缓存,从这里发送到网络上。

这显然是低效率的,有4次拷贝和2次系统调用。Sendfile通过直接将数据从页面缓存发送网卡接口缓存,避免了重复拷贝,大大的优化了性能。

在一个多consumers的场景里,数据仅仅被拷贝到页面缓存一次而不是每次消费消息的时候都重复的进行拷贝。这使得消息以近乎网络带宽的速率发送出去。这样在磁盘层面你几乎看不到任何的读操作,因为数据都是从页面缓存中直接发送到网络上去了。

 

数据压缩

很多时候,性能的瓶颈并非CPU或者硬盘而是网络带宽,对于需要在数据中心之间传送大量数据的应用更是如此。当然用户可以在没有Kafka支持的情况下各自压缩自己的消息,但是这将导致较低的压缩率,因为相比于将消息单独压缩,将大量文件压缩在一起才能起到最好的压缩效果。

Kafka采用了端到端的压缩:因为有“消息集”的概念,客户端的消息可以一起被压缩后送到服务端,并以压缩后的格式写入日志文件,以压缩的格式发送到consumer,消息从producer发出到consumer拿到都被是压缩的,只有在consumer使用的时候才被解压缩,所以叫做“端到端的压缩”。

Kafka支持GZIP和Snappy压缩协议。

吞吐量

高吞吐是kafka需要实现的核心目标之一,为此kafka做了以下一些设计:

1)             数据磁盘持久化:消息不在内存中cache,直接写入到磁盘,充分利用磁盘的顺序读写性能

2)             zero-copy:减少IO操作步骤

3)             数据批量发送

4)             数据压缩

5)             Topic划分为多个partition,提高parallelism

负载均衡

1)     producer根据用户指定的算法,将消息发送到指定的partition

2)     存在多个partiiton,每个partition有自己的replica,每个replica分布在不同的Broker节点上

3)     多个partition需要选取出lead partition,lead partition负责读写,并由zookeeper负责fail over

4)     通过zookeeper管理broker与consumer的动态加入与离开

拉取系统

由于kafka broker会持久化数据,broker没有内存压力,因此,consumer非常适合采取pull的方式消费数据,具有以下几点好处:

1)     简化kafka设计

2)     consumer根据消费能力自主控制消息拉取速度

3)     consumer根据自身情况自主选择消费模式,例如批量,重复消费,从尾端开始消费等

可扩展性

当需要增加broker结点时,新增的broker会向zookeeper注册,而producer及consumer会根据注册在zookeeper上的watcher感知这些变化,并及时作出调整。

 

 5.Kafka的Log存储解析

Kafka中的Message是以topic为基本单位组织的,不同的topic之间是相互独立的。每个topic又可以分成几个不同的partition(每个topic有几个partition是在创建topic时指定的),每个partition存储一部分Message。借用官方的一张图,可以直观地看到topicpartition的关系。


partition是以文件的形式存储在文件系统中,比如,创建了一个名为page_visitstopic,其有5partition,那么在Kafka的数据目录中(由配置文件中的log.dirs指定的)中就有这样5个目录: page_visits-0page_visits-1page_visits-2page_visits-3page_visits-4,其命名规则为<topic_name>-<partition_id>,里面存储的分别就是这5partition的数据。

接下来,本文将分析partition目录中的文件的存储格式和相关的代码所在的位置。

Partition的数据文件

Partition中的每条Messageoffset来表示它在这个partition中的偏移量,这个offset不是该Messagepartition数据文件中的实际存储位置,而是逻辑上一个值,它唯一确定了partition中的一条Message。因此,可以认为offsetpartitionMessageidpartition中的每条Message包含了以下三个属性:

·        offset

·        MessageSize

·        data

其中offsetlong型,MessageSizeint32,表示data有多大,datamessage的具体内容。它的格式和Kafka通讯协议中介绍的MessageSet格式是一致。

Partition的数据文件则包含了若干条上述格式的Message,按offset由小到大排列在一起。它的实现类为FileMessageSet,类图如下:

它的主要方法如下:

·        append:把给定的ByteBufferMessageSet中的Message写入到这个数据文件中。

·        searchFor:从指定的startingPosition开始搜索找到第一个Messageoffset是大于或者等于指定的offset,并返回其在文件中的位置Position。它的实现方式是从startingPosition开始读取12个字节,分别是当前MessageSetoffsetsize。如果当前offset小于指定的offset,那么将position向后移动LogOverHead+MessageSize(其中LogOverHeadoffset+messagesize,为12个字节)。

·        read:准确名字应该是slice,它截取其中一部分返回一个新的FileMessageSet。它不保证截取的位置数据的完整性。

·        sizeInBytes:表示这个FileMessageSet占有了多少字节的空间。

·        truncateTo:把这个文件截断,这个方法不保证截断位置的Message的完整性。

·        readInto:从指定的相对位置开始把文件的内容读取到对应的ByteBuffer中。

我们来思考一下,如果一个partition只有一个数据文件会怎么样?

1.    新数据是添加在文件末尾(调用FileMessageSetappend方法),不论文件数据文件有多大,这个操作永远都是O(1)的。

2.    查找某个offsetMessage(调用FileMessageSetsearchFor方法)是顺序查找的。因此,如果数据文件很大的话,查找的效率就低。

Kafka是如何解决查找效率的的问题呢?有两大法宝:1)分段 2) 索引。

数据文件的分段

Kafka解决查询效率的手段之一是将数据文件分段,比如有100Message,它们的offset是从099。假设将数据文件分成5段,第一段为0-19,第二段为20-39,以此类推,每段放在一个单独的数据文件里面,数据文件以该段中最小的offset命名。这样在查找指定offsetMessage的时候,用二分查找就可以定位到该Message在哪个段中。

为数据文件建索引

数据文件分段使得可以在一个较小的数据文件中查找对应offsetMessage了,但是这依然需要顺序扫描才能找到对应offsetMessage。为了进一步提高查找的效率,Kafka为每个分段后的数据文件建立了索引文件,文件名与数据文件的名字是一样的,只是文件扩展名为.index
索引文件中包含若干个索引条目,每个条目表示数据文件中一条Message的索引。索引包含两个部分(均为4个字节的数字),分别为相对offsetposition

·        相对offset:因为数据文件分段以后,每个数据文件的起始offset不为0,相对offset表示这条Message相对于其所属数据文件中最小的offset的大小。举例,分段后的一个数据文件的offset是从20开始,那么offset25Messageindex文件中的相对offset就是25-20 = 5。存储相对offset可以减小索引文件占用的空间。

·        position,表示该条Message在数据文件中的绝对位置。只要打开文件并移动文件指针到这个position就可以读取对应的Message了。

index文件中并没有为数据文件中的每条Message建立索引,而是采用了稀疏存储的方式,每隔一定字节的数据建立一条索引。这样避免了索引文件占用过多的空间,从而可以将索引文件保留在内存中。但缺点是没有建立索引的Message也不能一次定位到其在数据文件的位置,从而需要做一次顺序扫描,但是这次顺序扫描的范围就很小了。

Kafka中,索引文件的实现类为OffsetIndex,它的类图如下:


主要的方法有:

·        append方法,添加一对offsetpositionindex文件中,这里的offset将会被转成相对的offset

·        lookup,用二分查找的方式去查找小于或等于给定offset的最大的那个offset

小结

我们以几张图来总结一下Message是如何在Kafka中存储的,以及如何查找指定offsetMessage的。

Message是按照topic来组织,每个topic可以分成多个的partition,比如:有5partition的名为为page_visitstopic的目录结构为:


partition是分段的,每个段叫LogSegment,包括了一个数据文件和一个索引文件,下图是某个partition目录下的文件:



可以看到,这个partition4LogSegment

借用博主@lizhitao博客上的一张图来展示是如何查找Message的。



比如:要查找绝对offset7Message

1.    首先是用二分查找确定它是在哪个LogSegment中,自然是在第一个Segment中。

2.    打开这个Segmentindex文件,也是用二分查找找到offset小于或者等于指定offset的索引条目中最大的那个offset。自然offset6的那个索引是我们要找的,通过索引文件我们知道offset6Message在数据文件中的位置为9807

3.    打开数据文件,从位置为9807的那个地方开始顺序扫描直到找到offset7的那条Message

这套机制是建立在offset是有序的。索引文件被映射到内存中,所以查找的速度还是很快的。

一句话,KafkaMessage存储采用了分区(partition),分段(LogSegment)和稀疏索引这几个手段来达到了高效性。

0 0
原创粉丝点击