Server端Grpc调用Client(Agent应用)
当 Server 通过 gRPC Streaming 将 /chat/dispatch 请求推送到 Agent 的 1790 端口时,ClientGrpcServer 接收并交给 ClientRequestDispatcher.streamingHandler()。
整体理解:不是"推送",而是"Server 作为 gRPC 客户端主动调用 Agent"
这里的关键在于理解 gRPC Server Streaming 模式的语义:
Server(指 Server 端应用)作为 gRPC Client,主动向 Agent 的 gRPC Server 发起调用
Server Streaming 表示:Server 发一个请求,Agent 可以返回多个响应(流式)
所以 "推送" 实际上是 Server 作为 gRPC 客户端,调用 Agent gRPC 服务端的 Server Streaming 接口
第一步:Agent 启动时,Server 预先建立 gRPC Channel
Agent 启动后通过心跳注册到 Server。在 ClientInstanceManager.registerOrUpdate() 中:
public void registerOrUpdate(ClientRegistration registration) {
liveInstances.compute(key, (k, existing) -> {
if (existing == null) {
// 新客户端注册:创建 ClientInstanceInfo
ClientInstanceInfo info = new ClientInstanceInfo(
registration.getAppId(), registration.getHostId(),
registration.getHostIp(), registration.getGrpcPort());
// ★ 关键:创建到 Agent gRPC 端口的 ManagedChannel
info.setChannel(createChannel(info.getHostIp(), info.getGrpcPort()));
return info;
}
// 已有实例:确保 channel 存活
ensureChannelAlive(existing);
return existing;
});
}
createChannel() 方法创建一个到 Agent 的 gRPC 通道:
private ManagedChannel createChannel(String hostIp, int grpcPort) {
return NettyChannelBuilder.forAddress(hostIp, grpcPort) // Agent 的 IP + 端口(如 1790)
.usePlaintext()
.keepAliveTime(30, TimeUnit.SECONDS)
.keepAliveTimeout(10, TimeUnit.SECONDS)
.idleTimeout(5, TimeUnit.MINUTES)
.maxInboundMessageSize(10 * 1024 * 1024)
.build();
}
这个 ManagedChannel 被保存在 ClientInstanceInfo.channel 字段中,是一个长连接,后续所有对该 Agent 的 gRPC 调用都复用这个 channel。
第二步:ContextCollectorHandler 选择目标 Agent 实例
ContextCollectorHandler.handle() 在责任链 Order(75) 执行:
public void handle(AgentChatContext ctx) {
String appId = agent.getAppId();
// 获取该 appId 下所有存活的 Agent 实例
List<ClientInstanceInfo> candidates = instanceManager.getAliveInstances(appId);
// 按路由策略选择一个(默认"最小负载"策略)
ClientRouteStrategy routeStrategy = routeStrategyManager.get(routeKey);
ClientInstanceInfo target = routeStrategy.select(candidates, ctx.getConversationId());
// ★ 存入上下文,后续 LlmCallHandler 使用
ctx.setTargetClient(target);
}
此时 target 对象里就包含了之前心跳注册时创建的 ManagedChannel。
第三步:LlmCallHandler 通过 Channel 发起 gRPC Server Streaming 调用
LlmCallHandler.handle() 在责任链 Order(80) 执行,这是实际发起 gRPC 调用的地方:
public void handle(AgentChatContext ctx) {
// 1. 组装请求体(包含 Agent 配置、模型配置、历史消息、MCP、Skill 等全部上下文)
ChatDispatchRequest request = buildDispatchRequest(ctx);
String dispatchBody = JsonUtil.toJsonString(request);
// 2. ★ 关键调用:通过 target client 的 channel 发起 Server Streaming 调用
GrpcChannelUtil.sendServerStreaming(
ctx.getTargetClient().getChannel(), // ← 第一步建立的到 Agent 1790 端口的 channel
UriConstants.CHAT_DISPATCH, // ← URI: "/chat/dispatch"
dispatchBody, // ← 请求体 JSON
Map.of(), // ← 空 headers
new ChatStreamObserver(ctx, ...)); // ← 流式响应观察者
}
第四步:GrpcChannelUtil 底层如何发送
GrpcChannelUtil.sendServerStreaming() 的实现:
public static void sendServerStreaming(ManagedChannel channel, String uri, String body,
Map<String, String> headers,
StreamObserver<GrpcSnailAiResult> responseObserver) {
// 1. 构建 Protobuf 请求
GrpcSnailAiRequest request = buildRequest(uri, body, headers);
// request.metadata.uri = "/chat/dispatch"
// request.body = "{...ChatDispatchRequest JSON...}"
// request.reqId = 自增 ID
// 2. 构建 MethodDescriptor(描述 gRPC 方法元数据)
MethodDescriptor<GrpcSnailAiRequest, GrpcSnailAiResult> md =
buildMethodDescriptor(
MethodDescriptor.MethodType.SERVER_STREAMING, // ★ Server Streaming 模式
"ServerStreamingRequest", // 服务名
"serverStreamingRequest"); // 方法名
// 最终 fullMethodName = "ServerStreamingRequest/serverStreamingRequest"
// 3. ★ 发起异步 Server Streaming 调用
ClientCalls.asyncServerStreamingCall(
channel.newCall(md, CallOptions.DEFAULT), // 通过 channel 创建 call
request, // 请求消息
responseObserver); // 回调观察者
}
这里就是"推送"的本质:ClientCalls.asyncServerStreamingCall() 是 gRPC Java 标准 API,它通过已有的 ManagedChannel(TCP 长连接到 Agent 的 1790 端口)发送一个 HTTP/2 请求,帧类型为 SERVER_STREAMING,告诉对端:"我要调用你的 ServerStreamingRequest/serverStreamingRequest 方法,URI 是 /chat/dispatch,请流式返回结果"。
第五步:Agent 端 ClientGrpcServer 接收请求
Agent 启动时 ClientGrpcServer.start() 注册了 Streaming 服务定义:
public void start() throws Exception {
server = NettyServerBuilder.forPort(port) // 1790 端口
.addService(GrpcServiceDefinitionBuilder.createUnaryServiceDefinition(unaryHandler))
.addService(GrpcServiceDefinitionBuilder.createServerStreamingServiceDefinition(streamingHandler))
// ↑ 注册 Server Streaming 服务
.build().start();
}
createServerStreamingServiceDefinition() 注册的方法全名恰好是 "ServerStreamingRequest/serverStreamingRequest",与 Server 端调用的完全匹配:
MethodDescriptor.MethodType.SERVER_STREAMING,
"ServerStreamingRequest", // 服务名
"serverStreamingRequest" // 方法名
当 gRPC 请求到达 1790 端口时,Netty 根据方法名路由到 streamingHandler,即 ClientRequestDispatcher.streamingHandler():
public ServerCalls.ServerStreamingMethod<...> streamingHandler() {
return (request, observer) -> {
String uri = request.getMetadata().getUri(); // "/chat/dispatch"
grpcRequestDispatcher.dispatchStreaming( // 按 URI 路由
uri, headers, request.getBody(), request.getReqId(), observer);
};
}
当 gRPC 请求到达 1790 端口时,Netty 根据方法名路由到 streamingHandler,即 ClientRequestDispatcher.streamingHandler():
public ServerCalls.ServerStreamingMethod<...> streamingHandler() {
return (request, observer) -> {
String uri = request.getMetadata().getUri(); // "/chat/dispatch"
grpcRequestDispatcher.dispatchStreaming( // 按 URI 路由
uri, headers, request.getBody(), request.getReqId(), observer);
};
}
第六步:流式响应回传
Agent 处理过程中,每个 LLM 输出的文本 chunk 通过 observer.onNext() 沿同一条 gRPC 流回传。Server 端的 ChatStreamObserver 接收:
public void onNext(GrpcSnailAiResult result) {
ChatStreamResponse data = JsonUtil.parseObject(result.getData(), ChatStreamResponse.class);
switch (data.getType()) {
case TYPE_TEXT -> handleText(data); // 转发给前端 SSE
case TYPE_THINKING -> handleThinking(data); // 转发思考过程
case TYPE_COMPLETION-> handleCompletion(data); // 持久化 + 关闭流
case TYPE_ERROR -> handleError(data); // 转发错误
}
}
总结:完整调用链路图
┌─ Server (端口 8900) ──────────────────────────────────────────────┐
│ │
│ LlmCallHandler.handle() │
│ │ │
│ ├─ buildDispatchRequest() → ChatDispatchRequest JSON │
│ │ │
│ └─ GrpcChannelUtil.sendServerStreaming( │
│ targetClient.getChannel(), ←── 心跳时建立的 TCP 长连接 │
│ "/chat/dispatch", │
│ dispatchBody, │
│ new ChatStreamObserver(...)) ←── 异步回调观察者 │
│ │ │
│ │ ClientCalls.asyncServerStreamingCall() │
│ │ HTTP/2 帧: ServerStreamingRequest/serverStreamingRequest │
│ │ │
└───────────────┼────────────────────────────────────────────────────┘
│ TCP (Netty, HTTP/2)
▼
┌─ Agent (端口 1790) ───────────────────────────────────────────────┐
│ │
│ ClientGrpcServer (NettyServerBuilder) │
│ │ 匹配方法名 "ServerStreamingRequest/serverStreamingRequest" │
│ ▼ │
│ ClientRequestDispatcher.streamingHandler() │
│ │ 提取 URI = "/chat/dispatch" │
│ ▼ │
│ GrpcRequestDispatcher.dispatchStreaming() │
│ │ 遍历 streamingHandlers, 匹配 supports("/chat/dispatch") │
│ ▼ │
│ ChatDispatchStreamingHandler.handle() │
│ │ 解析 ChatDispatchRequest │
│ ▼ │
│ ChatSessionRuntime.execute() → ClientChatExecutor → LLM 调用 │
│ │ │
│ └─ 每个 chunk → observer.onNext() ──────────── 流式回传 ──→ Server │
│ │
└────────────────────────────────────────────────────────────────────┘
核心要点:"推送"本质上是 Server 作为 gRPC 客户端,通过心跳时预先建立的 ManagedChannel(TCP 长连接),使用 ClientCalls.asyncServerStreamingCall() 异步调用 Agent 的 Server Streaming 接口。这不是传统意义上的"推送"(如 WebSocket),而是标准的 gRPC 请求-流式响应模式。

浙公网安备 33010602011771号