Fork me on GitHub

kafka学习笔记

kafka学习笔记

参考文档

Kafka背景及架构介绍

Kafka背景

对网站使用情况做报表,如活动数据(page view、查看内容、搜索内容等)和运营数据(CPU、IO使用率、请求时间、服务器日志等)要用到的数据的收集和分析。

Kafka简介

Kafka是一个分布式的,基于发布/订阅的消息系统:

消息处理时间不受数据量的影响,即使TB级别的消息也可以保证访问的性能。
高吞吐量
支持Kafka Server间的消息分区,及分布式消费,同时保证每个Partition内的消息顺序传输
支持离线数据处理和在线实时数据处理
支持水平扩展

Kafka与常用消息中间件的区别

Kafka架构

Broker: 一个Kafka集群包含一个或多个borker
topic: 一个broker下包含多个topic
partition: 一个topic包含一个或多个partition
productor: 生产者
consumer : 消费者
Consumer Group: 每个Consumer属于一个特定的Consumer Group(可为每个Consumer指定group name,若不指定group name则属于默认的group)

Kafka消息投递的方式

productor: push
consumer: pull(消费者可以根据自身的能力来进行消费)

消息投递保障

Kafka默认保证At least once,并且允许通过设置Producer异步提交来实现At most once。而Exactly once要求与外部存储系统协作,幸运的是Kafka提供的offset可以非常直接非常容易得使用这种方式。

