www.processon.com/diagraming/…
Kafka 原理详细分析
- Controller 核心控制器
kafka集群中,存在一个或者多个Broker,需要对这些个 Broker进行监控管理,因此,需要在这些个 Broker中选举出一个Broker成为Controller,它负责整个集群中选举的工作。包括以下两种:
- 当某个分区中的某个Leader出现了故障,负责选举产生新的Leader,从 ISR列表 中选举一个Broker成为新的Leader(默认选择第一个Broker作为Leader)
- 当 ISR列表 出现变化(新增或者删除),它将负责通知其他Broker更新元数据
- 当新增分区时,它将分则 通知其他broker,为当前分区选举broker成为其leader
1.1. Controller 选举机制
在kafka集群中,使用脚本启动broker,都会竞争去zookeeper中创建一个 /controller的临时节点,zookeeper保证只有一个broker能成功创建controller临时节点,这个broker就成为了controller(总控制器)。
当controller这个broker宕机了,其他broker监听到controller这个broker宕机了,则会竞争去zookeeper中创建/controller节点。这是也会选举产生一个新的controller。
具备controller功能的broker节点,仅仅是在去承担一份原broker该有职责之外的工作:
- 监控broker的变化:监控着/broker/ids中数组的变化(数组里面维护的是各个broker节点ID)
- 监听Topic的变化:新增一个Topic,需要通知broker来对其进行操作(包括Partion的Leader选举)
- Partition的变化:从zk中读取所有Topic、partition以及broker相关信息并且进行管理,监控着所有topic下partition的变化
- 更新元数据:broker发生变化,则通知所有的broker更新元数据
1.2.Partitions 选举Leader机制
controller 监控到是Leader的Partition的broker挂掉了,则需要从 ISR列表中选举出一个broker成为Leader,分为两种情况,通过设置参数:unclean.leader.election.enable (默认值false)
- unclean.leader.election.enable = false,从ISR列表中选举第一个broker成为partitions的leader
- unclean.leader.election.enable = true,如果ISR列中没有broker,则从replicas 中选择一个broker成为leader,这种方式提高了可用性,但是带来的问题就是数据可能会少很多
follower节点进入到ISR列表中的条件需要满足以下:
-
保证与leader能进行网络传输
-
follower能复制leader上所有的写操作
-
Producer 发送Message机制
producer 采用 push 模式将消息发布到broker,每条消息都被 append 到 partition 中,这个 append 的过程属于 顺序写入磁盘(顺序写入磁盘比随机写入内存速度要快,保证了kafka的吞吐量)
producer 发送消息到 broker ,会根据分区算法选择存储到哪一个 partition 中,其路由机制:
- 如果指定了 发送到 partition,则无需使用路由算法,直接存储到指定的patition(leader)
- 如果没有指定 partition,则需要根据传入的 key 经过hash计算后,除以当前 TOPIC 总分区数,取模得到存储到那个 topic 上的哪个 partition 上,(公式:hash(key)%sum(partition))
- 如果 partition 和 key 都没有指定,则使用 负载均衡 选出一个 partition。
2.1. producer 发送流程
流程步骤分为以下几步:
-
Producer send 一条消息,首先需要通过分区算法,确定在发送消息时,是否指定了 partition,如果指定了,则直接发送给 Leader,执行 writer Log 操作,否则,需要需要进一步判断,是否指定key的value值,如果指定了,则经过hash计算得到对应 partition ,然后执行 writer 操作。如果都没有指定,则通过轮询算法,直接选择一个 partition ,进行writer Log 操作。(拿到 partition)
-
发送 Message 给 partition (Leader)
-
patition(Leader) writer Message 到 Local Log
-
follower 从 Leader 中 拉取Message
-
follower writer Message 到 follower Log
-
follower 给 Leader 应答
-
Leader 收到应答后,则执行 commit offset 操作,并且发送 ack 给 producer。
-
Consumer消费Message的offset机制
consumer 在提交后,会将offset提交给当前分区的kafka内部topic,用于记录最新offset的值:__consumer_offset ,提交过去的 数据 key = consumerGroup+topic+分区号,value = offset的值,kafka会定期清理topic中的数据。
原因是因为kafka默认给的分区数是0到49(50个分区)。
3.1. Consumer的Rebalance(重平衡)机制
如果指定分区,则不会出现 rebalance ,在没有指定 partition 的情况下,会出现 rebalance,以下几种情况会打破平衡:
- consumer Group 中 cusomer 的变化(新增或者减少)
- topic 的变化 (新增或者减少)
注意:rebalance 过程中,consumer 处于阻塞状态,无法从 kafka 服务器中提取消息,进行消费。因此发生 rebalance 会影响 kafka 的 tps 。
3.1.1. Rebalance 过程
- consumer 启动的时候,发出请求,与选择 coordinator(组协调器)建立连接,这时,coordinator 就具有了监控consumer的能力,可以监控consumer的心跳,判断是否宕机,是否发起 rebalance。 coordinator 选择通过 hash(group_id)% __consumer_offset(默认50个分区)
- 当有 consumer 加入到 consumerGroup 组时,首先需要向 coordinator 发出 join Group Requst 请求,coordinator 处理完成后,并且响应结果,如果此时 consumer 是第一个加入到 consumerGroup 中的,则将被推举为 consumer Leader,用于给 consumerGroup 中其他的 consumer 制定分区策略。
- 分区策略制定完成后,Leader 需要将结果发送 coordinator ,接着 coordinator 会将分区的方案下发给 consumer。
rebalance 策略:
分为3种:range(默认)、round-robin、sticky
range:用topic中 sum(partition)与 consumer-group 中 sum(consumer)做计算,得到计算结果,重新将分区分配给那个consumer进行消费,计算公式如下:
m = sum(partition) / sum(consumer) , n = sum(partition) % sum(consumer)
如:patition = 10,consumer = 3,经过计算:m = 3,n = 1 ,分区分配方案:consumer_1 = m+1 =【P0,P1,P2,P3】,consumer_2 = sum(consumer) - n = 【P4,P5,P6】,consumer_3 = sum(consumer) - n = 【P7,P8,P9】
round-robin:轮询分配策略,如consumer_1 = m+1 =【P0,P3,P6,P9】,consumer_2 = sum(consumer) - n = 【P1,P4,P7】,consumer_3 = sum(consumer) - n = 【P2,P5,P8】
sticky:均匀分配策略,如果发生 rebalance 机制,consumer 的个数将会发生改变,但是必须满足以下2个规则:
- 分区分配必须均匀的分配给consumer
- 分区分配方式尽量保持与未发生rebalance之前的分配规则一致
备注:如果以上两个规则产生分歧,优先保证规则1。如 group_consmer 中 3个 consumer,假设最后一个 cosumer 挂掉了,则需要将这个consumer消费的分区,分给consumer1,consumer2,同时保证这两个消费者得到分片后,总数上保持均匀。
- kafkaLog日志分段存储
kafka一个分区的消息存储在一个文件夹下,以topic名称 + 分区号命名,消息在分区内是分段存储的,每一段的消息都存储在不一样的log文件里,这个特性方便删除,且kafka规定一个log日志最大存储1个G的的内容,这样方便快速加载。
由图可以知道,kafka为每个分区都维护了一个 segment 列表,记录了分区内每一个日志文件的offset开始到达到1G时的offset,指向 segment 日志文件。
这样方便于删除,当删除时,只需要将 segment 列表中维护这个日志移除掉,则将没有引用只想segment file。
添加日志时,也只需要在 segment 列表最后,添加一个引用,然后指向分区下的 log 文件即可。