Kafka基本术语及描述

一、Kafka简介  

  Kafka是一个快速的、可扩展的、高吞吐的、可容错的分布式发布订阅系统,与传统的消息中间件(ActiveMQ、RabbitMQ)相比,Kafka具有高吞吐量、内置分区、支持消息副本和高容错的特点,非常适合大规模消息处理应用程序。

  其系统架构如下所示

  

 

   Kafka的应用场景很多,以下是几个比较常见的场景:

    消息系统 Messaging

    Web网站活动追踪  Website Activity Tracking

    数据监控 Metrics

    日志聚合 Log Aggregation

    流处理 Stream Processing

    事件源 Event Sourcing

    提交日志 Commit Log

  Kafka与其他MQ相比最大的特点就是吞吐率高,为了增加存储能力,Kafka将所有的消息都存放在了硬盘上,但是由于Kafka采用了如下方式,其仍然保证了高吞吐率:

    顺序读写:Kafka将消息写入到了分区partition中,而分区中消息是顺序读写的,顺序读写的速度要远高于随机读写 

    零拷贝:生产者、消费者对于Kafka中消息的操作是采用零拷贝实现的。

    批量发送:Kafka允许使用批量消息发送模式

    消息压缩:Kafka支持对消息进行压缩

二、Kafka的基本术语及描述

术语 说明
Topic 主题,用于划分消息类型,类似于分类标签,是个逻辑概念
Partition 分区,topic中的消息被分割为一个或多个partition,是一个物理概念,对应到系统上的是一个或者多个目录
Segment 段,将partition进一步细分为若干个段,每个segment文件的最大大小相等
Broker Kafka集群包含一个或多个服务器,每个服务器节点称为一个Broker,一个topic中设置partition的数量是broker的整数倍
Producer 生产者,即消息发送者,会将消息发送到相应的partition中
Consumer Group 消费组,一个partition中的消息只能被同一个消费组中的一个消费者进行消费;而一个消费组内的消费者只会消费一个或者几个特定的partition
Replication of partition 分区副本,副本是一个分区的备份,是为了防止消息丢失而创建的分区备份
Partition Leader 每个partition有多个副本,其中有且仅有一个作为Leader,Leader是当前负责读写的partition,即所有读写操作只能发生于Leader分区上
Partition Follower 所有Follower都要从Leader上同步消息,Follower与Leader始终保持消息同步;partition leader与partition follower之间是主备关系而非主从关系
ISR ISR:In-Sync Replicas,是指副本同步列表;AR:Assiged Replicas,指所有副本;OSR:Outof-Sync Replicas;AR=ISR+OSR 
offset 偏移量,每个消息都有一个当前Partition下唯一的64字节的offset,他是相当于当前分区第一条消息的偏移量
offset commit  当consumer从partition中消费了消息后,consumer会将其消费消息的offset提交给broker,表示当前partition已经消费到了该offset所标识的消息。
Rebalance 当消费者组中消费者数量发生变化或者topic中partition数量发生变化,partition的所有权会在消费者间转移,即partition会重新分配。
__commit_offsets  消费者提交的offset被封装为了一种特殊的消息被写入到一个由系统创建的、名称为__commit_offstes的特殊topic的partition中,该topic默认包含50个partition,这些offset的默认有效期为一天
Broker Controller Kafka集群的多个broker中,会有一个被选举为controller,负责管理集群中partition和副本replicas的状态。 
Zookeeper  负责维护和协调Broker,负责Broker Controller的选举;这里要说明一下,Broker Controller是由Zookeeper选举出来的,而Partition Leader是由Broker Controller选举出来的。
Group Coordinator group coordinator是运行在broker上的线程,主要用于consumer group中各个成员的offset位移管理和Rebalance;Group Coordinator同时管理着当前broker的所有消费者组。当Consumer需要消费数据时,并不是直接中__comsumer_offset的partition中获取的,而是从当前broker的Coordinator的缓存中获取的。而缓存中的数据是在consumer消费完提交offset时,同时提交到coordinator的缓存以及__consumer_offset的partition中的。

  里面有的内容直接看上面的说明就可以了,对于部分内容详细介绍一下。

