当前访客身份:游客 [ 登录  | 注册加入尚学堂]
直播

我来了!

拥有积分:3866
尚学堂雄起!!威武。。。

博客分类

笔记中心

课题中心

提问中心

答题中心

解答题中心

Paper Rush-3:Apache Kafka

我来了! 发表于 2年前 (2014-11-10 16:40:15)  |  评论(0)  |  阅读次数(1614)| 0 人收藏此文章,   我要收藏   

术语(Terminology)

Publisher : 消息的生产者,创建消息数据。

Broker : 实际上就是集群机器中的某一台

Consumer : 消息的消费者,消费消息数据。

Topic : 消息主题,订阅、发布消息内容都通过此指定,一个topic确定了一个消息项。

Partition : 为 了达到更好的分布效果,实现更负载均衡,将topic切分成partition;这样,消息内容就会分散到各parition中;consumer通过读 partition来获取消息;需要说明的是一个partition只能对应一个consumer。每个partition都是存放的数据都是已排好序 的。

Kafka是什么(Concept)

Kafka是Apache Foundation开发出的一个消息中间件产品。消息中间件就是在各种系统间传送消息任务的基础软件产品,例如如果核心系统的一条交易成功了,这个消息 需要通知给很多外围系统来做相应的动作,比如说发淘金币,增加会员经验等等。消息中件间的另两主要功能就是处理消息丢失”消息堆积”,在分布式系统中还需要考虑有消息存储”问题,如果设计得不好,就可能导致低效的问题。

原理(How It Works)

首先让我们来看看部署结构,以获得对此系统的一个整体印象:

图1 一个Kafka实例部署结构图

这里有几个信息:

  1. 一个Topic会被切割成多个Partition,这个数量是一个设定值。当然,后期也是可以随时变更的;
  2. 一个Publisher可以向多个Topic的多个Partion发布数据,这里一般按RR的原则,当然也可以只发到某一个上,为什么要这样做,我们待会儿再解释;
  3. 一个Partition最多被一个Subscriber消费,一个Subscriber可以消费多个Partion,同一个Topic或者不同的Topic;
  4. 一个Broker可以包含多个Partition;
  5. Subscriber采取的是主动拉机制而Publisher采取的是主动推送机制(为什么Subscriber要使用拉机制以及有什么好处,下面我们再说);
  6. Kafka使用ZooKeeper来管理协调、管理与配置同步。

每个Partition都可以理解成一个队列,队列内部是FIFO的顺序,也就是说对于同一个Partition Kafka是能够保证消息有序的,这也是为什么有时候Publisher需要指定Partition进行发布,这样就可以实现顺序消息。

细节(Implements)

数据结构


图2 Kafka的核心数据存储结构

Kafka的核心数据结构就是顺序log文件,每次写都是在其末尾进行 Append,所有有O(1)这样优秀的性能。比较特殊地,每个Record没有自己的编号,而是使用了offset来做为自己的ID,寻找下一条消息只 需要将自己的ID加上自己消息的长度就可以了,非常方便的。内存中存有一个总的Index文件,每个entry指向对应区间的log文件。以下为一条消息 的头字段:

message length : 4 bytes (value: 1+4+n)

“magic” value : 1 byte

crc : 4 bytes

playload : n bytes

其中的playload即是其ID。当然为了提高写性能,Kafka并不是每次都回写,在默认的配置里,只有当累积到一定时间或者累积到一定大小后才会写回磁盘,不过这样设计的后果是,如果这个时候Kafka出现故障,未落盘的数据都将会丢失。

数据存储

这里Kafka使用了文件系统。为了提高效率,除了上述的顺序存储结构 外,Kafka还充分利用了操作系统的Page Cache来解决热点问题。这样的好处是Kafka在程序内部根本就不需要关心缓存这个东西了,二来如果在JVM里做缓存,会增加GC的压加,或者或少都 会影响系统的性能,所以这种存储方式不仅降底了程序的复杂性,同时还提高了性能,可谓一举两得。为了进一步提高性能,Kafka还使用了批量提交,即每次 在对page cache进行修改后并不是立刻写回磁盘,而在等至超过配置好的时间或者大小时才会提交,这样便可以变大量离散操作为线性操作。可不要小看了这点,虽然磁盘在随机读在比不上Flash,但顺序写在可一点儿也不逊色,如下图所示:


