摘要:RequestHandler 从 requestQueue 取到 Request 之后,到底怎么处理?Request 对象内部长什么样?为什么要用策略模式按 apiKey 路由?Produce 请求从进来到返回 Response 经历了哪些步骤?这篇把 Request 的完整处理机制讲透。
关键词:Kafka Request、RequestHandler、策略模式、Produce流程、Fetch流程、Response
前情:Request 到底是什么
上一篇讲了 RequestHandler 从 requestQueue take() 出一个 Request。但这个 Request 对象内部长什么样?为什么不能直接拿 ByteBuffer 去处理?
因为 ByteBuffer 是原始字节,业务层没法操作——你不知道这是 Produce 还是 Fetch,不知道消息有多大。所以 Kafka 在 Processor 层就把字节组装成了 Java 对象。
01 Request 对象结构
一个完整的 Request 对象包含四部分:
publicclassRequest{privatefinalRequestHeaderheader;// apiKey/version/correlationId/clientIdprivatefinalRequestContextcontext;// 连接信息/认证/时间privatefinalStructbody;// 按apiKey解析的请求体privatefinalByteBufferbuffer;// 原始字节(不复制)}RequestHeader:固定格式,所有请求都有——apiKey 决定类型,apiVersion 决定协议版本,correlationId 用于匹配请求和响应。
RequestContext:上下文信息——这个请求来自哪个连接、哪个监听器、认证状态是什么、什么时候进来的。
body:根据 apiKey 不同,结构完全不同。Produce 的 body 是消息批次,Fetch 的 body 是要读的分区列表,Metadata 的 body 是要查的 Topic 列表。
buffer:原始 ByteBuffer 引用,不复制——这是零拷贝思路,省内存。
02 策略模式路由
不同请求类型的处理逻辑完全不同,Kafka 用策略模式按 apiKey 路由:
// 路由表Map<ApiKeys,RequestHandler>handlers=newEnumMap<>(ApiKeys.class);// 启动时注册handlers.put(ApiKeys.PRODUCE,newProduceRequestHandler());handlers.put(ApiKeys.FETCH,newFetchRequestHandler());handlers.put(ApiKeys.METADATA,newMetadataRequestHandler());handlers.put(ApiKeys.OFFSET_COMMIT,newOffsetCommitRequestHandler());// 运行时路由RequestHandlerhandler=handlers.get(request.header.apiKey());Responseresponse=handler.handle(request);每个 Handler 只关心自己的业务逻辑:
- ProduceRequestHandler:把消息写进日志,等副本同步
- FetchRequestHandler:从日志读消息,可能阻塞等待
- MetadataRequestHandler:查元数据缓存直接返回
- OffsetCommitRequestHandler:写消费位点到内部 Topic
为什么要这样设计?因为新增请求类型只要加一个 Handler 类+注册到路由表,不用改现有代码——开闭原则。如果用一个大 if-else 写在一起,每加一种请求就要改一大坨代码。
03 Produce 请求处理全流程
以最典型的 Produce 请求为例,完整处理流程六步:
// ProduceRequestHandler.handle()publicResponsehandle(Requestrequest){// 1. 校验:权限、参数、消息大小// 2. 写入 Leader 分区的日志LogAppendInfoinfo=log.append(records);// 3. 根据 acks 配置等待副本同步if(acks==ALL){replicaFutures.await();// 等所有ISR同步}// 4. 组装 ResponsereturnnewProduceResponse(partitionResults);}acks 配置决定等待策略:
acks=0:写完就返回,不等任何副本acks=1:等 Leader 写成功就返回acks=all:等所有 ISR 副本都写成功才返回
这就是可靠性和性能的权衡——acks 越高越可靠,但延迟越大。
04 Produce vs Fetch:写路径 vs 读路径
Produce 和 Fetch 是 Kafka 最核心的两种请求,处理路径完全不同:
Produce(写路径):消息追加到 LogSegment,可能阻塞等副本同步。acks 配置决定等待多久。
Fetch(读路径):从 LogSegment 读消息。拉不到数据会阻塞等待——消费者拉取时如果没新数据,Fetch 不会立即返回,而是等到有数据或者 max.wait.ms 超时才返回。这就是为什么 Fetch 是长轮询。
两种路径都是 IO 密集型,所以 RequestHandler 线程数建议设为 CPU 核数的 2 倍左右。
05 Response 怎么回给客户端
处理完之后,Response 放入 responseQueue:
// Response 包含:响应头 + 响应体 + 要写回的连接信息Responseresponse=newResponse(request.context(),responseBody);requestChannel.sendResponse(response);Processor 线程从 responseQueue 取出 Response,序列化到 ByteBuffer,写回对应的 SocketChannel。客户端收到响应。
从请求进来到响应回去,整个链路就是:
Processor读字节 → 解析成Request → requestQueue → RequestHandler处理 → responseQueue → Processor写字节 → 客户端写在最后
Request 的处理本质就是:组装对象 → 策略路由 → 执行业务 → 返回 Response。Kafka 用策略模式把不同请求类型隔离开,每个 Handler 只管自己的逻辑,这就是为什么 Kafka 能支持几十种请求类型而代码不屎山。