三、topic、partition、segment、broker的关系

  topic是一个主题,是一个逻辑概念,而partition是一个一个先进先出的队列,而消息信息就存在partition队列中,但是由于一个topic对应的partition中消息内容太大,因此将其分为多个segment用于存储。segment文件的最大大小是一致的。

  举个例子,如果一个segment的最大大小为10个字节,当写够10个字节后,就会重新再生成一个segment,segment对应的就是实际的内容文件,而内容文件又分为后缀名为log的具体消息文件和后缀名为index的消息索引文件。

  切换到kafka的日至目录文件,查看即可:

       

   这里因为是我自己的测试,没有那么大的数据量,所以文件都是0,文件的命名方式是以上一个文件的结尾偏移量得来的,第一个文件前面没有文件,所以偏移量为0。

  而broker是kafka的服务器,假设某topic中有N个partiton,集群中有M个broker,则partiton与broker的关系为:

    若N>M,且N%M =0 ,则每个broker会平均存储该topic的多个partiton

    若N>M,且N%M!=0,则每个broker中的partion数量不是不平均的。应尽量避免这种情况,容易导致Kafka 集群消息不均衡,各个 broker 的任务压⼒不均衡。

    若N<M,则会有N个broker中都存放了⼀个partition,⽽有M-N个broker是没有partition的。

      

  总而言之一句话:⼀个 Topic 的消息可以被存放到多个 Broker 中,⼀个 Broker 中可以存放⼀个 Topic 的多 个Partition,⽽⼀个 Partition 中可以存放很多的 Segment,⼀个 Segment 中可以存放很多的消息。

四、producer

  生产者,这个没什么可说的,主要说一下其发送消息时能够指定partition号,从⽽将消息持久化到特定的partition中。如果没有指定具体的partition号,那么Kafka Producer可以通过⼀定的算法计算出对应的partition号。如果消息指定了key,则对key进⾏hash,然后映射到对应的partition号如果消息没有指定key,则使⽤Round Robin轮询算法来确定partition号,这样可以保证数据在所有的partition上平均分配。
  另外,Kafka Producer也⽀持⾃定义的partition分配⽅式。客户端提供⼀个实现了org.apache.kafka.clients.producer.Partitioner 的类,然后将此实现类配置到Producer中即可。
//todo:需求:自定义kafka的分区函数
public class MyPartitioner implements Partitioner{
    /**
     * 通过这个方法来实现消息要去哪一个分区中
     * @param topic
     * @param key
     * @param bytes
     * @param value
     * @param bytes1
     * @param cluster
     * @return
     */
    public int partition(String topic, Object key, byte[] bytes, Object value, byte[] bytes1, Cluster cluster) {
            //获取topic分区数
            int partitions = cluster.partitionsForTopic(topic).size();
            //key.hashCode()可能会出现负数 -1 -2 0 1 2
            //Math.abs 取绝对值
            return Math.abs(key.hashCode()% partitions);
    }

    public void close() {
    }

    public void configure(Map<String, ?> map) {
    }
}

  

五、consumer、consumer group与partition的关系

  consumer是消费者,一个消费者可以订阅多个topic消息,也可以订阅同一个topic中多个partition的消息。

  consumer group是消费者组,每一条消息只能被组内一个实例进行消费,不同的消费组可以消费同一条消息。

  consumer与partition的关系:一个consumer可以消费一个或多个partition的消息,但是一个partition的消息不能被多个消费者消费,其主要目有两个,一个是为了正确的回写消费进度,另一个是为了保证同一个partition的消息顺序写、顺序读;但是这样就会造成一个问题,就是如果消费者的数量多于partition的数量的时候,就一定有消费者是处于空闲。而如果partition不是消费者的整数倍,那么也会存在有的消费者消费的partition比其他消费者多的情况,因此一般情况下,都会将partition的数量设置为消费者的整数倍,这样所有的消费者消费的partition数量一致,不会产生压力不均的问题。