图3 磁盘顺序读写性能比较 (来源:http://deliveryimages.acm.org/10.1145/1570000/1563874/jacobs3.jpg

虽然这个阈值在linux内核参数中也可以配置,但Kafka还是选择了在应用层来进行控制,这样更加利于操作,不会因不同机器环境而造成配置问题。

那么pache cache倒底是如何工作以至于Kakfa根本就不再需要关心缓存的呢?下面我们来看一看,如下图所示:


图4 Page Cache工作原理

Page Cache实质上就是对磁盘分布的一级内存映射,但一个分页被使用的频率较高时,它就会被调入内存,从而大大提高读取速度。当然,Linux还提供另一种名为Read-Ahead的 机制,在这种机制下,操作系统会自适应地在某块page被读取时将可能相关的page也加载进来。有了 Page Cache后每次写都是写就不直接写磁盘了(否则效率太低),变更后的Cache会被标记成Dirty,何时写将Dirty的Page写回磁盘这里又有很 多策略,具体可以参见这里

负载均衡/消息分发

Kafka分布式的核心就是Partition,Publisher按Round- Robin或者散列算法来将消息发送到对应的Partition,这里的散列算法可以用户自己制定的(例如按userID)。需要注意的是Kafka只保 证消息在同一个Partition里消费是有序的,不同的Partition无法保证顺序,所以如果你需要保证顺序,便需要将有序的数据都散列到同一个 Partition中去。Kafka所有的对应关系(如客户端与Partition的对应关系)都是通过ZooKeeper进行存储配置同步的。这里有个 问题,就是对于一个Topic,它对应的Partition应该是多少呢?我们知道,一个Partition最多只能被一个Client消费,如果太少, 那么就会有Client没有数据可用,如果太多呢,又会造成消息得不到消费。Kafka是这样做的:

  • 对于某个Topic的所有Partition数量,记其为
  • 该Topic的所有订阅者数量为
  • 则将分配给

也就是在这种算法中,一个Client可能会对应多个Partition,一个Partition仍然还是只对应一个客户端的。每次发布 Partition的增加或者Client的加入/离开都会触发上面的操作,这个操作被称为"Rebalance"。某个Topic的Partition 数量也是可以动态调整的。

现在我们来说说Kafka作为消息中间件所能完成的基本职责。一般来说,对于一个消息中间件,它有以下几种消息分发策略:

  1. 消息可能丢失,但不会多次投递(At most once
  2. 消息不可能丢失,但可能多次投递(At least once
  3. 每个消息只会被准确地投递一次(Exactly once)

很显然第三种情况是属于理想状态,Kafka实现的就是第二种, 其依靠的就是将每个Subscriber的消费记录存在Client而不是一般的Server中。每个Client在消费了以后才会把消息游标增加,如果 这时候不巧Client故障重启了,最多也就是将已经消费过的消息再消费一次。消费的游标保存在Client之后,Server主不必再为每个 Client维护一个消费状态了,这大大减化了Server的复杂程序;另外,Client还可能“时光倒流”去消费那些已经消费过的消息,只需要自己将 游标往回调一些便可以了。需要额外说明的是Kafka默认保证"at-least-once-delivery",但也可以通过禁用重试来实现"at-most-once-delivery"

对于消息的发送与订阅,通常也有两种方式,即服务端主动推送与客户端自主拉取。他们各有优劣的地方,推送的优化在于实时性高,缺点是流量难以控制,很容易把客户端弄成overflow,而自拉没有这个问题;自拉实时性低,但控制简单方便。还有一种发布者自拉模式,但太多写磁盘的动作会使系统变得相当复杂,难以维护。Kafka的发布端采取的是推送,而订阅端则采取自拉,同时为了避免空轮询,采取 long polling 机制。

传输性能

为了提高通信的性能,Kafka做了很多细节上的优化。

首先是Direct to Socket。通常一块数据要被发送到网络,需要以下几步:

  1. 将数据从磁盘拷贝到磁盘分页,这里是操作系统层面;
  2. 将数据从磁盘分页拷贝到用户进程数据区;
  3. 再将数据由用户进程数据区拷贝到Socket缓冲区等待发送。

由此可见这里绕了一个大圈,自Linux Kernel 2.2 起,操作系统提供了一个新的API:sendfile,这个API可以将数据直接从操作系统缓存发送到Socket缓冲区,不必再经过用户进程空间,减少了内存拷贝次数,由于这个过程进程不存在操作级别(原逻辑需要进程从用户态切换到核心态)的切换,从而可以大大提升传输效率。这种操作也被称作 Zero-Copy

图5 Zero-Copy技术

然后是通信数据格式标优化。Kafka对消息的头字段进行了缩减,使其只有10个左右的字节,相比JMX头已经小了非常多(JMX头光字段就有10上以上)。其次,Kafka支持消息嵌套,压缩返回批量数据客户端支持缓冲,批量发送。以上优化使得Kafka在消息传输性能上出类拔萃。


图6 Kafka对比其它MQ的传输性能(来源:http://www.dwz.cn/oSj1m


容灾

客户端使用消息下标记录已消费的消息情况,用于故障恢复。换句话说,客户端每消费一 个便记录下最后消息的消息ID,如果客户端故障重启了,只需要从这上次中断的地址恢复继续消费即可。当然这也存在一个问题,如果客户端故障的时候确实消费 了但没来得及记录,这个时候就会造成重复消费了,所以Kafka实现的是“At least once”。对Kafka而言,只要一个Broker写入成功,该消息都不会丢失,如果丢包,发布者按照指数延时重新发布。

由于Kafka使用的是ZooKeeper来管理、探测集群机器,所以当某个 Broker宕机后,ZooKeeper就能通过心跳快速发现故障机器,从而从更新配置并进行通知,Kafka的Master便能重新重新计算 “Partition"与“Consumer"之间的映射关系,将其重新指派到正常的机器,待故障机重启恢复后,便能恢复成原来的样子。

当然如果某个partition的磁盘如果故障了的话,这个partition的数据 便全部丢失了,如果确实对可靠性有非常严格的要求,那么可以对磁盘进行双写操作。双写操作是这么一种操作:一次写会写到两个地方,一般是异地机房,这样即 便是一个机房遭遇不测(如海啸、地震什么的),数据也依然安然无恙。当然省点事儿我们也可以使用RAID5之类磁盘容灾技术。

对比优势(Compared With Other MQs)

读写性能

Kafka拥有比RabbitMQ,ActiveMQ高得多的读写性能很大程度上是因为上述一系列的优化措施,包括:


  • Zero Copy,简化消息头
  • 将消费下标交由客户端处理
  • 直接依赖操作系统的Page Cache避免GC(Garbage Collect)
  • 采用顺序文件存储

从上面的图6中我们就可以看出,从实验的结果来看,Kafka的优势是相当的明显的。具体关于Kafka的性能,我们还可以参考官方的这篇文档

功能差异

RabbitMQ: 存在一个中间结点,设计实现较为复杂且不支持事务,相对于Kafka特点之一为服务端维护消费 状态,但支持多种路由方式(Advanced Message Queuing Protcol中的exchang、binding、queueing等)。不过也正是因为其中心结点的存在,使得其扩展较为不易,性能当然也不及 Kafka。

ZeroMQ:非常轻量级,也拥有非常高的吞吐量,也不支持事务。除了可以配置成与Kafka一样的Broker模式之外,还支持P2P的模式,但相比于Kafka来说,学习成本过高,看其长达80页的User Guide就知道了。

ActiveMQ:与ZeroMQ一样可以配置成Broker与P2P两种模式,另外其还支持Persistent与Non-Persistent两种投递模式,可以较简单地适配多种场景,不过最大的缺点就是性能太低,但支持事务是其一大亮点。


另外这里还有前人总结的各类MQ之间的比较文档(中文),比较细致,可以参看。


参考资料(References)





----------------------------------------------------草稿----------------------------------------------------------

Consumer rebalancing is triggered on each addition or removal of both broker nodes and other consumers within the same group.


The log provides a configuration parameter M which controls the maximum number of messages that are written before forcing a flush to disk. 


To avoid locking reads while still allowing deletes that modify the segment list we use a copy-on-write style segment list implementation that provides consistent views to allow a binary search to proceed on an immutable static snapshot view of the log segments while deletes are progressing.


The search is done as a simple binary search variation against an in-memory range maintained for each file.


Kafka handles this differently. Our topic is divided into a set of totally ordered partitions, each of which is consumed by one consumer at any given time.


What is perhaps not obvious, is that getting the broker and consumer to come into agreement about what has been consumed is not a trivial problem

Most messaging systems keep metadata about what messages have been consumed on the broker


data is pushed to the broker from the producer and pulled from the broker by the consumer


The Kafka consumer works by issuing "fetch" requests to the brokers leading the partitions it wants to consume. The consumer specifies its offset in the log with each request and receives back a chunk of log beginning from that position.


batching can be configured to accumulate no more than a fixed number of messages and to wait no longer than some fixed latency bound (say 100 messages or 5 seconds). 


The client controls which partition it publishes messages to.

同一个group的consumer数量不能高于partition数量

一个partition一次最多只能被一个consumer消费,这样做可以简化设计

分享到:0
关注微信,跟着我们扩展技术视野。每天推送IT新技术文章,每周聚焦一门新技术。微信二维码如下:
微信公众账号:尚学堂(微信号:bjsxt-java)
北京总部地址:北京市海淀区西三旗桥东建材城西路85号神州科技园B座三层尚学堂 咨询电话:400-009-1906 010-56233821
Copyright 2007-2015 北京尚学堂科技有限公司 京ICP备13018289号-1 京公网安备11010802015183