Kafka 深入分析,发送、消费消息全流程

Kafka的消息传递:首先我们要先定义好topic,然后producer生产message,push到broker,随后consumer 从订阅的toipc中pull到消息消费。

一、producer发布消息

1、写入方式

producer 采用 push 模式将消息发布到 broker,每条消息都被 append 到 patition 中,属于顺序写磁盘(顺序写磁盘效率比随机写内存要高,保障 kafka 吞吐率)。

2、消息路由

producer 发送消息到 broker 时,会根据分区算法选择将其存储到哪一个 partition。其路由机制为:

  • 指定了 patition,则直接使用。
  • 未指定 patition 但指定 key,通过对 key 的 value 进行hash 选出一个 patition。
  • patition 和 key 都未指定,使用轮询选出一个 patition。

3、写入流程

producer 的写入流程为,

  1. producer 先从 zookeeper 的 "/brokers/.../state" 节点找到该 partition 的 leader 
  2. producer 将消息发送给该 leader 
  3. leader 将消息写入本地 log 
  4. followers 从 leader pull 消息,写入本地 log 后 leader 发送 ACK 
  5. leader 收到所有 ISR 中的 replica 的 ACK 后,增加 HW(high watermark,最后 commit 的 offset) 并向 producer 发送 ACK

序列图如下,

4、producer delivery guarantee

kafka 提供三种方式,

  • At most once 消息可能会丢,但绝不会重复传输 
  • At least one 消息绝不会丢,但可能会重复传输 
  • Exactly once 每条消息肯定会被传输一次且仅传输一次

当 producer 向 broker 发送消息时,一旦这条消息被 commit,由于 replication 的存在,它就不会丢。但是如果 producer 发送数据给 broker 后,遇到网络问题而造成通信中断,那 Producer 就无法判断该条消息是否已经 commit。虽然 Kafka 无法确定网络故障期间发生了什么,但是 producer 可以生成一种类似于主键的东西,发生故障时幂等性的重试多次,这样就做到了 Exactly once,但目前还并未实现。所以目前默认情况下一条消息从 producer 到 broker 是确保了 At least once,可通过设置 producer 异步发送实现At most once。

二、broker 保存消息

1、存储方式

物理上把 topic 分成一个或多个 patition(对应 server.properties 中的 num.partitions=3 配置),每个 patition 物理上对应一个文件夹(该文件夹存储该 patition 的所有消息和索引文件),如下:

2、存储策略

无论消息是否被消费,kafka 都会保留所有消息。有两种策略可以删除旧数据:

  • 基于时间:log.retention.hours=168 
  • 基于大小:log.retention.bytes=1073741824

需要注意的是,因为Kafka读取特定消息的时间复杂度为O(1),即与文件大小无关,所以这里删除过期文件与提高 Kafka 性能无关。

3、topic 的创建

创建 topic 的流程,

  • controller 在 ZooKeeper 的 /brokers/topics 节点上注册 watcher,当 topic 被创建,则 controller 会通过 watch 得到该 topic 的 partition/replica 分配。
  • controller从 /brokers/ids 读取当前所有可用的 broker 列表,对于 set_p 中的每一个 partition:
    • 从分配给该 partition 的所有 replica(称为AR)中任选一个可用的 broker 作为新的 leader,并将AR设置为新的 ISR 
    • 将新的 leader 和 ISR 写入 /brokers/topics/[topic]/partitions/[partition]/state 
  • controller 通过 RPC 向相关的 broker 发送 LeaderAndISRRequest。

4、topic 的删除

删除 topic 的流程,

  • controller 在 zooKeeper 的 /brokers/topics 节点上注册 watcher,当 topic 被删除,则 controller 会通过 watch 得到该 topic 的 partition/replica 分配。 
  • 若 delete.topic.enable=false,结束;否则 controller 注册在 /admin/delete_topics 上的 watch 被 fire,controller 通过回调向对应的 broker 发送 StopReplicaRequest。

三、consumer 消费消息

1、consumer API

kafka 提供了两套 consumer API,

  • The high-level Consumer API 
  • The SimpleConsumer API

其中 high-level consumer API 提供了一个从 kafka 消费数据的高层抽象,而 SimpleConsumer API 则需要开发人员更多地关注细节。

2、consumer group

kafka 的分配单位是 patition。每个 consumer 都属于一个 group,一个 partition 只能被同一个 group 内的一个 consumer 所消费(也就保障了一个消息只能被 group 内的一个 consuemr 所消费),但是多个 group 可以同时消费这个 partition。

kafka 的设计目标之一就是同时实现离线处理和实时处理,根据这一特性,可以使用 spark/Storm 这些实时处理系统对消息在线处理,同时使用 Hadoop 批处理系统进行离线处理,还可以将数据备份到另一个数据中心,只需要保证这三者属于不同的 consumer group。如下图所示:

3、消费方式

consumer 采用 pull 模式从 broker 中读取数据。

push 模式很难适应消费速率不同的消费者,因为消息发送速率是由 broker 决定的。它的目标是尽可能以最快速度传递消息,但是这样很容易造成 consumer 来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。而 pull 模式则可以根据 consumer 的消费能力以适当的速率消费消息。

对于 Kafka 而言,pull 模式更合适,它可简化 broker 的设计,consumer 可自主控制消费消息的速率,同时 consumer 可以自己控制消费方式——即可批量消费也可逐条消费,同时还能选择不同的提交方式从而实现不同的传输语义。

