《Java 服务端通信工程》12.1 API 版本策略
围绕 linkd-v3 的 Library 服务,拆解 proto 包名、服务名、字段号三种版本载体,给出 v1/v2 并存、x-api-version 校验与只加不改不删的兼容矩阵,并附可编译的拦截器、网关路由代码与旧版本安全下线与观测口径。
tag
围绕 linkd-v3 的 Library 服务,拆解 proto 包名、服务名、字段号三种版本载体,给出 v1/v2 并存、x-api-version 校验与只加不改不删的兼容矩阵,并附可编译的拦截器、网关路由代码与旧版本安全下线与观测口径。
11.2 有了 trace_id,本节补上 span:定义客户端与服务端 span 的父子关系,实现头部采样与尾部采样两种策略,并把 trace_id 作为 exemplar 挂到延迟直方图上,同时说明这不是完整的 OpenTelemetry。
把单个请求串起来:按 W3C 规范解析与生成 traceparent,区分 request-id 与 trace_id,用 JDK 输出可检索的结构化 JSON 日志,并给出按 trace 一致采样的完整可编译实现、字段设计与日志脱敏边界。
用 JDK 自己实现一套 gRPC 指标:LongAdder 计数器、固定桶并发直方图、gRPC 四个黄金信号,以及手写 renderPrometheus() 的 # HELP、# TYPE、le 标签格式;讲清标签基数风险和 9095 暴露口径。
认证只回答「你是谁」,本节回答「你能做什么」:把 x-tenant-id 设为必填并校验租户归属,用 RBAC 加 ABAC 的简化模型做资源级授权,拒绝时返回 PERMISSION_DENIED 与 ErrorInfo(TENANT_FORBIDDEN),并防住跨租户 ID 枚举。
10.1 解决了链路加密,本节解决调用者身份:用 Metadata 传播 Bearer token,在 ServerInterceptor 里校验结构、HMAC 签名与过期时间,给出可编译实现,并划清教学代码与生产 JWT 库的边界和日志脱敏规则。
把 linkd-v3 的 9093 从明文换成 mTLS:用 keytool 生成 CA、服务端与客户端证书,再用 TlsServerCredentials 和 TlsChannelCredentials 接入 gRPC,讲清 PEM、SAN、主机名校验与握手失败的判读。
错误映射不是一张状态码表,而是服务端如何构造 google.rpc.Status、客户端如何从 trailers 还原、网关如何写出 Retry-After 的完整往返。本节给出 GrpcErrors 工具、ErrorInfo 与 RetryInfo 的打包解析、gRPC 与 HTTP 双向映射,以及服务端 onError 和网关写响应的代码。
BFF 的价值不是把多个接口拼在一起,而是用一次并行扇出、单跳 300ms 和明确的降级策略,把页面需要的字段裁成稳定契约。本节用 CompletableFuture.allOf 聚合并发调用,逐个处理部分失败,给出 BookCard 裁剪、800ms 总预算和预期响应示例。
外部客户端只会说 HTTP,内部服务说的是 gRPC。本节用 com.sun.net.httpserver.HttpServer 搭出 9094 网关,把路径映射到 GetBook 与 SearchBooks,按 Accept 在 JSON 和 protobuf 之间切换,并用 HttpClient 保留一条直连 libraryd 的兼容路径,最后给出完整的状态码映射表。
队列不是越大越好:有界 ArrayBlockingQueue 加明确的拒绝策略,才能把过载变成可观测的失败。本节给出 BoundedDispatcher 的构造、拒绝计数与队列深度指标,并用 disableAutoRequest/request(n) 把 gRPC 入站流控接到有界队列上,算清 128 个等待位在 300 ms 预算下的排队上限。
并发限制不是给线程池设个数字,而是要在拦截器里对在途调用精确计数、在超限时用 RESOURCE_EXHAUSTED 和 ErrorInfo 明确拒绝。本节给出 ServerInterceptor 的计数与释放路径、64/128 的装配方式,以及流式响应中用 isReady 和 onReadyHandler 回压生产者的完整实现。
令牌桶的关键不是公式,而是时钟、burst 和拒绝语义。本节用 System.nanoTime 补桶,给出 tryAcquire 与返回等待时长的实现,讨论冷启动满桶、多实例配额分摊和 retry-after 的边界,并用漏桶对照说明什么时候该排队、什么时候该立即拒绝。
熔断挡住已经坏掉的下游,Bulkhead 挡住「还没坏但很慢」的下游。本节用 Semaphore 加有界等待队列实现并发隔离,给出 acquire/release 配对、CompletableFuture.orTimeout 与取消的完整代码,并对比线程池隔离的代价。
熔断不是布尔开关,而是滑动窗口、最小请求数、失败率边界与半开探针共同组成的并发状态机。本节用 AtomicReference 切状态、用一把锁保护窗口,给出完整 CircuitBreaker 与按下游实例分桶的接入代码,并说明迟到响应为什么必须用 generation 丢弃。
从连接 drain 之后的下游抖动讲起,把重试拆成可重试码判定、指数退避与 ±20% 抖动、总 deadline 扣减和全局重试预算;给出 RetryPolicy、RetryBudget 与保留幂等键的 ClientCall 实现,并算清三层重试叠加时的放大倍数。
发布时最怕新请求还在进、老请求被一刀切断。本节拆开 ManagedChannel 与 Server 的 shutdown 语义,用 DrainCoordinator 统计在途请求,给出 linkd-v3 从停止入口、健康降级到关闭 channel 的完整顺序和时间预算。
一条 channel 通常就够,但隔离、并发额度和故障半径会逼你建第二条。本节给出 linkd-v3 的池键设计、有界 channel 池的完整实现,以及池大小与 MAX_CONCURRENT_STREAMS 的换算,避免把连接池写成每请求一条连接。
一条 HTTP/2 连接能同时跑多少请求,取决于流上限、流控窗口和服务端并发配置。本节用 1.76.0 的实测默认值拆开 MAX_CONCURRENT_STREAMS 与 1 MiB 窗口,给出连接数估算表和 linkd-v3 的 9093 配置。
拿到地址列表只是开始,选哪一条、怎么判断它还能不能用才是负载均衡。本节对比 pick_first 与 round_robin 的子通道行为,用 HealthStatusManager 和 HealthGrpc 做出真实的 SERVING/NOT_SERVING 探活,并给出 linkd-v3 的探活周期与抖动控制。