KafkaController创建topic流程解析

一、kafka-topic.sh

为了便于操作Kafka集群,Kafka源码包中提供了多个shell脚本,其中kafka-topic.sh提供了Topic的创建、修改、列举、描述、删除功能,内部通过TopicCommand来实现。其脚本内容如下:

//kafka-run-class.sh加载kafka的classpath,执行其中kafka.admin.TopicCommand的main函数
exec $(dirname $0)/kafka-run-class.sh kafka.admin.TopicCommand $@

通过解析不同的action来执行不同的操作

object TopicCommand {

  def main(args: Array[String]): Unit = {
    //解析参数
    val opts = new TopicCommandOptions(args)
    //如果没有参数,则输出使用方法并退出
    if(args.length == 0)
      CommandLineUtils.printUsageAndDie(opts.parser, "Create, delete, describe, or change a topic.")
    
    // 确认action的个数,如果有多个就退出
    val actions = Seq(opts.createOpt, opts.listOpt, opts.alterOpt, opts.describeOpt, opts.deleteOpt).count(opts.options.has _)
    if(actions != 1) 
      CommandLineUtils.printUsageAndDie(opts.parser, "Command must include exactly one action: --list, --describe, --create, --alter or --delete")
    //校验参数的有效性
    opts.checkArgs()
    //创建zk连接
    val zkClient = new ZkClient(opts.options.valueOf(opts.zkConnectOpt), 30000, 30000, ZKStringSerializer)

    try {
      //包含create关键字,则创建topic
      if(opts.options.has(opts.createOpt))
        createTopic(zkClient, opts)
      //包含alter关键字,则修改topic
      else if(opts.options.has(opts.alterOpt))
        alterTopic(zkClient, opts)
      //包含list关键字,则列举topic
      else if(opts.options.has(opts.listOpt))
        listTopics(zkClient, opts)
      //包含describe,则描述topic
      else if(opts.options.has(opts.describeOpt))
        describeTopic(zkClient, opts)
      //包含delete关键字,则删除topic
      else if(opts.options.has(opts.deleteOpt))
        deleteTopic(zkClient, opts)
    } catch {
      case e: Throwable =>
        println("Error while executing topic command " + e.getMessage)
        println(Utils.stackTrace(e))
    } finally {
      zkClient.close()
    }
  }

通过上面的代码可知,action的类型主要有:

     --create 创建topic

     --alter 修改topic配置

     --list 列举topic列表

     --describe 描数topic

    --delete 删除topic

下面详细介绍创建topic的流程。

二、创建Topic

使用kafka-topic.sh来创建topic的命令如下:

./kafka-topic.sh --create --zookeeper localhost:2181 --replication-factor 2 --partitions 2 
 --topic test

此时由于没有指定Partition的AR,则会根据负载均衡算法将Partition的Replica均分到各个Broker Server中,并且会选择AR列表中的第一个为Leader  Replica。如果指定了--replica-assignment 1:2,2:3,3:1这样的参数,则设置了Partition的列表。createTopic的流程如下:

def createTopic(zkClient: ZkClient, opts: TopicCommandOptions) {
    //解析出topic名称
    val topic = opts.options.valueOf(opts.topicOpt)
    //解析出配置文件
    val configs = parseTopicConfigsToBeAdded(opts)
    //如果包含--replica-assignment,则提前其中具体的参数
    if (opts.options.has(opts.replicaAssignmentOpt)) {
      val assignment = parseReplicaAssignment(opts.options.valueOf(opts.replicaAssignmentOpt))
      //将topic的配置参数和replic的分片情况分别持久化到zookeeper的不同目录中。
      AdminUtils.createOrUpdateTopicPartitionAssignmentPathInZK(zkClient, topic, assignment, configs)
    } else {
       //不包含--replica-assignment,则需要自动进行分配Replica
      CommandLineUtils.checkRequiredArgs(opts.parser, opts.options, opts.partitionsOpt, opts.replicationFactorOpt)
      val partitions = opts.options.valueOf(opts.partitionsOpt).intValue
      val replicas = opts.options.valueOf(opts.replicationFactorOpt).intValue
      //开始创建topic
      AdminUtils.createTopic(zkClient, topic, partitions, replicas, configs)
    }
    println("Created topic \"%s\".".format(topic))
  }

一般情况下,用户不会指定--replica-assignment,这个时候kafka会采用默认的分配算法来分配Partition的Replica,默认的分配算法主要有两个原则:

