Kafka-Topic创建源码分析

本文详细分析了Kafka中创建Topic的源码流程,从AdminClient的处理开始,包括MetadataRequest、createTopics的过程,再到Controller的Topic创建处理,以及Broker如何处理Topic创建。文中介绍了每个阶段的关键步骤和组件交互,如ReplicationControlManager的副本分配策略,并探讨了不同场景下的副本分配情况。最后,详细阐述了Broker如何通过MetadataDelta和ReplicaManager来处理Topic创建的相关操作,如applyDelta和applyLocalLeadersDelta等。
摘要由CSDN通过智能技术生成

Kafka-Topic创建源码分析

在kafka中,创建topic通过使用kafka-topics.sh脚本或者直接调用AdminClient对外提供的adminApi来进行创建.

即使是使用kafka-topics.sh,其最终会通过生成并调用AdminClient来进行处理.

0,创建topic流程图

在这里插入图片描述

1,创建topic示例代码

通过引入AdminClient利用代码实现创建topic的示例代码.

//配置brokerServer的链接地址(`bootstrap.servers`):`host1:port,host2:port`
Properties props = new Properties();
props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
//`Admin.create(props)`:生成`KafkaAdminClient`实例.
//==>jdk8以后可用try来自动执行close
try (Admin admin = Admin.create(props)) {
   
  String topicName = "test-topic";
  int partitions = 12;
  short replicationFactor = 2;
  NewTopic newTopic = new NewTopic(topicName, partitions, replicationFactor).configs(
      //配置topic的log的日志清理策略(适合于带有key,value消息的topic)
      Collections.singletonMap(TopicConfig.CLEANUP_POLICY_CONFIG, TopicConfig.CLEANUP_POLICY_COMPACT)
  );
  //向brokerServer发起创建topic的请求(`CreateTopicsRequest`).
  CreateTopicsResult result = admin.createTopics(Collections.singleton(newTopic));
  //等待response的响应结果.
  result.all.get();
}

2,AdminClient处理流程

2,1,Admin.create

Admin.create函数主要用于生成用于与broker节点进行通信的KafkaAdminClient实例.

static Admin create(Properties props) {
   
    return KafkaAdminClient.createInternal(new AdminClientConfig(props, true), null);
}

Admin.create的实现代码可以看到,其直接调用了KafkaAdminClient内部的createInternal函数来完成adminClient的初始化.

createInternal函数处理流程

Step=>1

生成用于管理client端metadataCacheAdminMetadataManager实例,实例初始化时依赖配置项:

retry.backoff.ms 默认值100ms,当metadata相关请求失败后,下一次重试的backOff时间.

metadata.max.age.ms 默认值5分钟,

​ client端metadataCache的过期时间,当metadata上一次更新时间超过这个值后,会重新请求获取新的metadata.

此时,其内部默认的updater实例默认为AdminMetadataUpdater.

//初始化AdminMetadataManager实例,其`metadataUpdate`.
AdminMetadataManager metadataManager = new AdminMetadataManager(logContext,
                config.getLong(AdminClientConfig.RETRY_BACKOFF_MS_CONFIG),
                config.getLong(AdminClientConfig.METADATA_MAX_AGE_CONFIG));

Step=>2

根据bootstrap.servers配置的brokers列表,初始化生成Cluster实例,并添加到metadataManager实例中.

此时: Cluster实例内部的isBootstrapConfigured属性默认值为true

isBootstrapConfigured值为true时,表示metadataManager处于未准备好的状态.并同时设置metadataManager实例的stateQUIESCENT,

List<InetSocketAddress> addresses = ClientUtils.parseAndValidateAddresses(
        config.getList(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG),
        config.getString(AdminClientConfig.CLIENT_DNS_LOOKUP_CONFIG));
//将初始化的Cluster设置到`metadataManager`中,并设置其`state`为`QUIESCENT`状态.
metadataManager.update(Cluster.bootstrap(addresses), time.milliseconds());

Step=>3

生成用于与broker进行网络通信的NetworkClient通道,其对应的metadataUpdater实例为AdminMetadataUpdater.

channelBuilder = ClientUtils.createChannelBuilder(config, time, logContext);
selector = new Selector(config.getLong(AdminClientConfig.CONNECTIONS_MAX_IDLE_MS_CONFIG),
        metrics, time, metricGrpPrefix, channelBuilder, logContext);
//生成`NetworkClient`实例,其对应的`medataUpdater`实例为`AdminMetadataUpdater`.
networkClient = new NetworkClient(
    metadataManager.updater(),null,selector,clientId,1,
    config.getLong(AdminClientConfig.RECONNECT_BACKOFF_MS_CONFIG),
    config.getLong(AdminClientConfig.RECONNECT_BACKOFF_MAX_MS_CONFIG),
    config.getInt(AdminClientConfig.SEND_BUFFER_CONFIG),
    config.getInt(AdminClientConfig.RECEIVE_BUFFER_CONFIG),
    (int) TimeUnit.HOURS.toMillis(1),
    config.getLong(AdminClientConfig.SOCKET_CONNECTION_SETUP_TIMEOUT_MS_CONFIG),
    config.getLong(AdminClientConfig.SOCKET_CONNECTION_SETUP_TIMEOUT_MAX_MS_CONFIG),
    time,true,apiVersions,null,logContext,
    (hostResolver == null) ? new DefaultHostResolver() : hostResolver);