1)consumer delivery guarantee

如果将 consumer 设置为 autocommit,consumer 一旦读到数据立即自动 commit。如果只讨论这一读取消息的过程,那 Kafka 确保了 Exactly once。但实际使用中应用程序并非在 consumer 读取完数据就结束了,而是要进行进一步处理,而数据处理与 commit 的顺序在很大程度上决定了consumer delivery guarantee。

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

2)consumer rebalance

当有 consumer 加入或退出、以及 partition 的改变(如 broker 加入或退出)时会触发 rebalance。consumer rebalance算法如下:

  • 将目标 topic 下的所有 partirtion 排序,存于PT 
  • 对某 consumer group 下所有 consumer 排序,存于 CG,第 i 个consumer 记为 Ci 
  • N=size(PT)/size(CG),向上取整 
  • 解除 Ci 对原来分配的 partition 的消费权(i从0开始) 
  • 将第i*N到(i+1)*N-1个 partition 分配给 Ci

四、kafka HA(高可用)

1、 replication(备份)

同一个 partition 可能会有多个 replica(对应 server.properties 配置中的 default.replication.factor=N)。没有 replica 的情况下,一旦 broker 宕机,其上所有 patition 的数据都不可被消费,同时 producer 也不能再将数据存于其上的 patition。引入replication 之后,同一个 partition 可能会有多个 replica,而这时需要在这些 replica 之间选出一个 leader,producer 和 consumer 只与这个 leader 交互,其它 replica 作为 follower 从 leader 中复制数据。

Kafka 分配 Replica 的算法如下:

  • 将所有 broker(假设共 n 个 broker)和待分配的 partition 排序 
  • 将第 i 个 partition 分配到第(i mod n)个 broker 上 
  • 将第 i 个 partition 的第 j 个 replica 分配到第((i + j) mode n)个 broker上

2、leader failover

当 partition 对应的 leader 宕机时,需要从 follower 中选举出新 leader。在选举新leader时,一个基本的原则是,新的 leader 必须拥有旧 leader commit 过的所有消息。

kafka 在 zookeeper 中(/brokers/.../state)动态维护了一个 ISR(in-sync replicas),由4.1.3节的写入流程可知 ISR 里面的所有 replica 都跟上了 leader,只有 ISR 里面的成员才能选为 leader。对于 f+1 个 replica,一个 partition 可以在容忍 f 个 replica 失效的情况下保证消息不丢失。

当所有 replica 都不工作时,有两种可行的方案:

  • 等待 ISR 中的任一个 replica 活过来,并选它作为 leader。可保障数据不丢失,但时间可能相对较长。 
  • 选择第一个活过来的 replica(不一定是 ISR 成员)作为 leader。无法保障数据不丢失,但相对不可用时间较短。

3、broker failover

kafka broker failover 的流程,

  • controller 在 zookeeper 的 /brokers/ids/[brokerId] 节点注册 Watcher,当 broker 宕机时 zookeeper 会 fire watch
  • controller 从 /brokers/ids 节点读取可用broker 
  • controller决定set_p,该集合包含宕机 broker 上的所有 partition 
  • 对 set_p 中的每一个 partition 
    • 从/brokers/topics/[topic]/partitions/[partition]/state 节点读取 ISR 
    • 决定新 leader 
    • 将新 leader、ISR、controller_epoch 和 leader_epoch 等信息写入 state 节点
  • 通过 RPC 向相关 broker 发送 leaderAndISRRequest 命令

4、controller failover

当 controller 宕机时会触发 controller failover。每个 broker 都会在 zookeeper 的 "/controller" 节点注册 watcher,当 controller 宕机时 zookeeper 中的临时节点消失,所有存活的 broker 收到 fire 的通知,每个 broker 都尝试创建新的 controller path,只有一个竞选成功并当选为 controller。

当新的 controller 当选时,会触发 KafkaController.onControllerFailover 方法,在该方法中完成如下操作:

  • 读取并增加 Controller Epoch。 
  • 在 reassignedPartitions Patch(/admin/reassign_partitions) 上注册 watcher。 
  • 在 preferredReplicaElection Path(/admin/preferred_replica_election) 上注册 watcher。 
  • 通过 partitionStateMachine 在 broker Topics Patch(/brokers/topics) 上注册 watcher。 
  • 若 delete.topic.enable=true(默认值是 false),则 partitionStateMachine 在 Delete Topic Patch(/admin/delete_topics) 上注册 watcher。 
  • 通过 replicaStateMachine在 Broker Ids Patch(/brokers/ids)上注册Watch。 
  • 初始化 ControllerContext 对象,设置当前所有 topic,“活”着的 broker 列表,所有 partition 的 leader 及 ISR等。 
  • 启动 replicaStateMachine 和 partitionStateMachine。 
  • 将 brokerState 状态设置为 RunningAsController。 
  • 将每个 partition 的 Leadership 信息发送给所有“活”着的 broker。 
  • 若 auto.leader.rebalance.enable=true(默认值是true),则启动 partition-rebalance 线程。 
  • 若 delete.topic.enable=true 且Delete Topic Patch(/admin/delete_topics)中有值,则删除相应的Topic。
发布了125 篇原创文章 · 获赞 116 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/shipfei_csdn/article/details/103919110