六、Replicas of partition、Partition Leader、Partition Follower

  Replicas of partition就是partition的副本数量,主要是为了解决单点问题导致的broker宕机后partition不可用,如果副本为3,那么看有几个broker,如果有一个,那么在当前的broker上会有相同的三个partition,如果有两个broker,那么会存在一个broker上有一个partition另外一个broker上有两个partition的情况,如果有三个broker,那么每一个broker上都会有一个partition。

      

   而Partition是存在Partition Leader和Partition Follower的,每一个partition在创建的时候,都会使用Zookeeper的临时节点来确定哪一个partition是Leader,那么其余的Partition则都是Follower,只有Leader会处理客户端的读写请求,而Follower只会将Leader中的数据同步到自己的日志中,而不向外部客户端提供任何服务,它的作用就是当Leader所在的broker宕机后,其所链接的Zookeeper就是重新选举一个Leader,这是其中一个Follower会被选举为Leader,其同步的数据也就派上了用场。

  创建一个topic的命令如下所示:
bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --replicationfactor 1 --partitions 1 --topic test

   其中partition表示该topic下面有多少个partition,factor是复制因子,表示每个partition需要复制几份进行保存,这里的复制因子数量不能超过broker的数量,因为超过了,一个broker中存在同一个partition的数量就大于一,没有任何意义,返回会增加数据同步和数据存储的压力。

七、ISR与Unclean

  AR表示所有的Partition副本列表,ISR表示副本同步列表,OSR表示移除的副本。

  ISR包括了Partition Leader和部分满足条件的Partition Follower,如果复制因子为1的话,那就只有Partition Leader。

  在Kafka中,判断Partition Flollower是否和Partition Leader数据同步,看的是Broker 端参数 replica.lag.time.max.ms 参数值。这个参数的含义是 Follower 副本能够落后 Leader 副本的最⻓时间间隔,当前默认值是 10 秒。这就是说,只要⼀个 Follower 副本落后 Leader 副本的时间不连续超过 10 秒,那么 Kafka 就认为该Follower 副本与 Leader 是同步的,即使此时 Follower 副本中保存的消息明显少于 Leader 副本中的消息。

  如上所述,ISR就是那些与Partition Leader保持同步的列表,如果Partition Leader所在的broker宕机,那么优先使用ISR中的Partition选举为Partition Leader。那么如果ISR中没有Partition如何处理呢?这就用到了Unclean选举,其用到的是Broker 端参数 unclean.leader.election.enable 控制是否允许 Unclean 领导者选举。

   (1)false:必须等待ISR列表中有副本活过来才进⾏新的选举。该策略可靠性有保证,但可⽤性低。

   (2)true:在ISR中没有副本的情况下可以选择任何⼀个该Topic的partition作为新的leader,该策略可⽤性⾼,但可靠性没有保证。(不建议使用)

八、offset、offset commit、_consumer offsets

  offset就是偏移量,consumer消费消息时,是通过指定的offset来定位下一条要读取消息的位置,offset的维护是由consumer进行维护的。

  在kafka0.8之后,offset保存在kafka集群上,在0.8版本之前,是保存在zookeeper上的。在新版本中,consumer的offset其实是作为一条普通的消息发送到kafka的,消息的默认主题是_consumer_offsets,其默认有50个partition。

  当consumer消费完消息后,会将消费消息的offset提交给broker,表示这些消息已经被消费。

  consumer提交消费offset的方式有自动提交、手动异步提交、手动同步提交、手动同步异步混合提交这几种方案:

    自动提交:自动提交只需要设置使用时只需要设置enable.auto.commit为true即可。其优点就是比较简单,但是缺点是会产生重复消息。因为自动提交默认的是5秒提交一次,提交的内容是上一次被消费的数据,那么如果在第三秒的时候出现了Rebalance,在Rebalance后,consumer需要重新从上一次确认过的offset处消费,就会造成之前三秒的数据再一次被消费。

    手动提交:手动同步提交需要使用commitSync(),而手动异步提交需要使用commitAsync(),同步提交的优点是比较灵活,但缺点也很明显,就是会阻塞;而异步提交的优点就是相对于同步来说,不会阻塞;那么一般我们会使用同步和异步组合使用,就是进行异步提交offset,但是需要监听broker的响应结果,如果相应结果是提交失败,则再以同步的方式进行提交。

  还有一种更为精致的提交方式,commitSync(Map<TopicPartition, OffsetAndMetadata>)和commitAsync(Map<TopicPartition, OffsetAndMetadata>)。它们的参数是⼀个 Map 对象,键就是 TopicPartition,即消费的分区,⽽值是⼀个