Step=>4

最后: 根据以上三歩初始化的信息,生成KafkaAdminClient实例.

adminClient端,其clientId的值默认为adminclient-number这样的值.

在初始化KafkaAdminClient时,会同时初始化启动其内部的AdminClientRunnable线程,此线程用于处理与broker的通信.

return new KafkaAdminClient(config, clientId, time, metadataManager, metrics, networkClient,
    timeoutProcessorFactory, logContext);

2,2,MetadataRequest

KafkaAdminClient实例初始化后,其内部的AdminClientRunnableIO线程被启动,

此时在执行processRequests函数时会生成MetadataRequest请求,并立即向broker节点请求获取集群的metadata信息.

KafkaAdminClient实例刚初始化完成时,

metadataManagerlastMetadataUpdateMslastMetadataFetchAttemptMs属性默认值为0,

因此:metadataManager.metadataFetchDelayMs(now)得到的delay时间为0,此时将执行makeMetadataCall生成MetadataRequest.

​ 同时通过maybeDrainPendingCall立即发起请求.

processRequests函数中判断并发起Metdata请求的代码片段:

pollTimeout = Math.min(pollTimeout, maybeDrainPendingCalls(now));
//判断当前metadata是否过期,初始化实例时metadataFetchDelayMs函数返回0,表示过期需要重新获取metadata.
long metadataFetchDelayMs = metadataManager.metadataFetchDelayMs(now);
if (metadataFetchDelayMs == 0) {
   
    metadataManager.transitionToUpdatePending(now);
    //生成`MetadataRequest`请求实例,此时将向`bootstrap.servers`配置的broker节点的随机一个节点发起请求.
    Call metadataCall = makeMetadataCall(now);
    //将获取metadata信息的`Call`实例添加到`callsToSend`队列中. 
    if (!maybeDrainPendingCall(metadataCall, now))
        pendingCalls.add(metadataCall);
}
//执行sendEligibleCalls函数,发起`MetadataRequest`请求.
pollTimeout = Math.min(pollTimeout, sendEligibleCalls(now));

Step=>1

metadataManager.transitionToUpdatePending(now)metadataManager对应的state的状态设置为UPDATE_PENDING.

Step=>2

makeMetadataCall函数,生成用于处理MetadataRequestCall实例.

**==>**此Call实例对应的nodeProvider实现为MetadataUpdateNodeIdProvider.

其生成MetadataRequest参数的实现代码:

public MetadataRequest.Builder createRequest(int timeoutMs) {
   
    return new MetadataRequest.Builder(new MetadataRequestData()
        .setTopics(Collections.emptyList())
        .setAllowAutoTopicCreation(true));
}

Step=>3

maybeDrainPendingCall函数,通过MetadataUpdateNodeIdProvider随机获取bootstrap.servers中的一个broker节点,发起Metadata请求.

//根据`MetadataUpdateNodeIdProvider`随机获取一个broker节点.
//==>同时把metadata请求信息添加到`callsToSend`队列中.
Node node = call.nodeProvider.provide();
if (node != null) {
   
    log.trace("Assigned {} to node {}", call, node);
    call.curNode = node;
    getOrCreateListValue(callsToSend, node).add(call);
    return true;
}

Step=>4

执行sendEligibleCalls函数,把callsToSend队列中已经准备好的请求发送到目标broker节点.

在初始化时此队列中默认只有MetadataRequest请求,因此此时只是向目标节点发起获取Metadata请求.

此时:,发起的MetadataRequest请求将由broker端的KafkaApis中的handleTopicMetadataRequest处理程序进行处理.

​ 此请求在broker端将返回当前metadataImage中所有的activeBrokers节点信息,

​ 并从activeBrokers中随机获取一个节点做为Controller节点.

Step=>5

broker端接收并完成对MetadataRequest请求的处理后进行响应的response将由makeMetadataCall生成的Call实例处理.

=>1, 根据Metadata请求响应的activeBrokerscontroller节点信息,重新生成Cluster实例,

​ 此时生成的Cluster实例对应的isBootstrapConfigured属性的值为false(表示metadata已经准备好).

=>2,执行metadataManagerupdate函数,把最新生成的Cluster更新到metadataManager实例中.

​ 此时:metadataManager对应的lastMetadataUpdateMs属性(metadata最后更新时间)将被更新为当前时间.

