《Java 服务端通信工程》附录 A 通信 API 速查

按 gRPC 核心、Metadata 与 Status、Protobuf、JDK HttpClient、HTTP server、并发与时间六组,列出卷三用到的类、方法签名、用途与对应章节,只收白名单 API,并标出容易记错的重载与调用顺序,供写 linkd-v3 时直接查表。

这张表怎么用

这不是教程,是把正文里出现过的类和方法压成一张可查的表。写 linkd-v3 时先在这里定位类,再回到对应章节看为什么这样用。

约定:

  • 版本固定为 JDK 25、gRPC-Java 1.76.0、Protobuf 4.33.2。方法签名省略返回值类型,只保留参数;需要返回值时看「用途」列。
  • 「包」列只写类所在的包,import 时补全类名。gRPC 的类集中在 io.grpc 与其子包,Protobuf 在 com.google.protobuf 与 com.google.rpc。
  • 「章节」指向正文节号,附录不重复解释机制。
  • 表中 ... 表示可变参数或省略的重载,不是可以照抄的省略号。
  • 只列白名单内的 API;不在表里的类,先确认版本再使用。

包名先记四个容易混的:

类正确包常见错写
ManagedChannelio.grpcio.grpc.netty
StreamObserverio.grpc.stubio.grpc
JsonFormatcom.google.protobuf.utilcom.google.protobuf
ErrorInfocom.google.rpccom.google.protobuf

ManagedChannel 是接口,不是 Netty 的类;StreamObserver 在 io.grpc.stub 下,import 写错是最常见的编译错误之一。

gRPC 核心:Channel、Server 与 Stub

Channel 是客户端的长生命周期对象,Server 是服务端的长生命周期对象,Stub 是每次调用前的轻量视图。三者都不要在请求里反复创建。

类包成员用途章节
ManagedChannelBuilderio.grpcforAddress(String host, int port)按地址创建 builder4.1
ManagedChannelBuilderio.grpcforTarget(String target)按 URI 目标创建,配合 NameResolver5.2
ManagedChannelBuilderio.grpcusePlaintext()关闭 TLS,仅测试与内网明文10.1
ManagedChannelBuilderio.grpcmaxInboundMessageSize(int)设置入站消息上限,默认 4 MiB4.3
ManagedChannelBuilderio.grpcbuild()构建 channel,不发起连接4.1
ManagedChannelio.grpcshutdown()停止新调用,等待在途完成6.3
ManagedChannelio.grpcshutdownNow()立即取消在途调用6.3
ManagedChannelio.grpcawaitTermination(long, TimeUnit)等待关闭完成6.3
ManagedChannelio.grpcgetState(boolean requestConnection)读连接状态,true 会触发连接5.3
ServerBuilderio.grpcforPort(int port)创建服务端 builder,0 表示随机端口4.1
ServerBuilderio.grpcaddService(BindableService)注册服务实现4.1
ServerBuilderio.grpcintercept(ServerInterceptor...)注册服务端拦截器,按顺序执行4.2
ServerBuilderio.grpcmaxInboundMessageSize(int)服务端入站上限4.3
ServerBuilderio.grpcbuild() / start()构建并启动,start 后才监听4.1
Serverio.grpcgetPort()实际监听端口,随机端口时使用15.2
Serverio.grpcshutdown() / shutdownNow()优雅 / 强制关闭15.3
Serverio.grpcawaitTermination(long, TimeUnit)等待排空15.3
Serverio.grpcisShutdown() / isTerminated()判断关闭阶段15.3
AbstractStubio.grpc.stubwithDeadlineAfter(long, TimeUnit)为单次调用设 deadline3.2
AbstractStubio.grpc.stubwithDeadline(Deadline)用绝对 deadline 覆盖3.2
AbstractStubio.grpc.stubwithInterceptors(ClientInterceptor...)为 stub 附加拦截器4.2
ClientCallsio.grpc.stubblockingUnaryCall(Channel, MethodDescriptor, CallOptions, ReqT)手写阻塞一元调用3.1
ClientCallsio.grpc.stubasyncUnaryCall(...)手写异步一元调用3.1
ServerCallsio.grpc.stubasyncUnaryCall(UnaryMethod, StreamObserver)手写服务端一元适配3.1

ManagedChannelBuilder.build() 不建立连接,第一次调用或 getState(true) 才会。启动自检如果只 build 不调用,看到的「成功」没有意义。

调用、拦截器与流式

