一、kafka体系架构
由上图可知,有三台机器搭建的kafka集群,kafka作为一种消息队列,producer以push模式将数据发送到kafka的机器上(每一台kafka机器可以认为是一个kafka broker),同时订阅了kafka broker的consumer,以pull模式对消息进行消费。请注意到一个问题,上图与zookeeper集群有联系的只有kafka集群和consumer,因为在kafka消息队列里面,它并不关心producer是谁,可以是flume,hdfs,这些客户端都不需要进行负载均衡,但是kafka集群和consumer需要,而且zookeeper还有一个作用,就是记录保存consumer消费消息的offset,consumer的checkpoint。
下面重点介绍一下两个概念:topic和partition
topic是一个“虚无”的东西,相对的,partition却是真正的物理存在;一个broker可以存放多个topic,一个topic可以存在多个partition。如下图:
当producer向Broker发送消息时,它通过zookeeper将消息尽可能的均匀push到同一个topic的各个partition里面,如果设置合理,topic的partition可以进行水平扩展。每条信息都会追加到partition的尾部,有趣的是,partition保存的并不是真正的数据,而是数据的偏移量offset,真正的数据则会持久化到磁盘里。那么有人会问,kafka不是号称高吞吐量吗?是用磁盘读写能到达这个要求吗?答案是绝对可以,完全得益于系统的顺序写入磁盘机制。经验证,顺序写磁盘效率比随机写内存还要高,这是Kafka高吞吐率的一个很重要的保证。
二、高可靠性存储
Kafka的高可靠性的保障来源于其健壮的副本(replication)策略。通过调节其副本相关参数,可以使得Kafka在性能和可靠性之间运转的游刃有余。
1、文件存储机制
kafka消息是以topic来分类,即producer发送信息时会指定某一个topic,同时,consumer通过订阅某个topic来消费消息。值得注意的是,这里常说的consumer,很多时候指的是consumer group(一个consumer group可以有一个或多个consumer客户端),一个topic的消息总和 = 一个consumer group消费的消息总和。
如上图,consumer group有两个consumer组成,根据负载均衡,它们有可能就这样对partition进行消费,一个partition只允许同一个consumer group的一个consumer消费。partition其实还可以细分,由多个Segment数据快组成,如图:
在kafka文件存储当中,每个partition会生成一个目录,可以将partition看成是由许多Segment数据块组成的“大文件”,在Segment中由两个文件组成,分别是.index后缀文件和.log后缀文件。这两个文件的命令规则为:partition全局的第一个segment从0开始,后续每个segment文件名为上一个segment文件最后一条消息的offset值,数值大小为64位,20位数字字符长度,没有数字用0填充。
- .index后缀文件:Segment索引文件,存储大量元数据,指向.log后缀文件数据的偏移量;
- .log后缀文件:Segment数据文件,存储了大量这些数据偏移量信息。
上图是其中一个Segment文数据块信息,以其中.index文件的一个元数据[4,476]为例,表示在.log文件的第四个信息,对应在整个partition的则是第(17041+4)个消息,物理地址偏移量为476。我们都知道数组是一种连续存储的数据结构,其读取时间复杂度为O(1),kafka是属于顺序存储的,设置partition,以及偏移量,可以快速的读取想要的数据。Segment还有一个作用,就是当超过设定的时间时,kafka会将最旧的消息删除,是以Segment为删除单元。
2、复制原理与同步方式
对于上层应用来说,可以将partition看成是一个最小单元,每一个partition都是有一些有序的(各自partition内部是有序的)、不可变的数据组成。为了提高数据的可靠性,kafka允许每个partition有N个副本,即只要N>=1,程序还是可以正常使用,即通过partition的副本进行故障转移。在N个副本当中,只有一个是leader,其他都是follower,不管producer向partition写入消息,还是consumer对partition进行消息消费,两者只对leader进行操作,follower只用于备用,以及定期向leader复制数据。
譬如,假设kafka集群有两个broker、某一个topic中有3个partition,每个partition的副本数是2:
如果leader发生故障或者挂掉,那么其对应会在其follower中选举一个作为新的leader,并接受producer的写入和consumer的读取,前提是有follower跟得上leader的数据。所谓的“跟得上”,就是follower的数据在允许滞后的范围之内。正常情况下,producer生产的数据首先达到leader中,follower才会去复制新的数据过来,那么由于网络原因,IO性能等因素,肯定存在滞后问题,但是并不是所有的follower都能跟得上leader,那么leader怎么去管理这些follower呢〉这就出现了一个新的概念:ISR(In-Sync Replicas)——副本同步队列,leader负责维护和跟踪这个队列,如果某些follower“落后”太多或者失效,那么leader就会将这些follower从ISR中剔除。
2.1、ISR
副本数对Kafka的吞吐率是有一定的影响,但极大的增强了可用性。默认情况下Kafka的replica数量为1,即每个partition都有一个唯一的leader,为了确保消息的可靠性,通常会为partition设置多个副本,由这多个副本组成的新概念,称之为Assigned Replicas,即AR。可用副本则为ISR,而被剔除的副本会存到一个叫做OSR(Outof-Sync Replicas)列表中,当然,新加入的也会被加入到OSR中。那么可以理解AR = ISR + OSR。
为了更好理解partition的复制,再来认知一下partition:
这里先介绍下LEO,LogEndOffset的缩写,表示每个partition的log最后一条Message的位置。HW是HighWatermark的缩写,是指consumer能够看到的此partition的位置。上面也介绍过,在ISR里面,包括了leader和可用的follower,LEO记录当前partition的最后一个offeset,HW记录的是ISR中所有partition的最小LEO,即当所有partition的数据一模一样时,HW = LEO。
看图说话:假设当前状态为3个partition都有3个数据,HW = LEO;当Producer向leader发送消息4和5过来了,那么HW=3,LEO = 5;这时,leader有了新的消息,就会将阻塞的follower解锁,通知它们来复制新消息;假如其中一个follower完全跟上leader,还有一个follower只复制了消息4,那么HW = 4, LEO = 5;当所有follower都跟上leader时,HW = LEO,follower又进入阻塞状态,继续等待leader的通知。
由此可见,kafka的复制机制并不是单纯的异步复制,或者同步复制。如果单纯的异步复制,当数据写入到leader时,就已经当作是commit,而follower还没来得复制新数据,leader就宕机了,这就造成数据的丢失;如果使用单纯的同步复制,只有当所有follower都复制完成,数据才算是commit,那么就极大的影响kafka的吞吐量。
3、数据持久化与可靠性
一般会用三种传输机制:
- at-least-once(最常用):至少发送一次,数据不会丢失,但有可能重复消费;
- at-most-once:最多发送一次,不会重复消费,但有可能丢失数据;
- Exactly once:每条消息肯定会被传输一次且仅传输一次
at-least-once:
此时,leader来了两条新消息,leader本地日志写完,返回客户端成功,followers开始同步数据,然而leader发生crash,只有一个follower来得及(或者没有一个来得及)复制完成,因此HW没有更新,返回客户端错误,之后,Producer会重新发送失败的信息,这时新leader接收到的新信息就有可能存在重复,后续follower会继续同步新leader的数据。
at-most-once:
此时,leader来了两条新消息,leader本地日志写完,返回客户端成功,followers开始同步数据,正当followers想要同步leader的数据时,leader宕机了,那么新的follower的HW=LEO,由于at-most-once机制,producer也不会重新发送失败的消息,因此导致数据的丢失。
3.1、进一步探讨HW
对于at-least-once的图,如果新的leader不是follower1,而是followe2,那么复制机制又是怎么样呢?
一个partition中的ISR列表中,leader的HW是所有ISR列表里副本中最小的那个的LEO。如果新的leader真如follower那样,那么HW=4,leader就会通知它的followers超过4的信息都截取掉(即follower1的消息5被截取),然后从HW=4的位置开始同步数据。