At most once 消息可能会丢,但绝不会重复传输
At least one 消息绝不会丢,但可能会重复传输 (Kafka默认支持该种方式
Exactly once 每条消息肯定会被传输一次且仅传输一次,很多时候这是用户所想要的。

productor 消息投递保障

默认情况下一条消息从Producer到broker是确保了At least once,可通过设置Producer异步发送实现At most once

也就是说默认情况小Kafka消息是会重复投递的。

consumer 消息投递保障

zookeeper中保存了consumer 消费的offset;consumer offset commit的方式有:

获取消息后马上commit:有可能消息得不到处理。At most once
获取消息后先处理再commit:消息至少被处理一遍。At least one
Exactly once:在At least one的基础上,从架构设计上(与外部系统结合)保证不重复处理。

Kafka的高可用架构

Data Replication
Leader Election: 针对partition而言的

为何需要Data Replication

没有Replication的,一旦某一个Broker宕机,则其上所有的Partition数据都不可被消费,这与Kafka数据持久性及Delivery Guarantee的设计目标相悖。同时Producer都不能再将数据存于这些Partition中。

如果Producer使用同步模式:尝试重新发送message.send.max.retries(默认值为3)次后抛出Exception

  • 停止发送后续数据:造成数据的阻塞
  • 继续发送后续数据:造成本应发往该Broker的数据的丢失

如果Producer使用异步模式:尝试重新发送message.send.max.retries(默认值为3)次后

  • 记录该异常并继续发送后续数据,这会造成数据丢失并且用户只能通过日志发现该问题。

为何需要Leader Election

指Replica之间的Leader Election

保证同一个Partition的多个Replica之间的数据一致性(其中一个宕机后其它Replica必须要能继续服务并且即不能造成数据重复也不能造成数据丢失)。

Kafka HA设计解析

如何将所有Replica均匀分布到整个集群

  • 将所有Broker(假设共n个Broker)和待分配的Partition排序
  • 将第i个Partition分配到第(i mod n)个Broker上
  • 将第i个Partition的第j个Replica分配到第((i + j) mod n)个Broker上

Data Replication

消息备份

Title:消息备份流程
producter->partition: (leader,负责读写数据)
partition->follower: ACK(先应答[内存中]再写入log)
follower->follower内存
follower内存->ACK应答
ACK应答->follower
follower->log
follower->partition: ISR列表中的所有follower都应答作为partition应答
partition->producter: 所有的ISR replica都应答后,leader partition应答

ACK前需要保证有多少个备份

备份同步列表:ISR(即in-sync Replica)

Leader会跟踪与其保持同步的Replica列表,该列表称为ISR(即in-sync Replica)

同步复制:同步复制要求所有能工作的Follower都复制完,这条消息才会被认为commit,这种复制方式极大的影响了吞吐率
异步复制:Follower异步的从Leader复制数据,数据只要被Leader写入log就被认为已经commit,这种情况下如果Follower都复制完都落后于Leader,而如果Leader突然宕机,则会丢失数据。

而Kafka采用的既不是同步复制,也不是异步复制。而是采用ISR的方式来均衡确保数据不丢失以及吞吐率。

领导选举

领导选举的方式

少数服从多数: 如果我们有2f+1个Replica(包含Leader和Follower),那在commit之前必须保证有f+1个Replica复制完消息,为了保证正确选出新的Leader,fail的Replica不能超过f个。在生产环境下为了保证较高的容错程度,必须要有大量的Replica,而大量的Replica又会在大数据量下导致性能的急剧下降。
Kafka在Zookeeper中动态维护了一个ISR(in-sync replicas),这个ISR里的所有Replica都跟上了leader,只有ISR里的成员才有被选为Leader的可能。在这种模式下,对于f+1个Replica,一个Partition能在保证不丢失已经commit的消息的前提下容忍f个Replica的失败。

如何处理所有Replica都不工作

在ISR中至少有一个follower时,Kafka可以确保已经commit的数据不丢失,但如果某个Partition的所有Replica都宕机了,就无法保证数据不丢失了。这种情况下有两种可行的方案:

等待ISR中的任一个Replica“活”过来,并且选它作为Leader: 等待时间可能较长。
选择第一个“活”过来的Replica(不一定是ISR中的)作为Leader: 可能会导致数据不全,部分消息丢失。

如何选举Leader

Title:传统Leader选举
clound->Leader: leader
Leader->zk: (leader 宕机)
zk->zk节点: znode会自动删除
clound->follower: follower
follower->zk节点: 创建节点
zk节点->Leader: 竞选leader成功

该方法会有3个问题:   

  • split-brain 这是由Zookeeper的特性引起的,虽然Zookeeper能保证所有Watch按顺序触发,但并不能保证同一时刻所有Replica“看”到的状态是一样的,这就可能造成不同Replica的响应不一致
  • herd effect 如果宕机的那个Broker上的Partition比较多,会造成多个Watch被触发,造成集群内大量的调整
  • Zookeeper负载过重 每个Replica都要为此在Zookeeper上注册一个Watch,当集群规模增加到几千个Partition时Zookeeper负载会过重。

Kafka 0.8.*的Leader Election方案解决了上述问题,它在所有broker中选出一个controller,所有Partition的Leader选举都由controller决定。controller会将Leader的改变直接通过RPC的方式(比Zookeeper Queue的方式更高效)通知需为此作出响应的Broker。同时controller也负责增删Topic以及Replica的重新分配。

broker failover过程简介

  • Controller在Zookeeper注册Watch,一旦有Broker宕机(这是用宕机代表任何让系统认为其die的情景,包括但不限于机器断电,网络不可用,GC导致的Stop The World,进程crash等),其在Zookeeper对应的znode会自动被删除,Zookeeper会fire Controller注册的watch,Controller读取最新的幸存的Broker
  • Controller决定set_p,该集合包含了宕机的所有Broker上的所有Partition
  • 对set_p中的每一个Partition
  • 从/brokers/topics/[topic]/partitions/[partition]/state读取该Partition当前的ISR
  • 决定该Partition的新Leader。如果当前ISR中有至少一个Replica还幸存,则选择其中一个作为新Leader,新的ISR则包含当前ISR中所有幸存的Replica。否则选择该Partition中任意一个幸存的Replica作为新的Leader以及ISR(该场景下可能会有潜在的数据丢失)。如果该Partition的所有Replica都宕机了,则将新的Leader设置为-1。
  • 将新的Leader,ISR和新的leader_epoch及controller_epoch写入/brokers/topics/[topic]/partitions/[partition]/state。注意,该操作只有其version在3.1至3.3的过程中无变化时才会执行,否则跳转到3.1
  • 直接通过RPC向set_p相关的Broker发送LeaderAndISRRequest命令。Controller可以在一个RPC操作中发送多个命令从而提高效率。

Kafka的高可用架构二

更多详细信息

Kafka Consumer设计解析

High Level Consumer: Consumer提供了一个从Kafka消费数据的高层抽象,从而屏蔽掉其中的细节并提供丰富的语义。
Consumer Group: High Level Consumer将从某个Partition中读取最后一条消息的offset存于Zookeeper中,这个offset基于客户程序提供给Kafka的名字来保存,这个名字就叫做consumer group。也就是说用来保存consumer offset的名字。

与传统mq的不同:

  • 很多传统的Message Queue都会在消息被消费完后将消息删除 (一方面避免重复消费,另一方面可以保证Queue的长度比较短,提高效率)。
  • Kafka保证一条消息在同一个consumer group中只消费一次。
  • 允许不同的consumer group同时消费同一条消息。

High Level Consumer Rebalance`

每个Consumer被创建时会触发Consumer Group的Rebalance:

  • High Level Cousumer 启动时将ID注册到Zookeeper的其Counsumer group下,路径为/consumers/[consumer group]/ids/[consumer id]
  • 在/consumers/[consumer group]/ids上注册Watch
  • 在/brokers/ids上注册Watch
  • 如果Consumer通过Topic Filter创建消息流,则它会同时在/brokers/topics上也创建Watch
  • 强制自己在其Consumer Group内启动Rebalance流程

存在的问题:

  • 任何Broker或者Consumer的增减都会触发所有的Consumer的Rebalance。
  • 多个consumer同时触发Rebalance,会导致不正确的Rebalance。

Low Level Consumer

需要对partion或者做特殊处理

  • 同一条消息读多次
  • 只读取某个Topic的部分Partition
  • 管理事务,从而确保每条消息被处理一次,且仅被处理一次

与Consumer Group相比,Low Level Consumer要求用户做大量的额外工作。

  • 必须在应用程序中跟踪offset,从而确定下一条应该消费哪条消息
  • 应用程序需要通过程序获知每个Partition的Leader是谁
  • 必须处理Leader的变化

使用Low Level Consumer的一般流程如下

  • 查找到一个“活着”的Broker,并且找出每个Partition的Leader
  • 找出每个Partition的Follower
  • 定义好请求,该请求应该能描述应用程序需要哪些数据
  • Fetch数据
  • 识别Leader的变化,并对之作出必要的响应

Kafka高性能架构之道

宏观架构层面

利用Partition实现并行处理

  • 由于不同Partition可位于不同机器,因此可以充分利用集群优势,实现机器间的并行处理。
  • 由于Partition在物理上对应一个文件夹,即使多个Partition位于同一个节点,也可通过配置让同一节点上的不同Partition置于不同的disk drive上,从而实现磁盘间的并行处理,充分发挥多磁盘的优势。

利用多磁盘的具体方法是,将不同磁盘mount到不同目录,然后在server.properties中,将log.dirs设置为多目录(用逗号分隔)。Kafka会自动将所有Partition尽可能均匀分配到不同目录也即不同目录(也即不同disk)上。

Partition是最小并发粒度

  • 多Consumer消费同一个Topic时,同一条消息只会被同一Consumer Group内的一个Consumer所消费。
  • 如果Consumer的个数多于Partition的个数,那么会有部分Consumer无法消费该Topic的任何数据,也即当Consumer个数超过Partition后,增加Consumer并不能增加并行度。

Partition个数决定了可能的最大并行度。

ISR实现可用性与数据一致性的动态平衡

CAP理论

CAP理论是指,分布式系统中,一致性、可用性和分区容忍性最多只能同时满足两个。

一致性

  • 通过某个节点的写操作结果对后面通过其它节点的读操作可见
  • 如果更新数据后,并发访问情况下后续读操作可立即感知该更新,称为强一致性
  • 如果允许之后部分或者全部感知不到该更新,称为弱一致性
  • 若在之后的一段时间(通常该时间不固定)后,一定可以感知到该更新,称为最终一致性

可用性

  • 任何一个没有发生故障的节点必须在有限的时间内返回合理的结果

分区容忍性

  • 部分节点宕机或者无法与其它节点通信时,各分区间还可保持分布式系统的功能

一般而言,都要求保证分区容忍性。所以在CAP理论下,更多的是需要在可用性和一致性之间做权衡。

常用数据复制及一致性方案

Master-Slave

  • RDBMS的读写分离即为典型的Master-Slave方案
  • 同步复制可保证强一致性但会影响可用性
  • 异步复制可提供高可用性但会降低一致性

基于ISR的数据复制方案

只有ISR中的所有Replica都复制完,Leader才会将其置为Commit,它才能被Consumer所消费。

使用ISR方案的原因

  • 由于Leader可移除不能及时与之同步的Follower,故与同步复制相比可避免最慢的Follower拖慢整体速度,也即ISR提高了系统可用性。
  • ISR中的所有Follower都包含了所有Commit过的消息,而只有Commit过的消息才会被Consumer消费,故从Consumer的角度而言,ISR中的所有Replica都始终处于同步状态,从而与异步复制方案相比提高了数据一致性。
  • ISR可动态调整,极限情况下,可以只包含Leader,极大提高了可容忍的宕机的Follower的数量。与Majority Quorum方案相比,容忍相同个数的节点失败,所要求的总节点数少了近一半。

具体实现层面

高效使用磁盘

顺序写磁盘

  • 将Partition分为多个Segment,每个Segment对应一个物理文件;
  • 通过删除整个文件的方式去删除Partition内的数据。这种方式清除旧数据的方式,也避免了对文件的随机写操作。

** 充分利用Page Cache **

Page Cache的优势:

  • I/O Scheduler会将连续的小块写组装成大块的物理写从而提高性能
  • I/O Scheduler会尝试将一些写操作重新按顺序排好,从而减少磁盘头的移动时间
  • 充分利用所有空闲内存(非JVM内存)。如果使用应用层Cache(即JVM堆内存),会增加GC负担
  • 读操作可直接在Page Cache内进行。如果消费和生产速度相当,甚至不需要通过物理磁盘(直接通过Page Cache)交换数据
  • 如果进程重启,JVM内的Cache会失效,但Page Cache仍然可用

存在问题:

  • 将数据写入Page Cache,并不保证数据一定完全写入磁盘。
  • 可能会造成机器宕机时,Page Cache内的数据未写入磁盘从而造成数据丢失。

解决措施:

  • 这种场景完全可以由Kafka层面的Replication机制去解决
  • 提供了flush.messages和flush.ms两个参数将Page Cache中的数据强制Flush到磁盘,但是Kafka并不建议使用。(降低性能)

支持多Disk Drive

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

零拷贝

  • 传统模式下的四次拷贝与四次上下文切换
  • sendfile和transferTo实现零拷贝

Linux 2.4+内核通过sendfile系统调用,提供了零拷贝。数据通过DMA拷贝到内核态Buffer后,直接通过DMA拷贝到NIC Buffer,无需CPU拷贝。这也是零拷贝这一说法的来源。除了减少数据拷贝外,因为整个读文件-网络发送由一个sendfile调用完成,整个过程只有两次上下文切换,因此大大提高了性能。

注: transferTo和transferFrom并不保证一定能使用零拷贝。实际上是否能使用零拷贝与操作系统相关,如果操作系统提供sendfile这样的零拷贝系统调用,则这两个方法会通过这样的系统调用充分利用零拷贝的优势,否则并不能通过这两个方法本身实现零拷贝。

减少网络开销

  • 批处理: 批处理既减少了网络传输的Overhead,又提高了写磁盘的效率。
  • 数据压缩降低网络负载: 将整个Batch的消息一起压缩后传输。数据压缩的一个基本原理是,重复数据越多压缩效果越好。因此将整个Batch的数据一起压缩能更大幅度减小数据量,从而更大程度提高网络传输效率。
  • 高效的序列化方式: 减少实际网络传输和磁盘存储的数据规模,从而提高吞吐率。这里要注意,如果使用的序列化方法太慢,即使压缩比非常高,最终的效率也不一定高。
posted @ 2017-07-18 19:07  秋楓  阅读(1526)  评论(0编辑  收藏  举报