Kafka API: TopicMetadata

Kafka元数据查询与管理
本文介绍如何使用Kafka的TopicMetadataAPI获取Broker、topic、partition信息,并探讨了自动创建topic的功能。此外,还讨论了如何利用Zookeeper进行Topic的查询与管理。

Jusfr 原创,转载请注明来自博客园

TopicMetadataRequest/TopicMetadataResponse

前文简单说过“Kafka是自描述的”,是指其broker、topic、partition 信息可以通过 TopicMetadata API 获取。

TopicMetadataRequest 的内容非常简单,是一个包含 TopicName 的数组,TopicMetadataResponse 则告诉使用者 Broker、Topic、Partition 的分布情况。

使用空数组可以获取完整数据。

在 Chuye.Kafka 里,使用 Connection/Router 对应的发起一个请求:

    var section = new KafkaConfigurationSection("jusfr.redis", 9092);
    var demoTopics = new String[0];
    var connection = new Router(section);    
    connection.TopicMetadata(demoTopics).Dump("Metadata");

Connection.TopicMetadata() 使用 TopicName 数组作为参数构造了一个 TopicMetadataRequest 实例,将其序列化,发送 KafkaConfigurationSection 指向的主机和端口,读取响应再解析为 TopicMetadataResponse 对象,单机部署的 TopicMetadataResponse 可能有如下结构:

TopicMetadata

当 Kafka 服务的启动参数auto.create.topics.enable设置为true的时候,TopicMetadataRequest 传递的 TopicName 不存在时将被自动创建;

集群模式下 Topic 的自动创建复杂一些,Kafka 携带的 bin/kafka-topics.sh 提供了再多参数。


Zookeeper

  • 如何使用程序查询、删除 Topic? 如何彻底删除 Topic ?
  • 如何在集群模式下管理 Topic

源码阅读得知,Kafka 对 TopicMetadataRequest 的响应是通过引用 Zookeeper 来完成的。Zookeeper 在 .Net 上的实现有 ZooKeeperNet, NuGet 上是3.4.6.2 版本。

Zookeeper 编程又是一大块内容,这里只是略加提及。

ZooKeeper 的两个方法最重要:GetChildren()GetData(),前者提供了路径查询,后者提供了节点数据获取,可以使用以下代码递归访问:

void Main() {
    ZooKeeper zk = new ZooKeeper("jusfr.mac", TimeSpan.FromSeconds(10), null);
    var paths = zk.GetChildren("/", false).ToArray();
    foreach (var path in paths) {
        GetChildren(zk, "/" + path);
    }
}

void GetChildren(ZooKeeper zk, String path) {
    var data = zk.GetData(path, null, null); 
    var paths = zk.GetChildren(path, false).ToArray();
    if (paths.Length > 0) {
        foreach (var p in paths) {
            GetChildren(zk, path + "/" + p);
        }
    }
}

在集群环境下部分响应示例

topics

// /brokers/topics/demoTopic1
{"version":1,"partitions":{"0":[2]}}


// /brokers/topics/demoTopic1/partitions/0/state
{"controller_epoch":1,"leader":2,"version":1,"leader_epoch":0,"isr":[2]} 

// /brokers/ids/1
{"jmx_port":-1,"timestamp":"1457431238732","endpoints":["PLAINTEXT://jusfr.kafka-1:9093"],"host":"jusfr.kafka-1","version":2,"port":9093} 

路径 /brokers/topics 存储了topic 信息,/admin/delete_topics 存储了被删除的 topic,这只是一个标记,由于 Kafka 是基于文件系统的,你需要等待 Kafka 在某个时机真正移除它们。部分参考

delete_topics

由于 Kafka 通过 Zookeeper 返回元数据,故任何 Broker 节点都能应答 TopicMetadataRequest 并提供完整响应;

TopicMeatadata in cluster