OffsetAndMetadata 对象,保存的主要是位移数据。

  上面提到的_consumer_offsets,该topic的partition默认为50个,使用哪个partition使用的是consumer groupID的hash值与partition数量取模处理,该topic中数据的有效期为1天,其key=groupid+topic+分区号,value就是当前offset的值。写⼊到__consumer_offsets主题的partition中的offset消息格式为:[Group, Topic, Partition]::[OffsetMetadata[Offset, Metadata], CommitTime, ExpirationTime]。当 Kafka 集群中的第⼀个 Consumer 程序启动时,Kafka 会⾃动创建位移主题。

      

九、Rebalance

   Rebalance 本质上是⼀种协议,规定了⼀个 Consumer Group 下的所有 Consumer 如何达成⼀致,来分配订阅 Topic 的每个分区。其触发条件包括:组成员数发⽣变更、订阅主题数发⽣变更、订阅主题的分区数发⽣变更。

   其实Rebalance有一个问题,就是发生Rebalance时,所有的consumer都会需要参与,如果consumer集群足够大,就会造成很大的浪费和性能瓶颈,例如有的大集群,Rebalance一次就需要十几分钟,这是不可以忍受的。其实如果consumer要是可以最小化的参与,就可以很完美的解决这个问题,但是目前kafka还没有提供这样的功能,所以,自求多福,哈哈。

  那么什么是最小化的参与呢,例如现在有三个consumer A、B、C,那么当B宕机之后,只需要将B消费的partition重新Rebalance到A和C上即可,A和C原来消费的Partition就不需要再重新rebalance了,因为这些消费是没有问题的,这样就完美解决了大集群Rebalance的问题,但是目前Kafka是在每一次Rebalance的时候,都会将所有的Partition和Consumer进行处理,原来A和C消费的Partition也会重新Rebalance。

