1.Kafka架构演进

通过kafka架构演变过程可以更好的理解Kafka架构。

第一阶段:原始状态,一个生产者多个消费者

kafka单分区多消费者 kafka多个分区一个消费者_zookeeper

第二阶段:分区设计。Kafka的分区也类似Spark的分区,是针对一个topic的分区,当生产速度远大于消费速度时,topic越来越大,这时候可以进行分区,每个分区保存topic一部分数据。分区可以提高吞吐量。

分区后,Producer将数据发往不同分区,每个分区数据不同。

kafka单分区多消费者 kafka多个分区一个消费者_kafka_02

 

第三阶段:消费者组。当分区后,consumer也发生变化,单一消费者变成消费者组,组中包括多个消费者,每个消费者对应一个分区或多个分区,从而并行消费一个topic,各个组之间互不影响。(会出现分区数和消费者数不一致的情况,使用时要保证消费者≤分区个数,因为多了会闲置。

kafka单分区多消费者 kafka多个分区一个消费者_zookeeper_03

第四阶段:分区分布式部署,broker是kafka的物理节点,在broker充足的情况下,每个分区占用一个broker。

kafka单分区多消费者 kafka多个分区一个消费者_kafka单分区多消费者_04

第五阶段:高可用设计。如果一个borker挂掉,其中一个分区的数据就会丢失,所以会做HA,每个分区都有一个副本,处于工作状态的分区为leader,处于备份状态分区为follower,follower从leader同步数据。此时就需要Zookeeper来实现leader选举。

kafka单分区多消费者 kafka多个分区一个消费者_kafka单分区多消费者_05

创建topic时可以指定分区数和副本数。

2.Kafka架构深入

2.1整体工作流程

kafka单分区多消费者 kafka多个分区一个消费者_kafka单分区多消费者_06

Producer按照策略发送消息到不同分区,消费者群组各消费者从不同分区拉取消息,每次拉取都会记录当前的offset,当消费者挂掉重启后,按offset继续消费即可。

Offset类似一个书签,看书时能知道上次看到哪一页。Offset在0.8以前版本存在zookeeper,0.9之后存到kafka内部的一个topic中,topic名为__consumer_offsets_,所有在0.8之前消费kafka时要指定zookeeper集群。

2.2kafka文件存储机制

每个partition对应于一个log文件,该log文件中存储的就是producer生产的数据,示意图如下:

kafka单分区多消费者 kafka多个分区一个消费者_zookeeper_07

如果采用一个大log文件来保存数据,其坏处在于:7天有效期,清除之前数据较难,并且offset寻址较慢。

为解决这个问题,Kafka采取了分片索引机制,将每个partition分为多个segment。每个segment对应两个文件——“.index”文件和“.log”文件。

kafka单分区多消费者 kafka多个分区一个消费者_kafka单分区多消费者_08

其中segment是一个逻辑概念,底层是log文件和index文件,其中log文件保存数据,index文件保存索引信息,index文件中的元数据指向对应数据文件中message的物理偏移地址。

以下举例如何查找数据。

kafka单分区多消费者 kafka多个分区一个消费者_zookeeper_09

①根据文件名确定offset=3的消息在哪个segment对应的index文件,index和log的命名是存储的第1条消息的offset;

②在index文件中找出offset=3的消息对应的物理偏移量;

③在log文件中根据偏移量找到消息的数据。

2.3生产者

2.3.1分区策略

虽然在命令行有专门的创建topic命令,在API中却是由生产者来创建topic的,所以分区放在生产者中介绍。

  • 分区的原因

①方便在集群中扩展,分区后就可以保存在不同的物理服务器上;

②提高并发度;

  • 分区的原则

在API中,将producer发送的数据封装成一个ProducerRecord对象。

kafka单分区多消费者 kafka多个分区一个消费者_kafka单分区多消费者_10

①指明 partition 的情况下,直接将指明的值直接作为 partiton 值;

②没有指明 partition 值但有 key 的情况下,将 key 的 hash 值与 topic 的 partition 数进行取余得到 partition 值;

③既没有 partition 值又没有 key 值的情况下,第一次调用时随机生成一个整数(后面每次调用在这个整数上自增),将这个值与 topic 可用的 partition 总数取余得到 partition 值,也就是常说的 round-robin(轮询)算法。

2.3.2数据可靠性保证

数据可靠性主要保证数据不丢失。

Kafka通过ack机制保证数据的可靠性。kafka提供三种可靠性级别,用户可根据可靠性和延迟权衡进行选择。

acks的参数配置:

0:producer不等待broker的ack,这一操作提供了一个最低的延迟,broker一接收到还没有写入磁盘就已经返回,当broker故障时有可能丢失数据

1:producer等待broker的ack,partition的leader落盘成功后返回ack,如果在follower同步成功之前leader故障,那么将会丢失数据

kafka单分区多消费者 kafka多个分区一个消费者_kafka_11

-1(all):producer等待broker的ack,partition的leader和follower全部落盘成功后才返回ack。但是如果在follower同步完成后,broker发送ack之前,leader发生故障,那么会造成数据重复。(leader没来得及返回ack就挂了)

kafka单分区多消费者 kafka多个分区一个消费者_数据_12

2.3.3分区间数据的一致性

先介绍Log文件中两个概念:

LEO(Log End Offset):每个副本最后一个offset;

HW(High Watermart):所有副本中最下小的LEO,类似木桶原理中最短的那块木板。

kafka单分区多消费者 kafka多个分区一个消费者_kafka_13

follower故障时,会被踢出ISR,待恢复后,follower读取上次记录的HW,将log中高于HW的部分删除,然后从leader同步,等follower的LEO大于等于该Partition新的HW,重新加入ISR;

leader故障时,会从ISR中选出新的leader,为保证副本间数据一致,其余follower将各自log文件高于HW的部分删除,然后从新leader同步数据。

如果新leader不是最长的,则会有数据丢失,所以只能保证副本数据一致性,不能保证数据不丢失或重复。

2.3.4Exactly once

Exactly once是保证每条消息被发送且仅发送一次。

要实现Exactly once,需保证数据不丢失,并且去重重复数据。

kafka通过幂等性配合acks=-1,来实现Exactly once。

acks=-1时,可以保证数据不丢失;

kafka引入幂等性机制实现数据去重,其使用唯一ID来实现去重,ID=PID+MID,producer id和message id。

使用方法:只需将enable.idempotence属性设置为true,kafka自动将acks属性设为-1。

2.4消费者

2.4.1消费方式

消息消费有两种方式:推和拉。

推的方式容易造成consumer来不及处理消息。

拉的方式可以根据消费者的消费能力进行消费,适应不同的消费者。缺点是如果kafka没有数据,消费者会陷入循环,一致返回空数据。

kafka采用的是拉pull的方式。

2.4.2分区分配策略

一个consumer group中有多个consumer,一个 topic有多个partition,所以必然会涉及到partition的分配问题,即确定那个partition由哪个consumer来消费。

Kafka有两种分配策略,一是roundrobin(轮询,默认方式),一是range。

  • 轮询方式:分区从0到6轮流分配给各个消费者

kafka单分区多消费者 kafka多个分区一个消费者_kafka单分区多消费者_14

  • range方式:先把分区按消费者个数平分,然后分配给各个消费者

kafka单分区多消费者 kafka多个分区一个消费者_zookeeper_15

 

2.4.3offset的维护

由于consumer在消费过程中可能会出现断电宕机等故障,consumer恢复后,需要从故障前的位置的继续消费,所以consumer需要实时记录自己消费到了哪个offset,以便故障恢复后继续消费。

Kafka 0.9版本之前,consumer默认将offset保存在Zookeeper中,从0.9版本开始,consumer默认将offset保存在Kafka一个内置的topic中,该topic为__consumer_offsets

相应的,kafka中有个auto.offset.reset的参数。(这个参数应该只在offset选择auto时有效)

auto.offset.reset 的值为smallest,和,largest.(offest保存在zk中)

auto.offset.reset 的值更改为:earliest,latest,和none (offest保存在kafka的一个特殊的topic名为:__consumer_offsets里面)

2.5特性实现

2.5.1Kafka高吞吐量

kafka通过顺序写磁盘和零拷贝技术,实现kafka高吞吐量。

  • 顺序写磁盘

Kafka的producer生产数据,要写入到log文件中,写的过程是一直追加到文件末端,为顺序写。

同样的磁盘,顺序写能到到600M/s,而随机写只有100k/s,因为省去了大量磁头寻址的时间。

  • 零拷贝技术

传统的发送文件的流程:

kafka单分区多消费者 kafka多个分区一个消费者_kafka_16

其中user space为用户层,kernel space为内核层,NIC为网卡。

kafka的方式如下:

kafka单分区多消费者 kafka多个分区一个消费者_数据_17

写入log中的数据是未解码的,所以不需要用户层再进行处理,越过用户层直接通过网卡NIC往外传输,这就是零拷贝。

2.5.2Zookeeper在Kafka作用

kafka单分区多消费者 kafka多个分区一个消费者_kafka_18

所有broker启动时都会创建一个KafkaController,哪个先注册到Zookeeper,哪个KafkaController就会保留。

topic的partitons leader选举过程如下:

①broker0-2启动,在Zookeeper上注册节点/brokers/ids;

②KakfaController监听broker节点变化;

③此时如果创建topic,会在Zookeeper上创建topic的节点信息,first是topic名,leader位于broker0,isr为【012】;

④如果broker0挂掉,kafkaController会监听到broker0挂掉,并且可以从zookeeper上获取哪些topic的leader在broker0上;

⑤KafkaController选举broker1为first的新leader;

⑥kafkaController更新zookeeper上保存的leader及isr。

这样,就完成了leader的重新选举。