可以看到 demoTopic3 的 PartitionId=0 分区所在 Leader=1,即 Broker NodeId=1 的节点 jusfr.kafka-1:9093 ,PartitionId=1 分区所在 Leader=2,即 Broker NodeId=2 的节点 jusfr.kafka-2:9094。读写 demoTopic3 的分区0 需要连接到主机 jusfr.kafka-1、端口9093,读写 demoTopic3 的分区1 需要连接到主机 jusfr.kafka-2、端口9094,此过程我称为 Broker route。错误的 Broker 访问、不正确的 server.properties 配置可能触发状态码为 UnknownTopicOrPartition 的响应。

Chuye.Kafka 的 Router 对象从 IRouter 定义,继续自Connection,重写了 Route 方法,内部便是 Partition-Broker 检查逻辑。集群模式下涉及到 Zookeeper 编程,Chuye.Kafka 可能未能给予支持。

Jusfr 原创,转载请注明来自博客园

转载于:https://www.cnblogs.com/Jusfr/p/5257258.html

<think>Kafka使用自定义的二进制协议进行客户端与broker之间的通信。该协议设计高效,通过TCP实现,每个请求和响应都是二进制格式。协议支持多种类型的请求(如生产消息、消费消息、元数据请求等)。下面详细介绍Kafka通信协议的关键点: ### 1. **请求/响应结构** 每个请求和响应都包含一个头部和主体: - **请求头部**: - API Key(2字节):标识请求类型(如PRODUCE、FETCH等) - API Version(2字节):请求的API版本 - Correlation ID(4字节):客户端生成的唯一ID,用于匹配请求和响应 - Client ID(字符串):客户端标识(可选) - **请求主体**:特定于请求类型的数据 - **响应头部**: - Correlation ID(4字节):与请求中的Correlation ID一致 - **响应主体**:包含响应数据和错误码 ### 2. **常见请求类型** | API Key | 名称 | 描述 | |---------|---------------|--------------------------| | 0 | PRODUCE | 生产者发送消息请求 | | 1 | FETCH | 消费者拉取消息请求 | | 3 | METADATA | 获取集群元数据 | | 8 | OFFSET_COMMIT | 提交消费位移 | | 9 | OFFSET_FETCH | 获取消费位移 | | 18 | SASL_HANDSHAKE| SASL握手请求 | | 19 | API_VERSIONS | 查询broker支持的API版本 | ### 3. **协议示例:PRODUCE请求** ```plaintext Request Header: API Key = 0 (PRODUCE) API Version = 7 (Kafka 1.0+) Correlation ID = 123 Client ID = "my-producer" Request Body: transactional_id = null acks = 1 timeout_ms = 5000 [topic_data]: topic_name = "test-topic" [partition_data]: partition_index = 0 record_set = [MessageSet] ``` ### 4. **协议示例:FETCH请求** ```plaintext Request Header: API Key = 1 (FETCH) API Version = 7 Correlation ID = 456 Client ID = "my-consumer" Request Body: replica_id = -1 (consumer) max_wait_ms = 500 min_bytes = 1 max_bytes = 52428800 isolation_level = 0 (read_uncommitted) [topics]: topic_name = "test-topic" [partitions]: partition_index = 0 fetch_offset = 12345 log_start_offset = -1 max_bytes = 1048576 ``` ### 5. **二进制编码示例** 以下伪代码展示PRODUCE请求的编码结构(简化版): ```java // 伪代码:PRODUCE请求编码 ByteBuffer buffer = ByteBuffer.allocate(1024); // 请求头部 buffer.putShort(0); // API Key = 0 (PRODUCE) buffer.putShort(7); // API Version = 7 buffer.putInt(123); // Correlation ID putCompactString(buffer, "my-producer"); // Client ID (使用紧凑字符串) // 请求主体 buffer.putShort(0); // 事务ID(null表示为-1,但实际使用紧凑字符串,长度为-1表示null) buffer.putShort(1); // acks = 1 buffer.putInt(5000); // timeout_ms putCompactArray(buffer, topics); // 主题数组 // 主题数据 putCompactString(buffer, "test-topic"); // 主题名 putCompactArray(buffer, partitions); // 分区数组 // 分区数据 buffer.putInt(0); // 分区索引 putRecords(buffer, records); // 消息集 ``` ### 6. **实际协议实现** Kafka协议使用紧凑的二进制格式,并随着版本迭代不断优化(如KIP-482引入灵活版本头)。实际开发中,客户端库(如Java的`KafkaProducer`)会处理协议细节。 ### 7. **协议版本演进** Kafka协议支持版本协商: - 客户端发送`ApiVersionsRequest`查询broker支持的API版本 - broker返回支持的API版本列表 - 客户端选择双方都支持的版本进行通信 --- §§相关问题§§: 1. Kafka 协议中如何实现消息压缩? 2. 在 Kafka 协议中,消费者组管理(如再平衡)是如何进行的? 3. Kafka 协议如何保证向后兼容性? 4. 如何通过抓包工具(如 Wireshark)分析 Kafka 协议数据包?</think># Kafka 通信协议详解 Kafka 使用自定义的二进制协议进行客户端与服务器之间的通信。该协议设计高效、紧凑,支持多种类型的请求和响应。以下是 Kafka 通信协议的全面解析: ## 一、协议核心特性 1. **二进制格式**:所有消息都采用二进制编码,而非文本格式(如 HTTP/JSON) 2. **请求-响应模型**:客户端发送请求,broker 返回响应 3. **TCP 基础**:基于 TCP 协议实现可靠传输 4. **零拷贝优化**:使用 `sendfile` 和 `mmap` 技术提高性能 5. **版本兼容**:支持协议版本协商和向后兼容 ## 二、协议结构 ### 请求结构(客户端 → Broker) ```plaintext +-------------------------------------------------------------------+ | 请求头 (Request Header) | +-----------------+-----------------+---------------+---------------+ | 字段长度 (4字节) | API Key (2字节) | 版本号 (2字节) | 关联ID (4字节) | +-----------------+-----------------+---------------+---------------+ | 客户端ID长度 (2字节) | 客户端ID (变长) | | +---------------------+---------------+--------------------------------+ | 请求体 (Request Body) | | (内容取决于API Key) | +-------------------------------------------------------------------+ ``` ### 响应结构(Broker → 客户端) ```plaintext +-------------------------------------------------------------------+ | 响应头 (Response Header) | +-----------------+-----------------------------------------------+ | 关联ID (4字节) | 响应体 (Response Body) | +-----------------+-----------------------------------------------+ | (内容取决于API Key) | +-------------------------------------------------------------------+ ``` ## 三、核心 API Key 类型 | API Key | 名称 | 功能描述 | |---------|------------------|----------------------------| | 0 | PRODUCE | 生产者发送消息 | | 1 | FETCH | 消费者拉取消息 | | 3 | METADATA | 获取集群元数据 | | 8 | OFFSET_COMMIT | 提交消费位移 | | 9 | OFFSET_FETCH | 获取消费位移 | | 10 | FIND_COORDINATOR| 查找协调器(用于消费组) | | 11 | JOIN_GROUP | 加入消费组 | | 12 | HEARTBEAT | 消费组心跳 | | 13 | LEAVE_GROUP | 离开消费组 | | 18 | SASL_HANDSHAKE | SASL 认证握手 | | 19 | API_VERSIONS | 查询 API 版本支持 | ## 四、协议实现示例(Java) ### 1. 元数据请求实现(METADATA) ```java import org.apache.kafka.clients.*; import org.apache.kafka.common.requests.*; import org.apache.kafka.common.protocol.types.*; public class KafkaProtocolDemo { // 构建元数据请求 public static MetadataRequest buildMetadataRequest(List<String> topics) { return new MetadataRequest.Builder(topics, true).build(); } // 解析元数据响应 public static void parseMetadataResponse(AbstractResponse response) { MetadataResponse metadataResponse = (MetadataResponse) response; System.out.println("集群控制器: " + metadataResponse.controller()); System.out.println("集群ID: " + metadataResponse.clusterId()); for (MetadataResponse.TopicMetadata topicMetadata : metadataResponse.topicMetadata()) { System.out.println("\n主题: " + topicMetadata.topic()); System.out.println("错误码: " + topicMetadata.error().code()); for (MetadataResponse.PartitionMetadata partition : topicMetadata.partitionMetadata()) { System.out.printf("分区 %d: 领导者 %d, 副本 %s, ISR %s\n", partition.partition(), partition.leader().id(), partition.replicas(), partition.inSyncReplicas()); } } } } ``` ### 2. 生产请求实现(PRODUCE) ```java public class KafkaProducerProtocol { // 构建生产请求 public static ProduceRequest buildProduceRequest(String topic, int partition, byte[] key, byte[] value) { MemoryRecordsBuilder builder = MemoryRecords.builder( ByteBuffer.allocate(1024), CompressionType.NONE, TimestampType.CREATE_TIME, 0L // baseOffset ); builder.append(0L, key, value); MemoryRecords records = builder.build(); Map<TopicPartition, MemoryRecords> recordsMap = new HashMap<>(); recordsMap.put(new TopicPartition(topic, partition), records); return new ProduceRequest.Builder( (short) 7, // acks: 0=无确认, 1=领导者确认, -1=所有ISR确认 5000, // 超时时间(毫秒) recordsMap ).build(); } } ``` ## 五、协议优化技术 ### 1. 批处理机制 ```mermaid graph LR A[生产者] -->|批量消息| B[Broker] B -->|批量响应| A ``` ### 2. 零拷贝数据传输 ```java // Kafka 使用 FileChannel.transferTo 实现零拷贝 FileChannel fileChannel = new FileInputStream(file).getChannel(); fileChannel.transferTo(position, count, socketChannel); ``` ### 3. 消息压缩 支持多种压缩算法: 1. GZIP (最高压缩率) 2. Snappy (低延迟) 3. LZ4 (平衡型) 4. Zstandard (Kafka 2.1+) ## 六、协议版本协商流程 1. 客户端发送 `ApiVersionsRequest` 2. Broker 返回支持的 API 版本范围 3. 客户端选择双方都支持的版本 4. 使用选定版本进行后续通信 ```java // 版本协商实现 ApiVersionsRequest request = new ApiVersionsRequest.Builder().build(); ApiVersionsResponse response = sendRequest(request); for (ApiVersionsResponse.ApiVersion version : response.apiVersions()) { System.out.printf("API Key %d: 支持版本 %d-%d\n", version.apiKey, version.minVersion, version.maxVersion); } ``` ## 七、协议安全性 1. **SASL 认证流程**: ```mermaid sequenceDiagram 客户端->>Broker: SASL_HANDSHAKE 请求 Broker-->>客户端: 支持的认证机制 客户端->>Broker: SASL_AUTH 请求(凭据) Broker-->>客户端: 认证结果 ``` 2. **SSL/TLS 加密**: ```java // Kafka SSL 配置示例 Properties props = new Properties(); props.put("security.protocol", "SSL"); props.put("ssl.truststore.location", "/path/to/truststore.jks"); props.put("ssl.truststore.password", "password"); props.put("ssl.keystore.location", "/path/to/keystore.jks"); props.put("ssl.keystore.password", "password"); props.put("ssl.key.password", "key-password"); ``` ## 八、协议性能优化 1. **请求合并**:将多个小请求合并为单个大请求 2. **响应压缩**:使用 LZ4 压缩响应数据 3. **直接缓冲区**:使用堆外内存减少 JVM GC 压力 4. **连接复用**:保持长连接减少 TCP 握手开销
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值