Kafka请求进门口:Processor是怎么把请求读上来的

摘要:上一篇讲 Processor 怎么启动、怎么把新连接注册到 Selector。但连接注册完只是第一步------真正的问题是:Producer 发过来的字节,Processor 是怎么读上来、怎么解析成一个完整 Request 的?这篇从 poll 到入队,把请求接收全链路讲透。

关键词:Kafka Processor、OP_READ、ByteBuffer、粘包拆包、RequestHeader、二进制协议

前情:连接注册了,然后呢

上一篇讲了 configureNewConnections() 把 Acceptor 塞进来的 SocketChannel 注册到 Selector 的 OP_READ。但注册完只是"监听"------真正的字节还在网络上。

Processor 的核心循环里有一步 selector.poll(),这个调用会阻塞直到有连接可读。一旦 poll 返回,Processor 就开始读字节。

01 完整接收链路

整个过程分六步:

java 复制代码
// Processor.run() 中 poll 之后
selector.poll(pollTimeoutMs);

// 遍历所有就绪的连接
for (KChannel channel : selector.completedReceives()) {
    // 1. 从 SocketChannel 读到 ByteBuffer
    // 2. 解析 RequestHeader
    // 3. 按 apiKey 解析 RequestBody
    // 4. 组装 Request 对象
    // 5. 送入 RequestChannel
    Request request = buildRequest(channel);
    requestChannel.sendRequest(request);
}

关键:读字节和解析是在同一个 Processor 线程里做的。如果一次 poll 读到半包,ByteBuffer 保留着,等下次 OP_READ 继续往里塞。

02 Kafka 的二进制协议

Kafka 不是 HTTP,是自定义的二进制协议。每条请求的格式是:

复制代码
[4字节length][apiKey:2][apiVersion:2][correlationId:4][clientId:变长][RequestBody:变长]

为什么先读 length?因为 TCP 是流式协议,没有消息边界。4 字节 length 告诉 Processor 这条消息总共多少字节。不够就等下次读,够了才开始解析。这和 HTTP 的 Content-Length 思路一样。

解析代码大致是这样:

java 复制代码
// 从 ByteBuffer 解析
int length = buffer.getInt();       // 消息总长度
short apiKey = buffer.getShort();   // 请求类型
short version = buffer.getShort();  // 版本号
int correlationId = buffer.getInt(); // 关联ID
String clientId = readString(buffer); // 客户端ID

// 根据 apiKey 找到对应的 Schema
ApiKeys api = ApiKeys.forId(apiKey);
Struct requestBody = api.requestSchema(version).read(buffer);

// 组装完整 Request
RequestHeader header = new RequestHeader(apiKey, version, correlationId, clientId);
Request request = Request.parseRequest(header, requestBody);

apiKey 是请求类型的编号:Produce=0、Fetch=1、Metadata=3、JoinGroup=11......每个 apiKey 对应一个请求处理器。

03 ByteBuffer 怎么处理粘包拆包

TCP 是流,没有消息边界。Kafka 的处理方式很经典------length 前缀 + ByteBuffer 累积。

三种情况:

完整消息:一次 poll 读够了 4+N 字节,直接解析入队,清空 buffer。

半包(拆包):只读了 length 的前 2 字节,或 length 够了但 body 只到一半。ByteBuffer 不清空,保留现有数据,等下次 OP_READ 继续往里读。读够了再解析。

粘包:一次读到了 2 条完整消息。解析第一条入队,buffer 里剩下的第二条继续处理。循环直到 buffer 不够一条完整消息为止。

这个设计的核心是:ByteBuffer 是有状态的------它记录了当前读到哪了。Processor 不需要每条消息都重新读,只需要维护好 position 和 limit。

04 为什么不在业务线程里读

有人可能会问:为什么读字节和解析要放在 Processor 线程,不让业务线程池去做?

因为 NIO Selector 必须在同一个线程里操作。如果业务线程去读 SocketChannel,Selector 的状态就乱了。所以 Processor 负责 IO(读字节、解析),业务线程负责逻辑处理(写磁盘、复制副本)。这是典型的"IO 和业务分离"模型。

数据从网络到业务线程的完整路径:

  1. Processor 读字节 → 解析成 Request
  2. Request 塞进 RequestChannel 的队列
  3. RequestHandler 线程池从队列取 Request
  4. 执行业务逻辑(写日志、处理请求)
  5. Response 塞进 Processor 的响应队列
  6. Processor 把 Response 写回 Channel

写在最后

Processor 接收请求的核心就两件事:读字节(处理半包粘包)、解析协议(length→header→body→Request)。理解了这两步,就理解了 Kafka 网络层怎么从字节流变成业务对象。

相关推荐
骑着蜗牛撵大象32721 小时前
558 万美元训出 671B 模型:DeepSeek-V3 算法与系统的协同拆解
分布式·算法·moe·deepseek-v3·mla·fp8训练
德迅云安全-上官1 天前
德迅游戏盾:面向 TCP 业务的分布式云安全防护方案
分布式·tcp/ip·游戏
麻瓜code1 天前
分布式锁演进:从一个用户下两单,到 Redisson,我改了四版
分布式·wpf
starzy19901 天前
Kafka网络层第一道门:Acceptor线程是怎么启动的
大数据·kafka
skywalk81631 天前
hive —— 多机 AI Agent 协同的任务总线:分布式,多节点同时运行,保障不会宕机
人工智能·hive·分布式
huaweichenai1 天前
spring boot对接kafka实现消息队列处理
spring boot·后端·kafka
你看我名字1 天前
Kafka 架构、消息可靠性与生产运维:从分区模型到故障治理
运维·架构·kafka
一条泥憨鱼1 天前
【RabbitMQ】高级篇
java·分布式·rabbitmq·php·ruby
棣廷1 天前
初识Spark Core
大数据·分布式·spark