Kafka的框架与运行原理


一、Kafka概述

定义:Kafka 是一个分布式的基于发布/订阅模式的消息队列(Message Queue),主要应用于大数据实时处理领域。

消息队列

  1. 消息队列的同步和异步
    【Kafka】Kafka的框架与运行原理_java
    【Kafka】Kafka的框架与运行原理_数据_02


使用消息队列的好处

解耦 :允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。

可恢复性:系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。
缓冲:有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况。
灵活性 & 峰值处理能力:在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见。如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。
异步通信:很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。


消息队列的两种模式

点对点模式(一对一,消费者主动拉取数据,消息收到后消息清除:消息生产者生产消息发送到Queue中,然后消息消费者从Queue中取出并且消费消息。消息被消费以后,queue 中不再有存储,所以消息消费者不可能消费到已经被消费的消息。
Queue 支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费。

【Kafka】Kafka的框架与运行原理_big data_03

发布/订阅模式(一对多,消费者消费数据之后不会清除消息:消息生产者(发布)将消息发布到 topic 中,同时有多个消息消费者(订阅)消费该消息。和点对点方式不同,发布到 topic 的消息会被所有订阅者消费。

【Kafka】Kafka的框架与运行原理_kafka_04



Kafka基础架构

【Kafka】Kafka的框架与运行原理_big data_05


  • 成员分析
    kafka:就是一个暂存数据,以保持数据一直流通的软件;
    Producer:消息生产者,就是向 kafka broker 发消息的客户端;
    Consumer :消息消费者,向 kafka broker 取消息的客户端;
    Consumer Group (CG):消费者组,由多个 consumer 组成。消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个组内消费者消费;消费者组之间互不影响。所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。
    Broker :一台 kafka 服务器就是一个 broker。一个集群由多个 broker 组成。一个 broker 可以容纳多个 topic。
    Topic :可以理解为一个队列,生产者和消费者面向的都是一个 topic;
    Partition:为了实现扩展性,一个非常大的 topic 可以分布到多个 broker(即服务器)上,一个 topic 可以分为多个 partition,每个 partition 是一个有序的队列;
    Replication:副本,为保证集群中的某个节点发生故障时,该节点上的 partition 数据不丢失,且 kafka 仍然能够继续工作,kafka 提供了副本机制,一个 topic 的每个分区都有若干个副本,一个 leader 和若干个 follower。
    leader:每个分区多个副本的“主”,生产者发送数据的对象,以及消费者消费数据的对象都是 leader。
    follower:每个分区多个副本中的“从”,实时从 leader 中同步数据,保持和 leader 数据的同步。leader 发生故障时,某个 follower 会成为新的 follower。

    在生产者和消费者数量一一对应时效率最高,只要消费值多余生产者,那么就是对资源的浪费

二、Kafka架构深入

工作流程及文件存储机制

工作流程【Kafka】Kafka的框架与运行原理_java_06


  • Kafka可以保持数据的区内有序,全局不能确定是否有序
  • 每一个原文件叫做leader,而他的副本文件叫做follower,当leader坏掉后,follower就会成为新的leader,并且leader和follower不能存储在一个节点之上
  • topic时逻辑上的概念,而partiton时物理上的概念,每个partition都对应一个log文件,该log文件存储product产生的数据,并且Producter产生的数据会被不断的追加到该log文件末端,且每条数据都有自己的offset
  • 消费组中的每个消费者都会实时记录自己消费类那个offset,以便出错时恢复
  • 在消费数据时,会优先消费leader数据


文件存储

【Kafka】Kafka的框架与运行原理_java_07

  • 由于生产者生产的消息会不断追加到 log 文件末尾,为防止 log 文件过大导致数据定位效率低下,Kafka 采取了分片和索引机制,将每个 partition 分为多个 segment。每个 segment 对应两个文件——“.index”文件和“.log”文件。这些文件位于一个文件夹下,该文件夹的命名规则为:topic 名称+分区序号。例如,first 这个 topic 有三个分区。

  • 则其对应的文件夹为 first-
    【Kafka】Kafka的框架与运行原理_kafka_08
    index 和 log 文件以当前 segment 的第一条消息的 offset 命名(偏移量,也就是说文件命名是当前数据最小的一个偏移量)。下图为 index 文件和 log 文件的结构示意图。
    【Kafka】Kafka的框架与运行原理_java_09
    “.index”文件存储大量的索引信息,“.log”文件存储大量的数据,索引文件中的元数据指向对应数据文件中 message 的物理偏移地址。
    如上图举例:索要寻找第700到1000的字符数据:首先会去查找index文件,发现第700个字符数据在2.index对应的log文件也就是​Message-2​中,第1000个字符数据在​4.index​对应的​Message-4​文件中,那么就会去​Message2-4​文件中去寻找

生产者的分析

分区策略

  • 分区的原因
    方便再集群中扩展,每个 Partition 可以通过调整以适应它所在的机器,而一个 topic 又可以有多个 Partition 组成,因此整个集群就可以适应任意大小的数据了;
    可以提高并发,因为可以以 Partition 为单位读写了。

  • 分区的原则
    需要将producer发送的数据封装成一个ProducerRecord对象
    【Kafka】Kafka的框架与运行原理_消息队列_10
    在指明partition的情况下,知将指明的值作为partition值
    在没有明指partition值但是有key值得情况下,将key得hash值与tpoic得partition数进行取余得到partition值
    既没有明值partition值也没有key值得情况下,第一个调用时随机生成一个整数,之后每一个调用整数向上自增,将这个值与topic可用得partition总数取余得到partition值,也就是常说的round-robin算法

可靠性保证

  • 为保证 producer 发送的数据,能可靠的发送到指定的 topic,topic 的每个 partition 收到 producer 发送的数据后,都需要向 producer 发送 ack(acknowledgement 确认收到),如果 producer 收到 ack,就会进行下一轮的发送,否则重新发送数据。
    【Kafka】Kafka的框架与运行原理_消息队列_11
    俩种方案的优缺点

方案

优点

缺点

半数以上同意就发送ack

延迟低

选举新的leader时,认容n台节点故障,需要2n+1副本(需要的节点数多)

全部完成同步,才发送ack

选举新的leader是,容忍n台节点故障,需要n+1个副本(需要的副本数也就是节点数少)

延迟高



Kafka选择的是第二种方案:
Kafka得每个分区都有着大量的数据,第一种方案需要更多的节点,会造成严重的数据冗余
第二种方案得延迟虽然高,但是对网络得要求较低




ISR得产生于应用
当采用第二种方案时,若leader收到数据,所有的follower开始同步数据,但是有一个follower因为故障而不能完成同步,那么leader是否需要一直等下去?一直不向producer放松ack?

Leader 维护了一个动态的 in-sync replica set (ISR),意为和 leader 保持同步的 follower 集合。当ISR中的follower完成数据同步之后,leader会给follower发送ack消息,并且会将迟迟没有响应得follower踢出ISR,若leader也出现了故障,那么就需要从ISR中的follower选举出一个新的leader

该时间阈值(等待时间)是由replica.lag.time.max.ms参数设定




ack应答机制
由于数据的重要性不一致,有些数据可以容忍数据的得些许丢失,这一样就不需要等到ISR中的follower全部的接受完成才发送ack,所以卡夫卡为用户提供了三种可靠性级别
参数为0:producer不等待broker得ack,这个操作有着最低得延迟,但是broker故障时会导致数据丢失
参数为1:producer等待broker得ack,partition得leader落盘成功后返回ack,但是若leader在follower同步成功之前出现故障,那么就会丢失数据
参数为-1:producer等待broker得ack,partition得leader和follower全部成功落盘后才返回ack,但是若在follerer成功同步之后,broker发送ack之前,leader出现故障,会造成数据的重复




  • 故障处理细节
    LEO:每个副本得最后一个offset;HW:所有副本中最小的LEO
    【Kafka】Kafka的框架与运行原理_java_12
    follower故障:follower发生故障后会被踢出ISR,等到1恢复之后,follower会读取本地磁盘记录的上次的HW,并将文件高于HW得部分截掉,从HW开始向leader同步,等待follower得LED大于等于该Partition得HW,也就是follower追上leader之后,就可以重新加入ISR了
    leader故障:会从 ISR 中选出一个新的 leader,之后,为保证多个副本之间的数据一致性,其余的 follower 会先将各自的 log 文件高于 HW 的部分截掉,然后从新的 leader 同步数据。
    这些操作只能保证数据的一致性,但是不能保证数据不丢失或者不会重复

Exactly Once 语义


  • 将服务器的 ACK 级别设置为-1,可以保证 Producer 到 Server 之间不会丢失数据,即At Least Once 语义。相对的,将服务器 ACK 级别设置为 0,可以保证生产者每条消息只会被发送一次,即 At Most Once 语义。
  • At Least Once 可以保证数据不丢失,但是不能保证数据不重复;相对的,At Least Once 可以保证数据不重复,但是不能保证数据不丢失。但是,对于一些非常重要的信息,比如说交易数据,下游数据消费者要求数据既不重复也不丢失,即 Exactly Once 语义。
  • 0.11 版本的 Kafka,引入了一项重大特性:幂等性。所谓的幂等性就是指 Producer 不论向 Server 发送多少次重复数据,Server 端都只会持久化一条。幂等性结合 At Least Once 语义,就构成了 Kafka 的 Exactly Once 语义。即:
    ​​At Least Once + 幂等性 = Exactly Once
  • 要启用幂等性,只需要将 Producer 的参数中 enable.idompotence 设置为 ​true​ 即可。Kafka 的幂等性实现其实就是将原来下游需要做的去重放在了数据上游。开启幂等性的 Producer 在初始化的时候会被分配一个 PID,发往同一 Partition 的消息会附带 Sequence Number。而Broker 端会对​<PID, Partition, SeqNumber>​做缓存,当具有相同主键的消息提交时,Broker 只会持久化一条
  • 但是 PID 重启就会变化,同时不同的 Partition 也具有不同主键,所以幂等性无法保证跨分区跨会话的 Exactly Once。

消费者的分析

消费方式


  • consumer 采用 pull(拉)模式从 broker 中读取数据。
  • push(推)模式很难适应消费速率不同的消费者,因为消息发送速率是由 broker 决定的。它的目标是尽可能以最快速度传递消息,但是这样很容易造成 consumer 来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。而 pull 模式则可以根据 consumer 的消费能力以适当的速率消费消息。
  • pull 模式不足之处是,如果 kafka 没有数据,消费者可能会陷入循环中,一直返回空数据。针对这一点,Kafka 的消费者在消费数据时会传入一个时长参数 timeout,如果当前没有数据可供消费,consumer 会等待一段时间之后再返回,这段时长即为 timeout。


分区分配策略



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



Kafka 有两种分配策略,一是 RoundRobin,一是 Range。



RoundRobin(轮询)
分区数据会按照数据一个个轮回的给消费者
【Kafka】Kafka的框架与运行原理_java_13



range:按照单个主题
是由总的partition数去除以总消费者数,没有余数那么久平均分,有余数,在前面的消费值每个多分到一个
range也是默认的方式【Kafka】Kafka的框架与运行原理_java_14



offset 的维护


  • 由于 consumer 在消费过程中可能会出现断电宕机等故障,consumer 恢复后,需要从故障前的位置的继续消费,所以 consumer 需要实时记录自己消费到了哪个 offset,以便故障恢复后继续消费。
    【Kafka】Kafka的框架与运行原理_消息队列_15
  • Kafka 0.9 版本之前,consumer 默认将 offset 保存在 Zookeeper 中,从 0.9 版本开始, consumer 默认将 offset 保存在 Kafka 一个内置的 topic 中,该 topic 为__consumer_offsets。


Kafka的高效读写


  • 顺序写磁盘:Kafka 的 producer 生产数据,要写入到 log 文件中,写的过程是一直追加到文件末端,为顺序写。官网有数据表明,同样的磁盘,顺序写能到 600M/s,而随机写只有 100K/s。这与磁盘的机械机构有关,顺序写之所以快,是因为其省去了大量磁头寻址的时间。
  • 零复制技术
    【Kafka】Kafka的框架与运行原理_数据_16


zookeeper在Kafka中的作用


  • Kafka 集群中有一个 broker 会被选举为 Controller,负责管理集群 broker 的上下线,所有 topic 的分区副本分配和 leader 选举等工作。
  • Controller 的管理工作都是依赖于 Zookeeper 的。
    【Kafka】Kafka的框架与运行原理_java_17
    当一个leader死掉或者出现故障后,那么在ISR中的follower会进行选举,超过半数的机器同意后那么被选举的follower就会成为新的leader


Kafka事务

Producer


  • 为了实现跨分区跨会话的事务,需要引入一个全局唯一的 Transaction ID,并将 Producer 获得的PID 和Transaction ID 绑定。这样当Producer重启后就可以通过正在进行的Transaction ID 获得原来的 PID。
  • 为了管理 Transaction,Kafka 引入了一个新的组件 Transaction Coordinator。Producer 就是通过和 Transaction Coordinator 交互获得 Transaction ID 对应的任务状态。Transaction Coordinator 还负责将事务所有写入 Kafka 的一个内部 Topic,这样即使整个服务重启,由于事务状态得到保存,进行中的事务状态可以得到恢复,从而继续进行。


Consumer

  • 事务上述事务机制主要是从 Producer 方面考虑,对于 Consumer 而言,事务的保证就会相对较弱,尤其时无法保证 Commit 的信息被精确消费。这是由于 Consumer 可以通过 offset 访问任意信息,而且不同的 Segment File 生命周期不同,同一事务的消息可能会出现重启后被删除的情况。