public void handleResponse(AbstractResponse abstractResponse) {
   
    MetadataResponse response = (MetadataResponse) abstractResponse;
    long now = time.milliseconds();
    metadataManager.update(response.buildCluster(), now);

    // Unassign all unsent requests after a metadata refresh to allow for a new
    // destination to be selected from the new metadata
    unassignUnsentCalls(node -> true);
}

2,3,createTopics

发起请求

从示例代码中可以看到,当利用AdminClient来创建topic时,在生成NewTopic实例后,会最终调用KafkaAdminClient.createTopics来进行处理.

createTopics代码实现:

=>1,先迭代传入的newTopics容器,取出每一个NewTopic实例,

​ 根据其自定义配置,topicName,numPartitions以及replicationFactor生成对应的CreatableTopic实例.

=>2,根据新生成的CreatableTopic的容器(一到多个),通过调用getCreateTopicsCall生成用于网络请求的Call实例.此时:

​ a,Call实例用于查找请求的目标节点的nodeProvider实例为ControllerNodeProvider(metadata必须初始化,有对应的controller节点).

​ b,生成CreateTopicsRequest请求与其对应的响应处理程序handleResponse(由Call实例定义).

=>3,将新生成用于发起CreateTopicsRequest请求的Call添加到IO线程的newCalls队列中.

public CreateTopicsResult createTopics(final Collection<NewTopic> newTopics,
                                       final CreateTopicsOptions options) {
   
    //生成topic创建是否成功的future监听,client端可通过对函数调用的返回值来监听创建的成功失败.
    final Map<String, KafkaFutureImpl<TopicMetadataAndConfig>> topicFutures = new HashMap<>(newTopics.size());
    final CreatableTopicCollection topics = new CreatableTopicCollection();
    //迭代传入的`NewTopic`集合参数,检查topicName是否为空,
    //==>并根据其`name`,`numPartitions`,`replicationFactor`等配置信息生成`CreatableTopic`添加到`topics`集合中.
    for (NewTopic newTopic : newTopics) {
   
        if (topicNameIsUnrepresentable(newTopic.name())) {
   
            KafkaFutureImpl<TopicMetadataAndConfig> future = new KafkaFutureImpl<>();
            future.completeExceptionally(new InvalidTopicException("The given topic name '" +
                newTopic.name() + "' cannot be represented in a request."));
            topicFutures.put(newTopic.name(), future);
        } else if (!topicFutures.containsKey(newTopic.name())) {
   
            topicFutures.put(newTopic.name(), new KafkaFutureImpl<>());
            topics.add(newTopic.convertToCreatableTopic());
        }
    }
    //如果校验并转换为`CreatableTopic`的信息不为空,生成发起`CreateTopicsRequest`请求的`Call`实例.
    //==>并添加到runnable线程的`newCalls`队列中.
    if (!topics.isEmpty()) {
   
        final long now = time.milliseconds();
        final long deadline = calcDeadlineMs(now, options.timeoutMs());
        final Call call = getCreateTopicsCall(options, topicFutures, topics,
            Collections.emptyMap(), now, deadline);
        runnable.call(call, now);
    }
    return new CreateTopicsResult(new HashMap<>(topicFutures));
}

此时:runnableIO线程(AdminClientRunnable)在processRequests处理时的执行顺序:

=>1, drainNewCalls:将newCalls队列中新生成的Request移动到pendingCalls队列中.

=>2, maybeDrainPendingCalls: 将pendingCalls队列的Request移动到callsToSend队列中(根据Call.nodeProvide查找到node).

=>3,sendEligibleCalls: callsToSend队列中的Request发起网络请求,向指定的目标节点.

此时,请求将由broker端接收到后,会被ForwardingManager组件包装为EnvelopeRequest后,直接转发给activeController进行处理.

​ 通过ForwardingManager中的BrokerToControllerChannelManageractiveController转发请求.

即: 请求最终将由Controler中的ReplicationControlManager组件来进行CreateTopicsRequest处理.

​ 在controllerReplicationControlManager组件用于管理集群的topic信息以及各ISR与partitionLeader的平衡.

​ 并在向metadata中写入TopicRecordPartitionRecord 消息后,由所有kafkaServer节点进行replay来执行真正意义上的创建工作.

同时broker端由到activeControllerresponse后,再重新把response转发给client端.

接收响应

broker端接收到activeController的响应后,会将response转发给client端,此时:

将由getCreateTopicsCall生成的Call实例的handleResponse来进行处理.

其处理response的代码片段:

ApiError error = new ApiError(result.errorCode(), result.errorMessage());
if (error.isFailure()) {
   
    if (error.is(Errors.THROTTLING_QUOTA_EXCEEDED)) {
   
        ThrottlingQuotaExceededException quotaExceededException = new ThrottlingQuotaExceededException(
            response.throttleTimeMs(), error.messageWithFallback());
        if (options.shouldRetryOnQuotaViolation()) {
   
            retryTopics.add(topics.find(result.name()).duplicate());
            retryTopicQuotaExceededExceptions.put(result.name(), quotaExceededException)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值