![d65c083557b8f4cd8736036e03aa787a.png](https://img-blog.csdnimg.cn/img_convert/d65c083557b8f4cd8736036e03aa787a.png)
本文是 Kafka 源码解析的第三篇,主要讲述一个 topic 的创建过程,从 topic 是如何创建到 topic 真正创建成功的中间详细过程,文章主要内容可以分为以下几个部分:
- topic 是如何创建的?
- 命令行创建;
- Producer 发送数据时,自动创建;
- topic 创建时,replicas 是如何分配的?
- 指定 replicas 的分配;
- 自动 replicas 分配;
- replicas 更新到 zk 后,底层如何创建一个 topic?
- 创建 Partition 对象及状态更新;
- 创建 Partition 的 replica 对象及状态更新。
一个 topic 的完整创建过程如下图所示(以 topic 的 replicas 自动创建,且 broker 没有机架感知为例)
![59e6319193ebc33aeb226e72987cd52b.png](https://img-blog.csdnimg.cn/img_convert/59e6319193ebc33aeb226e72987cd52b.png)
上图只是列出一些主要的方法调用,具体内容下面会详细讲述(在看下面的内容时,最后配合上面这张图来看)。
topic 介绍
topic 是 Kafka 中的一个消息队列的标识,也可以认为是消息队列的一个 id,用于区分不同的消息队列,一个 topic 由多个 partition 组成,这些 partition 是通常是分布在不同的多台 Broker 上的,为了保证数据的可靠性,一个 partition 又会设置为多个副本(replica),通常会设置两副本或三副本。如下图所示,这个一个名为『topic』的 topic,它由三个 partition 组成,两副本,假设 Kafka 集群有三台 Broker(replica 0_1 代表 partition 0 的第一个副本)。
![a1c13f368d55c618306c33d0df3d941c.png](https://img-blog.csdnimg.cn/img_convert/a1c13f368d55c618306c33d0df3d941c.png)
Kafka Topic 的组成
在设置副本时,副本数是必须小于集群的 Broker 数的,副本只有设置在不同的机器上才有作用。
topic 如何创建
topic 在创建时有两种方式:
- 通过
kafka-topics.sh
创建一个 topic,可以设置相应的副本数让 Server 端自动进行 replica 分配,也可以直接指定手动 replica 的分配; - Server 端如果
auto.create.topics.enable
设置为 true 时,那么当 Producer 向一个不存在的 topic 发送数据时,该 topic 同样会被创建出来,此时,副本数默认是1。
下面看一下这两种方式的底层实现。
kafka-topics.sh 创建 topic
在 Kafka 的安装目录下,通过下面这条命令可以创建一个 partition 为3,replica 为2的 topic(test)
./bin/kafka-topics.sh --create --topic test --zookeeper XXXX --partitions 3 --replication-factor 2
kafka-topics.sh
实际上是调用 kafka.admin.TopicCommand
的方法来创建 topic,其实现如下:
//note: 创建 topic
def createTopic(zkUtils: ZkUtils, opts: TopicCommandOptions) {
val topic = opts.options.valueOf(opts.topicOpt)
val configs = parseTopicConfigsToBeAdded(opts)
val ifNotExists = opts.options.has(opts.ifNotExistsOpt)
if (Topic.hasCollisionChars(topic))
println("WARNING: Due to limitations in metric names, topics with a period ('.') or underscore ('_') could collide. To avoid issues it is best to use either, but not both.")
try {
if (opts.options.has(opts.replicaAssignmentOpt)) {//note: 指定 replica 的分配,直接向 zk 更新即可
val assignment = parseReplicaAssignment(opts.options.valueOf(opts.replicaAssignmentOpt))
AdminUtils.createOrUpdateTopicPartitionAssignmentPathInZK(zkUtils, topic, assignment, configs, update = false)
} else {//note: 未指定 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
val rackAwareMode = if (opts.options.has(opts.disableRackAware)) RackAwareMode.Disabled
else RackAwareMode.Enforced
AdminUtils.createTopic(zkUtils, topic, partitions, replicas, configs, rackAwareMode)
}
println("Created topic "%s".".format(topic))
} catch {
case e: TopicExistsException => if (!ifNotExists) throw e
}
}
如果指定了 partition 各个 replica 的分布,那么将 partition replicas 的结果验证之后直接更新到 zk 上,验证的 replicas 的代码是在parseReplicaAssignment
中实现的,如下所示
def parseReplicaAssignment(replicaAssignmentList: String): Map[Int, List[Int]] = {
val partitionList = replicaAssignmentList.split(",")
val ret = new mutable.HashMap[Int, List[Int]]()
for (i <- 0 until partitionList.size) {
val brokerList = partitionList(i).split(":").map(s => s.trim().toInt)
val duplicateBrokers = CoreUtils.duplicates(brokerList)
if (duplicateBrokers.nonEmpty)//note: 同一个 partition 对应的 replica 是不能相同的
throw new AdminCommandFailedException("Partition replica lists may not contain duplicate entries: %s".format(duplicateBrokers.mkString(",")))
ret.put(i, brokerList.toList)
if (ret(i).size != ret(0).size)//note: 同一个 topic 的副本数必须相同
throw new AdminOperationException("Partition " + i + " has different replication factor: " + brokerList)
}
ret.toMap
}
如果没有指定 parittion replicas 分配的话,将会调用AdminUtils.createTopic
方法创建 topic,这个方法首先会检测当前的 Kafka 集群是否机架感知,如果有的话先获取 Broker 的机架信息,接着再使用 Replica 自动分配算法来分配 Partition 的 replica,最后就跟指定 replica 方式一样,将 replicas 的结果更新到 zk 中。
def createTopic(zkUtils: ZkUtils,
topic: String,
partitions: Int,
replicationFactor: Int,
topicConfig: Properties = new Properties,
rackAwareMode: RackAwareMode = RackAwareMode.Enforced) {
val brokerMetadatas = getBrokerMetadatas(zkUtils, rackAwareMode)//note: 有机架感知的情况下,返回 Broker 与机架之间的信息
val replicaAssignment = AdminUtils.assignReplicasToBrokers(brokerMetadatas, partitions, replicationFactor)//note: 获取 partiiton 的 replicas 分配
AdminUtils.createOrUpdateTopicPartitionAssignmentPathInZK(zkUtils, topic, replicaAssignment, topicConfig)//note: 更新到 zk 上
}
Producer 创建 topic
只有当 Server 端的 auto.create.topics.enable
设置为 true 时ÿ