「Golang kafka客户端sarama源码分析」 4. Broker的管理和Kafka API的调用

1,042 阅读5分钟

写在前面

上一章中介绍了 partitionConsumer 和 brokerConsumer 二者的协作。

其中提到,partitionConsumer 根据自己订阅的 Topic/Partition,从 Client 中找到相应的 Broker 实例,并查找或创建 brokerConsumer。

进一步的我们知道,brokerConsumer 下的 Broker 实例才是和 Kafka集群Broker 交互的组件,负责调用 Kafka的API,拉取消息。

本章介绍 Client 实例是怎么管理这些 Broker 实例的。

关系概要

saramaClient-导出 (1).png Client 实例中有两个至关重要的成员变量:

  • brokers:map类型变量,通过 BrokerID 索引每个Broker实例。
  • metadata:map类型变量,通过 Topic/Partition 索引到存储每个 Partition 元数据的结构体 PartitionMetadata。在 PartitionMetadata 结构体中,有一个 Leader 变量,它的含义就是,当前 Partitions 中,Leader 分区所在的那个 Kafka集群Broker 的 BrokerID。而进一步的,这个 Leader 所代表的 BrokerID 就是 brokers 成员变量中的索引。

企业微信截图_9e0c7338-d0a5-4920-9504-f8d3a6060580.png 这些有关Kafka集群中Broker和主题分区的元数据,它们都是通过协程的方式,定时触发调用 Kafka 的 GetMetadata API来获取的。这个协程的调用,就在 Client 实例的创建阶段完成。

Client的创建

在专题的第二章中提到,在创建 ConsumerGroup 实例之前,会调用 NewClient 方法创建出 Client 实例。

NewClient 方法在创建出 Client 实例后,会:

  • 根据传入的Kafka集群中Broker的地址列表,调用 randomizeSeedBrokers 方法,为每个地址生成一个 Broker 实例。这些 Broker 实例并不参消息拉取,而是做为“种子Brokers”(seedBrokers),用来在初次拉取Kafka集群元数据时,提供调用Kafka API的接口函数。
  • 通过go withRecover(client.backgroundMetadataUpdater)开启一个协程。

企业微信截图_1d925424-1110-427d-93bf-1742d5aa7352.png

元数据定时更新

协程中运行的 backgroundMetadataUpdater 方法,通过 ticker 定时器配合循环 select 监听的方式,持续间歇性调用 refreshMetadata 方法。

企业微信截图_5def7516-2029-405e-b1d9-e1d721d3aaee.png

这个 refreshMetadata 方法,最终经过下层的调用链,会去执行 tryRefreshMetadata 方法。在这个方法中, 主要做以下几件事:

  • LeastLoadedBroker:根据一定规则,选取一个可用的 Broker 实例(可能从前面提到的“种子Brokers“中获取),作为Kafka API的调用者。
  • GetMetadata:使用上一步选取出的 Broker 实例,调用 GetMetadata API,获取 Kafka 集群中Broker和所有主题/分区的元数据信息。其中包括BrokerID,主题名称,分区ID和分区Leader的信息。

企业微信截图_5973c9b9-5a69-4a73-8e16-5cdb4a37fe55.png

  • updateMetadata:该方法中,根据上一步中获取到的元数据信息,对 Client 实例的 brokers 和 metadata 这两个成员变量进行更新。代码较为简单,这里不过多赘述。

分析至此,我们知道 Client 实例通过定期更新的方式,去拉取Kafka集群的元数据。不断的刷新 Broker 实例和 Topic/Partition 元数据信息。为 sarama 的消费者的每个 Topic/Partition 订阅,提供可用的 Broker 实例。

Broker拉取消息

在上一章中,我们提到 brokerConsumer 调用 fetchNewMessages 去拉取消息。在完成 request 的构建后,最后就是通过 brokerConsumer 所属的成员变量 broker 调用KafkaAPI Fetch去完成消息的拉取。

接下来我们以 Fetch 为例,介绍 Broker 的request和response过程。 企业微信截图_05b11e9c-7eb5-4825-a329-70e335914adc.png

sendAndReceive