拦截器分服务端和客户端两套,方法签名对称但泛型位置不同。Forwarding* 是包装调用的入口,不要直接实现 ServerCall 的全部方法。

类包成员用途章节
ServerInterceptorio.grpcinterceptCall(ServerCall, Metadata, ServerCallHandler)服务端拦截入口4.2
ClientInterceptorio.grpcinterceptCall(MethodDescriptor, CallOptions, Channel)客户端拦截入口4.2
ServerCallio.grpcgetMethodDescriptor()取方法元数据4.2
ServerCallio.grpcsendHeaders(Metadata)发送响应头4.2
ServerCallio.grpcsendMessage(RespT)发送一条响应消息3.1
ServerCallio.grpcclose(Status, Metadata)关闭调用,trailers 随状态返回1.2
ServerCallio.grpcisCancelled()客户端是否已取消3.2
ServerCallHandlerio.grpcstartCall(ServerCall, Metadata)启动业务处理,返回 listener4.2
ForwardingServerCall.SimpleForwardingServerCallio.grpc(ServerCall)包装服务端调用13.2
ForwardingClientCall.SimpleForwardingClientCallio.grpc(ClientCall)包装客户端调用13.2
ClientCallio.grpcstart(Listener, Metadata)发起调用并写请求头4.2
ClientCallio.grpcsendMessage(ReqT) / halfClose()发送请求、关闭写方向3.1
ClientCallio.grpccancel(String, Throwable)主动取消3.2
StreamObserverio.grpc.stubonNext(T) / onError(Throwable) / onCompleted()流式响应三件套3.1
ServerCallStreamObserverio.grpc.stubsetOnCancelHandler(Runnable)客户端取消回调3.2
ServerCallStreamObserverio.grpc.stubsetOnReadyHandler(Runnable)可写回调,背压用8.3
ServerCallStreamObserverio.grpc.stubisCancelled() / isReady()取消与可写状态8.3
ClientCallStreamObserverio.grpc.stubcancel(String, Throwable)客户端流取消3.2
ClientCallStreamObserverio.grpc.stubisReady() / setOnReadyHandler(Runnable)客户端背压8.3

ServerCall.close 只能调用一次,重复调用会抛 IllegalStateException。在拦截器里提前关闭后,不要再把调用交给 delegate;13.2 的 abort 就是直接返回空 listener。

Metadata、Deadline 与 Context

Metadata 是 ASCII 键值对加二进制值的容器,key 必须通过 Metadata.Key.of 创建,不能直接用字符串。Deadline 是绝对时间点,Context 是调用范围内的隐式状态。

类包成员用途章节
Metadataio.grpcKey.of(String, BinaryMarshaller)创建 key,ASCII 或二进制4.2
Metadataio.grpcASCII_STRING_MARSHALLER字符串值的 marshaller4.2
Metadataio.grpcBINARY_BYTE_MARSHALLER二进制值的 marshaller4.2
Metadataio.grpcput(Key, T) / get(Key)写入 / 读取,重复键用 getAll4.2
Metadataio.grpckeys() / containsKey(Key)遍历与判断4.2
Metadataio.grpcmerge(Metadata)合并两组元数据9.1
Deadlineio.grpcafter(long, TimeUnit)相对当前时间创建3.2
Deadlineio.grpctimeRemaining(TimeUnit)剩余时间,可为负3.2
Deadlineio.grpcisExpired()是否已过期3.2
Contextio.grpccurrent()当前调用上下文3.2
Contextio.grpccurrent().getDeadline()取传播过来的 deadline3.2
Contextio.grpccurrent().isCancelled()是否已取消3.2
Contextio.grpcwithDeadline(Deadline, ScheduledExecutorService)派生带 deadline 的上下文3.2
Contextio.grpcattach() / detach(Context)手动挂载与恢复3.2
CallOptionsio.grpcwithDeadline(Deadline)在调用选项上设 deadline3.2
CallOptionsio.grpcwithDeadlineAfter(long, TimeUnit)相对 deadline3.2
CallOptionsio.grpcgetDeadline()读当前 deadline3.2

Deadline.timeRemaining 可能返回负数,表示已经过期;不要直接用返回值算 sleep,先判断 isExpired()。Context.attach() 必须与 detach() 配对,否则线程复用时上下文会串。

Status 与错误详情

Status 是 gRPC 的错误模型。StatusRuntimeException 是同步调用抛出的形式,StatusException 是异步/流式常用的受检形式。错误原因码放在 ErrorInfo 里,通过 StatusProto 与 Status 互转。