十、Controller

  1、Controller的选举:

    controller 除了是⼀个普通的 broker 之外,还是集群的总扛把⼦,它负责副本 leader 的选举、topic 的创建和删除、副本的迁移、副本数的增加、broker 上下线的管理等等。broker controller负责管理分区与副本,broker controller 由 zk 负责选举。Broker 在启动时,会尝试去 ZooKeeper 中创建 /controller 节点。第⼀个成功创建 /controller 节点的 Broker 会被指定为控制器。

      

 

     上图可以看到,我搭建的集群里面,controller是id为3的broker。

  2、Controller的重新选举

    当Controller所在的broker发生宕机时,其连接的zk就会删除Controller这个临时节点,然后其他的broker注册的监听器就会监听到这个变化,然后重新向ZK注册Controller节点,注册成功的broker就成为了新的Controller。

   选举出新 controller 的同时, /controller_epoch 中的值也会加 1,这个节点记录 controller的代数。

      

 

   3、Controller管理broker的离开和加入

    topic下的partition有Leader和Follower,当Leader所在的broker发生宕机后,Controller会向所有ISR(可以被选举为Leader的Follower)发送信息,重新选择一个Partition Leader。

      

 

   4、Controller的作用

    Controller的主要作用就是用于协调。

    (1)选举Leader和ISR

      从分区副本列表中选取一个作为该分区的Leader,并将该分区对应所有副本置于ISR列表中。

    (2)同步元数据信息包括broker和分区的元数据信息

      控制器将ZK的/brokers/ids以及上⼀个步骤得到的topic下各分区leader和ISR将这些元数据信息同步到集群每个broker。当有broker或者分区发⽣变更时及时更新到集群保证集群每⼀台broker缓存的是最新元数据。

      

 

    (3)broker增删监听与处理

      控制器启动时就起⼀个监视器监视ZK/brokers/ids/⼦节点。当存在broker启动加⼊集群后都会在ZK/brokers/ids/增加⼀个⼦节点brokerId,控制器的监视器发现这种变化后,控制器开始执⾏broker加⼊的相关流程并更新元数据信息到集群。

      控制器启动时就起⼀个监视器监视ZK/brokers/ids/⼦节点。当⼀个broker崩溃时,该broker与ZK的会话失效导致ZK会删除该⼦节点,控制器的监视器发现这种变化后,控制器开始执⾏broker删除的相关流程并更新元数据信息到集群。

    (4)topic变化监听与处理

      控制器启动时就起⼀个监视器监视ZK/brokers/topics/⼦节点。当通过脚本或者请求创建⼀个topic后,该topic对应的所有分区及其副本都会写⼊该⽬录下的⼀个⼦节点。控制器的监视器发现这种变化后,控制器开始执⾏topic创建的相关流程包括leader选举和ISR并同步元数据信息到集群;且新增⼀个监视器监视ZK/brokers/topics/<新增topic⼦节点内容>防⽌该topic内容变化。

      控制器启动时就起⼀个监视器监视ZK/admin/delete_topics/⼦节点。当通过脚本或者请求删除⼀个topic后,该topic会写⼊该⽬录下的⼀个⼦节点。控制器的监视器发现这种变化后,控制器开始执⾏topic删除的相关流程包括通知该topic所有分区的所有副本停⽌运⾏;通知所有分区所有副本删除数据;删除ZK/admin/delete_topics/<待删除topic⼦节点>。

    (5)分区变化监听与变化处理

      当创建⼀个topic后,控制器会增加⼀个监视器监视ZK/brokers/topics/<新增topic⼦节点内容>防⽌该topic内容变化。当通过脚本执⾏分扩展后会在该⽬录增加新的分区⽬录。控制器的监视器发现这种变化后,控制器开始执⾏分区扩展相应流程如选举leader和ISR并同步。

    (6)broker优雅退出

      相⽐较broker机器直接宕机或强制kill,通过脚本关闭⼀个broker我们称为broker优雅退出。即将关闭的broker向控制器发送退出请求后⼀直阻塞。控制器接收到请求后,执⾏leader重选举和ISR后响应broker。broker接收后退出。

      这个⽐较特殊,不依赖ZK,直接通过broker和控制器RPC通信即可完成。

    (7)数据服务

      控制器的最后⼀⼤类⼯作,就是向其他 Broker 提供数据服务。控制器上保存了最全的集群元数据信息,其他所有 Broker 会定期接收控制器发来的元数据更新请求,从⽽更新其内存中的缓存数据。

      控制器保存数据如下:

         所有主题信息。包括具体的分区信息,⽐如领导者副本是谁,ISR 集合中有哪些副本等。

        所有 Broker 信息。包括当前都有哪些运⾏中的 Broker,哪些正在关闭中的 Broker 等。

        所有涉及运维任务的分区。包括当前正在进⾏ Preferred 领导者选举以及分区重分配的分区列表

      

十一、Group Coordinator

  Group Coordinator主要⽤于Consumer Group中的各个成员的offset位移管理和Rebalance。Group Coordinator同时管理着当前broker的所有消费者组。

  1、Coordinator的作⽤

    每个consumer group都会选择⼀个broker作为⾃⼰的coordinator,他是负责监控这个消费组⾥的各个消费者的⼼跳,以及判断是否宕机,然后开启rebalance。根据内部的⼀个选择机制,会挑选⼀个对应的Broker,Kafka总会把各个消费组均匀分配给各个Broker作为coordinator来进⾏管理的。consumer group中的每个consumer刚刚启动就会跟选举出来的这个consumer group对应的coordinator所在的broker进⾏通信,然后由coordinator分配分区给你的这个consumer来进⾏消费。coordinator会尽可能均匀的分配分区给各个consumer来消费。

  2、如何选择哪台是coordinator

    ⾸先对消费组的groupId进⾏hash,接着对consumer_offsets的分区数量取模,找到你的这个consumer group的offset要提交到consumer_offsets的哪个分区。⽐如说:groupId,"membership-consumer-group" -> hash值(数字)-> 对50取模 -> 就知道这个consumer group下的所有的消费者提交offset的时候是往哪个分区去提交offset,找到consumer_offsets的⼀个分区,consumer_offset的分区的副本数量默认来说1,只有⼀个leader,然后对这个分区找到对应的leader所在的broker,这个broker就是这个consumer group的coordinator了,consumer接着就会维护⼀个Socket连接跟这个Broker进⾏通信。

 

 

posted @ 2021-03-21 18:14  李聪龙  阅读(535)  评论(0编辑  收藏  举报