      1、针对Topic内的所有的Replicas,要将它们均匀的分配到所有的Broker Servers上。

      2、针对Partition内的Replicas,要将它们均匀的分配到所有的Broker Servers上。

因此分配算法的流程如下:

      1、从Broker Server的随机位置开始按照轮询的方式选择每个Partition的First Replica。

       2、不同Partition剩余的Replica按照一定的偏移量紧跟着各自的First Replica。

假设当前Kafka集群有三个Broker Servers,编号分别为Broker-0,Broker-1,Broker-2,此时准备创建分区个数为3,副本个数为2的Topic,其中P0-1代表分区为0的第一个副本,其它类推,其Replicas的分配流程如下:

其中P0-1恰好落在了Broker-1的机器上,之后的P1-1,P2-1按照轮询的方式分配,并且每个分区的第二个Replica距离第一个Replica恰好一个为一个分区,AdminUtils.createTopic的代码如下:

def createTopic(zkClient: ZkClient,
                  topic: String,
                  partitions: Int, 
                  replicationFactor: Int, 
                  topicConfig: Properties = new Properties) {
    //在分配之前将broker servers排序
    val brokerList = ZkUtils.getSortedBrokerList(zkClient)
    //执行分配算法
    val replicaAssignment = AdminUtils.assignReplicasToBrokers(brokerList, partitions, replicationFactor)
    将topic的配置和Replicas的分配情况持久化至zookeeper
    AdminUtils.createOrUpdateTopicPartitionAssignmentPathInZK(zkClient, topic, replicaAssignment, topicConfig)
  }

 其中assignReplicasToBrokers负责具体的分配算法,其代码如下:

def assignReplicasToBrokers(brokerList: Seq[Int],
                              nPartitions: Int,
                              replicationFactor: Int,
                              fixedStartIndex: Int = -1,
                              startPartitionId: Int = -1)
  : Map[Int, Seq[Int]] = {
    //nPartitions 参数必须大于0
    if (nPartitions <= 0)
      throw new AdminOperationException("number of partitions must be larger than 0")
    //replicationFactor  参数必须大于0
    if (replicationFactor <= 0)
      throw new AdminOperationException("replication factor must be larger than 0")
    /*replicationFactor 不能大于broker server的数量,否则同一个broker会分配到同一个partition的两个Replica.
    */
    if (replicationFactor > brokerList.size)
      throw new AdminOperationException("replication factor: " + replicationFactor +
        " larger than available brokers: " + brokerList.size)

    val ret = new mutable.HashMap[Int, List[Int]]()
    //起始偏移量,如果小于0就取随机数,
    val startIndex = if (fixedStartIndex >= 0) fixedStartIndex else rand.nextInt(brokerList.size)
    //起始分配的Partition,默认从第一个Partition开始
    var currentPartitionId = if (startPartitionId >= 0) startPartitionId else 0
     //计算第二个Replica相比于第一个的偏移量。
    var nextReplicaShift = if (fixedStartIndex >= 0) fixedStartIndex else rand.nextInt(brokerList.size)
    for (i <- 0 until nPartitions) {
      /*如果currentPartitionId 正好等于broker个数,则nextReplicaShift加1,
      从第一个Broker开始
      */
      if (currentPartitionId > 0 && (currentPartitionId % brokerList.size == 0))
        nextReplicaShift += 1
      //计算分区的第一个Replica索引
      val firstReplicaIndex = (currentPartitionId + startIndex) % brokerList.size
      //获取对应的Broker Id
      var replicaList = List(brokerList(firstReplicaIndex))
      for (j <- 0 until replicationFactor - 1)
        //分配分区内其它的Replicas,距离第一个Replica的偏移量为nextReplicaShift 
        replicaList ::= brokerList(replicaIndex(firstReplicaIndex, nextReplicaShift, j, brokerList.size))
      //分配结束,保存起来
      ret.put(currentPartitionId, replicaList.reverse)
      //分区索引+1,继续分配
      currentPartitionId = currentPartitionId + 1
    }
    ret.toMap
  }

当分配结束之后,会将Replica的分配情况写入到zookeeper的/brokers/topics/[topic]目录中,从而触发TopicChangeListener监听器,从而进行Topic的真正创建。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值