类包成员用途章节
Statusio.grpcfromCode(Status.Code)由枚举创建1.2
Statusio.grpcwithDescription(String)附一句人类可读描述1.2
Statusio.grpcwithCause(Throwable)附根因,不传给对端1.2
Statusio.grpcasRuntimeException()转成可抛出的异常1.2
Statusio.grpcasException()转成受检异常3.1
Status.Codeio.grpcOK / NOT_FOUND / UNAVAILABLE / DEADLINE_EXCEEDED常用状态码1.2
Status.Codeio.grpcRESOURCE_EXHAUSTED / ABORTED / PERMISSION_DENIED限流、冲突、鉴权1.2
StatusRuntimeExceptionio.grpcgetStatus() / getTrailers()取状态与 trailing metadata1.2
StatusExceptionio.grpcgetStatus()受检异常取状态3.1
com.google.rpc.Statuscom.google.rpcnewBuilder().setCode(int).setMessage(String)构造带详情的状态1.2
com.google.rpc.Statuscom.google.rpcaddDetails(Any)附加 ErrorInfo、RetryInfo1.2
ErrorInfocom.google.rpcnewBuilder().setReason(String).setDomain(String)原因码与域1.2
ErrorInfocom.google.rpcputMetadata(String, String)附加上下文,如 isbn1.2
RetryInfocom.google.rpcnewBuilder().setRetryDelay(Duration)告诉客户端多久后重试7.1
StatusProtoio.grpc.protobuftoStatusRuntimeException(com.google.rpc.Status)带详情的状态转异常1.2
StatusProtoio.grpc.protobuffromThrowable(Throwable)从异常还原 com.google.rpc.Status9.3
ProtoUtilsio.grpc.protobufkeyForProto(Message)为 protobuf 消息创建 metadata key4.2

Status.withCause 只影响本地日志,不会把堆栈序列化给对端。要把原因码传给客户端,必须用 ErrorInfo 加 StatusProto。

Protobuf 与 JSON

Protobuf 的消息对象不可变,修改要通过 toBuilder()。JsonFormat 用于调试和 HTTP 网关的 JSON 转换,不适合作为 gRPC 的线格式。

类包成员用途章节
Messagecom.google.protobuftoByteArray() / getSerializedSize()序列化与大小2.1
Messagecom.google.protobuftoBuilder()基于现有消息构造 builder2.1
Messagecom.google.protobufgetDefaultInstanceForType()取默认实例2.1
ByteStringcom.google.protobufcopyFromUtf8(String) / copyFrom(byte[])构造字节串2.1
ByteStringcom.google.protobuftoByteArray() / toStringUtf8()转回字节或字符串2.1
Descriptors.Descriptorcom.google.protobufgetFields() / findFieldByName(String)反射读字段定义2.3
Descriptors.FieldDescriptorcom.google.protobufgetNumber() / getType()字段号与类型2.3
JsonFormat.Printercom.google.protobuf.utilprinter().print(Message)消息转 JSON2.2
JsonFormat.Parsercom.google.protobuf.utilparser().merge(String, Builder)JSON 转消息2.2
JsonFormatcom.google.protobuf.utilprinter().includingDefaultValueFields()输出默认值字段2.2
JsonFormatcom.google.protobuf.utilparser().ignoringUnknownFields()忽略未知字段,兼容演进2.3

JsonFormat 默认不输出默认值字段,网关如果直接透传,客户端可能看不到 stock: 0。需要显式开启 includingDefaultValueFields,并注意这与 proto3 的「默认值不序列化」语义不同。

JDK HttpClient

网关调用 libraryd 用 JDK 自带客户端,不引入额外 HTTP 库。HttpClient 是线程安全的,应该复用;HttpRequest 是每次请求构造的不可变对象。