在 Broker 调用 Kafka API 发送请求时,都会调用 sendAndReceive 方法,在这个方法中,完成 request 和response 的处理。 企业微信截图_4bbef869-a60d-44b0-870c-99a4b4e5288d.png

  • send:send 方法会将请求进行发送,并返回一个叫做 promise 变量。它是 *responsePromise 类型的变量,指向 responsePromise 实例。responsePromise 实例中两个 channel,分别是 packages 和 errors。它们分别用来传送 Kafka API 返回的正常结果或者错误结果。
  • handleResponsePromise:handleResponsePromise 方法中会通过 select 监听 promise,得到请求结果或者错误结果。 企业微信截图_0151d080-4348-4585-bda7-e3acd28d919b.png

发送request

send 方法首先初始化 promise 实例,并调用 sendWithPromise 方法 企业微信截图_ab65d2da-347c-4a30-a3f5-6b24da8048c3.png

sendWithPromise 方法中会去调用 sendInternal 去发送请求。 企业微信截图_ca1136c2-7042-43c5-8498-bc2c61b63d18.png

sendInternal 方法首先会将 request 序列化,然后调用b.write(buf) 将请求数据写入操作系统 socket。

而接下来一个很重要的是,通过自增 correlationID 生成 Kafka API请求的唯一ID,然后将 correlationID 赋值给 promise 实例。

然后将 promise 实例写入 Broker 实例的 responses channel。这样请求就发送完毕了。

CorrelationId,int32类型,由客户端指定的一个数字唯一标示这次请求的id,
服务器端在处理完请求后也会把同样的CorrelationId写到Response中,
这样客户端就能把某个请求和响应对应起来了。
转载自 --- https://www.zhihu.com/question/436617723

企业微信截图_5d223f5a-2d79-4cf5-869c-d01e02a26713.png

接收response

Broker 实例中有一个叫做 responseReceiver 方法,它会通过 responses channel 接收 promise 实例。 在接收到新的 promise 实例后,会通过b.readFull(header)方法,阻塞读取 response 数据。 在读取到 Kafka 服务端返回的结果后,会将 response 中的 correlationID 和 promise 的 correlationID 对比,如果两者相同,则表示当前结果为这个 promise 等待的请求 response 数据。

企业微信截图_516a025a-bbb6-4550-8bfb-140fbb6b5564.png

在确认 correlationID 后,会执行response.handle(其实就是执行 promise 实例的 handle 方法)处理 response 数据。 企业微信截图_1e52bf18-24a8-4fdd-aacd-96f4b46ab44c.png

在 handle 方法中,会根据请求结果,将数据放入 promise 实例的 packets channel 或者 errors channel 中。前文中 sendAndRevice 方法中调用的 handleResponsePromise 方法,就是在监听 promise 的这两个通道去接收请求结果。 企业微信截图_2b296e27-2215-43af-ae62-020eec05618d.png

到此为止还有一个问题,就是 responseReceiver 方法是何时被调用的?

这个答案藏在上一篇中 pratitionConsumer 创建并匹配 brokerConsumer 实例时调用的 LeaderAndEpoch 方法中。 企业微信截图_c9f28943-d25d-48fa-b9b2-18402e948b68.png

LeaderAndEpoch 会调用 cachedLeader 方法,该方法在通过 Topic/Partition 订阅成功匹配到 Broker 实例后,会调用 Broker 下的 Open 方法。 企业微信截图_c0506982-85a6-4aa2-8853-f97c0c430280.png

在 Broker 的 Open 方法中会做三件事:

  • 创建 Broker 实例和 Kafka集群 Broker 的TCP连接。
  • 创建 responses 通道,用于 promise 的传送。
  • 通过执行go withRecover(b.responseReceiver),调用 responseReceiver 方法,去监听 responses 通道,处理 promise。

以上步骤就是Broker与Kafka服务端的交互过程。Broker 的运行机制还是较为复杂的。

总结

本章分析了 sarama ConsumerGroup 源码中涉及到的最后两个组件:Client 和 Broker。对于 ConsumerGroup 的源码分析也就结束了。