类包成员用途章节
HttpClientjava.net.httpnewBuilder().connectTimeout(Duration)连接超时9.1
HttpClientjava.net.httpnewBuilder().version(HttpClient.Version.HTTP_2)指定协议版本9.1
HttpClientjava.net.httpnewBuilder().executor(Executor)指定异步执行器9.1
HttpClientjava.net.httpsend(HttpRequest, BodyHandler)同步发送,阻塞当前线程9.1
HttpClientjava.net.httpsendAsync(HttpRequest, BodyHandler)异步发送,返回 CompletableFuture9.1
HttpRequestjava.net.httpnewBuilder(URI)构造请求9.1
HttpRequestjava.net.httptimeout(Duration)单请求超时9.1
HttpRequestjava.net.httpheader(String, String)添加请求头9.1
HttpRequestjava.net.httpGET() / POST(BodyPublisher)指定方法与 body9.1
HttpRequest.BodyPublishersjava.net.httpofString(String) / ofByteArray(byte[])构造请求体9.2
HttpResponsejava.net.httpstatusCode() / body()状态码与响应体9.3
HttpResponsejava.net.httpheaders().firstValue(String)读响应头,返回 Optional9.3
HttpResponse.BodyHandlersjava.net.httpofString() / ofByteArray() / discarding()响应体处理器9.1

HttpClient.send 会阻塞调用线程。在 gRPC 服务端直接调用它,会占住服务线程;要么用 sendAsync,要么把阻塞调用移到专用执行器。connectTimeout 和 HttpRequest.timeout 是两个不同的超时,前者管建连,后者管整个请求。

HTTP server 与并发

com.sun.net.httpserver.HttpServer 适合写测试替身和轻量网关,不适合高并发生产服务。并发工具用来实现限流、Bulkhead 和延迟统计。

类包成员用途章节
HttpServercom.sun.net.httpservercreate(InetSocketAddress, int backlog)创建服务器9.1
HttpServercom.sun.net.httpservercreateContext(String, HttpHandler)注册路径9.1
HttpServercom.sun.net.httpserversetExecutor(Executor)设置处理线程池9.1
HttpServercom.sun.net.httpserverstart() / stop(int delaySeconds)启动 / 停止9.1
HttpExchangecom.sun.net.httpservergetRequestMethod() / getRequestURI()方法与路径9.1
HttpExchangecom.sun.net.httpservergetRequestBody() / getResponseBody()请求与响应流9.1
HttpExchangecom.sun.net.httpserversendResponseHeaders(int, long)状态码与长度,-1 表示未知9.1
HttpHandlercom.sun.net.httpserverhandle(HttpExchange)处理入口9.1
ExecutorServicejava.util.concurrentsubmit(Callable) / shutdown()任务提交与关闭8.2
Executorsjava.util.concurrentnewFixedThreadPool(int)固定线程池8.2
Executorsjava.util.concurrentnewVirtualThreadPerTaskExecutor()每任务一个虚拟线程7.3
Semaphorejava.util.concurrentacquire() / tryAcquire(long, TimeUnit) / release()Bulkhead 并发许可7.3
CountDownLatchjava.util.concurrentcountDown() / await()等待一批任务完成14.2
CompletableFuturejava.util.concurrentthenApply / whenComplete / join异步编排9.2
LongAdderjava.util.concurrent.atomicincrement() / sum()高并发计数11.1
AtomicLongjava.util.concurrent.atomicincrementAndGet() / get()序号与计数11.1
ConcurrentHashMapjava.util.concurrentcomputeIfAbsent / merge并发缓存与注册表5.1
Durationjava.timeofMillis(long) / toSeconds()时间间隔3.2
Instantjava.timenow() / toEpochMilli()时间点与时间戳11.2
Clockjava.timesystemUTC()可替换时钟,便于测试3.2

HttpServer.stop(delay) 的 delay 是秒,不是毫秒;传 0 会立即关闭,可能截断在途响应。Executors.newVirtualThreadPerTaskExecutor() 适合阻塞调用多的场景,但不适合 CPU 密集任务,也不解决下游容量问题。

在 linkd-v3 里落地

一次带元数据、deadline 和错误映射的一元调用:

package com.plume.linkd.v3.gateway;

import com.plume.linkd.v3.api.Book;
import com.plume.linkd.v3.api.GetBookRequest;
import com.plume.linkd.v3.api.LibraryGrpc;
import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
import io.grpc.ClientInterceptor;
import io.grpc.ForwardingClientCall;
import io.grpc.ManagedChannel;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;

import java.util.concurrent.TimeUnit;

public final class UnaryCallExample {

    private static final Metadata.Key<String> REQUEST_ID =
            Metadata.Key.of("x-request-id", Metadata.ASCII_STRING_MARSHALLER);

    public static Book fetch(ManagedChannel channel, String isbn, String requestId) {
        ClientInterceptor attach = new ClientInterceptor() {
            @Override
            public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
                    MethodDescriptor<ReqT, RespT> method, CallOptions options, Channel next) {
                return new ForwardingClientCall.SimpleForwardingClientCall<>(
                        next.newCall(method, options)) {
                    @Override
                    public void start(Listener<RespT> listener, Metadata headers) {
                        headers.put(REQUEST_ID, requestId);
                        super.start(listener, headers);
                    }
                };
            }
        };
        LibraryGrpc.LibraryBlockingStub stub = LibraryGrpc.newBlockingStub(channel)
                .withInterceptors(attach)
                .withDeadlineAfter(300, TimeUnit.MILLISECONDS);
        return stub.getBook(GetBookRequest.newBuilder().setIsbn(isbn).build());
    }
}

带 ErrorInfo 的错误构造与解析:

package com.plume.linkd.v3.core;

import com.google.protobuf.Any;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.rpc.ErrorInfo;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import io.grpc.protobuf.StatusProto;

public final class RpcErrors {

    public static StatusRuntimeException bookNotFound(String isbn) {
        ErrorInfo info = ErrorInfo.newBuilder()
                .setReason("BOOK_NOT_FOUND")
                .setDomain("library")
                .putMetadata("isbn", isbn)
                .build();
        com.google.rpc.Status status = com.google.rpc.Status.newBuilder()
                .setCode(Status.Code.NOT_FOUND.value())
                .setMessage("book not found")
                .addDetails(Any.pack(info))
                .build();
        return StatusProto.toStatusRuntimeException(status);
    }

    public static String reasonOf(Throwable error) {
        com.google.rpc.Status status = StatusProto.fromThrowable(error);
        if (status == null) {
            return "UNKNOWN";
        }
        for (Any detail : status.getDetailsList()) {
            if (detail.is(ErrorInfo.class)) {
                try {
                    return detail.unpack(ErrorInfo.class).getReason();
                } catch (InvalidProtocolBufferException e) {
                    return "MALFORMED_ERROR_INFO";
                }
            }
        }
        return "UNKNOWN";
    }
}

Any.is(Class) 与 Any.unpack(Class) 是 Protobuf 的标准方法;原因码放在 ErrorInfo 里,而不是拼在 Status.description 字符串里,客户端才能稳定解析。

常见错误与边界

错误一:ManagedChannel 每次请求新建。 连接、TLS 握手、NameResolver 全部重来,延迟和 fd 都会失控。Channel 应该是进程级单例。

错误二:Server.shutdownNow() 当默认关闭。 在途请求被取消,客户端看到 UNAVAILABLE。默认用 shutdown() 加 awaitTermination。

错误三:Metadata key 用字符串直接构造。 Metadata.Key 必须通过 of 创建,直接 put("x-request-id", ...) 编译不过。

错误四:把 Status.description 当原因码。 描述是给人看的,可能变化;原因码要放 ErrorInfo.reason。

错误五:在服务线程里调用 HttpClient.send。 阻塞会占住 gRPC 执行线程,高并发时线程池迅速耗尽。用 sendAsync 或专用执行器。

错误六:HttpServer.stop(0)。 参数是秒,0 表示立即关闭,会截断在途响应。

边界:本表只覆盖卷三用到的 API。 gRPC 还有 NameResolver、LoadBalancer、Credentials 等接口,正文按需展开;不在表里的方法,使用前先查 1.76.0 的 javadoc。

边界:版本差异会改变默认值。 消息大小上限、压缩算法、keepalive 默认值都可能随版本变化;升级 gRPC 时重新核对本文的默认参数。

小结

  • Channel 与 Server 是长生命周期对象,Stub 是轻量视图;Channel 不要每次请求新建。
  • 拦截器用 Forwarding* 包装,ServerCall.close 只能调用一次。
  • Metadata key 必须用 Key.of 创建;Deadline 是绝对时间点,剩余时间可能为负。
  • 原因码放 ErrorInfo,用 StatusProto 与 Status 互转;description 不是稳定契约。
  • JsonFormat 默认不输出默认值字段,网关透传时要显式开启。
  • HttpClient 复用、sendAsync 优先;HttpServer.stop 的参数是秒。
  • 并发工具按场景选:Semaphore 做 Bulkhead、LongAdder 做计数、虚拟线程处理阻塞调用。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「books」更多文章

  1. 《Java 消息中间件·事件驱动架构》附录 B 架构决策记录模板
  2. 《Java 消息中间件·事件驱动架构》附录 A 术语表
  3. 《Java 消息中间件·事件驱动架构》15.3 全书验收