gRPC、REST、Connect-RPC 与 tRPC

共 63 题
📑 题目列表 63 题
#
★★★

1. gRPC 的流式模式(unary/server streaming/client streaming/bidi)在 Java 中的线程模型与背压控制

gRPC 的四种流式模式(unary、server streaming、client streaming、bidirectional streaming)在 Java 实现中的线程模型是怎样的?背压(backpressure)如何控制?

  • gRPC 四种调用模式对应的 API 形态与适用场景
  • Java gRPC 的线程模型(调用线程、EventLoop/执行器、流式回调)
  • 背压机制:流控、OutboundFlowController、消息缓冲

gRPC 定义了四种流式模式:unary(单请求单响应)、server streaming(单请求多响应)、client streaming(多请求单响应)和 bidi streaming(多请求多响应)。在 Java 中,unary 调用由调用线程发起并阻塞等待(同步 stub),或通过 ListenableFuture/CompletableFuture 异步回调;流式调用则注册 StreamObserver 回调,事件的回调在线程池/EventLoop 上执行。gRPC 的流控基于 HTTP/2 的窗口机制,OutboundFlowController 负责在发送端限制待发送字节数,避免内存暴涨;接收端通过 inflight 窗口暂停或恢复。生产环境应使用显式的 Executor 将流回调与业务线程隔离,避免阻塞默认的 EventLoop。

背压的根源是"生产者快于消费者"时内存被无限积压。gRPC 用 HTTP/2 流控窗口把积压上移到协议层,配合应用层缓冲(如 StreamObserver 的队列)实现有限背压。正确理解四种模式与线程模型,才能在高吞吐流式场景避免内存溢出与线程饥饿。

// server streaming 响应端
ServerCallStreamObserver<String> so = (ServerCallStreamObserver<String>) responseObserver;
so.setOnReadyHandler(() -> {
    while (so.isReady() && !queue.isEmpty()) {
        so.onNext(queue.poll());
    }
});
#
★★★

2. gRPC 的 Channel 连接管理与 keepalive,在虚拟线程下如何复用连接并控制并发流

gRPC 的 Channel 如何管理连接与 keepalive?在虚拟线程(virtual thread)场景下如何复用连接并控制并发流?

  • Channel 与 ManagedChannel 的复用机制
  • HTTP/2 连接内的多路复用与 MAX_CONCURRENT_STREAMS
  • keepalive 参数(ping)与虚拟线程下的资源模型

ManagedChannel 是 gRPC 客户端连接管理的核心,一个 Channel 维护一个或多个 HTTP/2 连接,多个调用复用同一连接中的并发流。keepalive 通过周期性地发送 HTTP/2 PING 帧探测连接活性,由 keepAliveTime、keepAliveTimeout、keepAliveWithoutCalls 控制。在虚拟线程下,阻塞式同步 stub 不再占用平台线程,可以创建大量并发调用,但每个调用仍占用一个 HTTP/2 流,因此并发流数量受 MAX_CONCURRENT_STREAMS 限制——超出时调用会排队等待。虚拟线程的低成本使"每请求一线程"天然可行,但连接与流仍应受控,避免单连接上的流过多导致对端或网络拥塞。

虚拟线程消除了"线程数"这一瓶颈,但 HTTP/2 流的数量与连接带宽仍是有限资源。因此控制并发流、合理设置保持连接与流控,是虚拟线程下 gRPC 性能的关键。合理配置 Channel 复用一个连接池,配合流控窗口即可。

#
★★★

3. REST 的幂等性设计,PUT/DELETE 的幂等语义与 POST 的幂等键(Idempotency-Key)实践

REST 接口的幂等性如何设计?PUT/DELETE 的幂等语义与 POST 的幂等键(Idempotency-Key)实践分别是什么?

  • HTTP 方法本身的幂等语义
  • 幂等键的服务端去重实现
  • 幂等与并发/重试的配合

HTTP 规范中 GET、PUT、DELETE、HEAD、OPTIONS 是幂等的(重复执行结果一致),POST 不幂等。PUT 用完整替换语义天然幂等,DELETE 对已删除资源重复删除也返回成功。对于非幂等的 POST,可引入 Idempotency-Key 请求头:客户端为每次"逻辑操作"生成唯一键,服务端记录该键及其处理结果,收到重复键时直接返回已保存的结果,从而保证重试安全。服务端需用数据库唯一约束或分布式锁保证幂等键的原子判重,并设置过期时间清理。

幂等性本质上把"客户端重试"变成安全操作。幂等键是解决"网络重试导致重复扣款/重复下单"的标准手段。实现时关键是"判重+结果缓存"的原子性,避免并发重复请求都通过判重,导致重复执行。

#
★★★

4. gRPC 错误模型(google.rpc.Status)与 HTTP 状态码的映射规则,客户端如何区分可重试与失败

gRPC 的错误模型(google.rpc.Status)与 HTTP 状态码的映射规则是什么?客户端如何区分可重试与不可重试的错误?

  • google.rpc.Status 结构(code、message、details)
  • gRPC 状态码与 HTTP 状态码的映射
  • 可重试与不可重试错误的判定

gRPC 错误使用 google.rpc.Status 结构,包含 code(gRPC 状态码)、message 和 details(可携带 Any 类型的结构化错误详情)。gRPC 状态码(如 INVALID_ARGUMENT、NOT_FOUND、UNAVAILABLE、DEADLINE_EXCEEDED)属于一个 0-16 的枚举,与 HTTP 状态码可通过规范映射(如 UNAVAILABLE→503、NOT_FOUND→404、INVALID_ARGUMENT→400)。客户端判断是否可重试:UNAVAILABLE、RESOURCE_EXHAUSTED、DEADLINE_EXCEEDED、ABORTED 等通常可重试,而 INVALID_ARGUMENT、NOT_FOUND、ALREADY_EXISTS、PERMISSION_DENIED 等确定性错误不可重试。服务端返回的 details 可携带 RetryInfo 指示重试间隔。

错误模型的关键在于"用枚举状态码表达错误类别",让客户端能程序化决策。可重试性由错误是否"瞬时/可恢复"决定。区分可重试与失败避免了对确定性错误盲目重试造成资源浪费或放大错误。

#
★★★

5. REST API 的版本化策略,URI 版本、Accept 头版本与查询参数版本的取舍

REST API 的版本化策略有哪些?URI 版本、Accept 头版本与查询参数版本各自的取舍是什么?

  • 三种版本化方式及其实现
  • 缓存、可读性与兼容性差异
  • 演变式 vs 破坏式版本

常见版本化方式有三种:URI 版本(/v1/users,最简单直观,便于日志与路由,但 URI 被污染、不利于共享缓存);Accept 头版本(Content-Type: application/vnd.api+json;version=2,通过媒体类型协商,保留干净 URI,但难以在浏览器/日志中直接看到,需要内容协商支持);查询参数版本(?version=2,实现简单但易被缓存混淆、参数可被忽略)。取舍上,URI 版本利于运维与调试,是多数公开 API 的选择;Accept 头版本适合同一资源多形态并存;查询参数版本适合内部服务快速切换。现代实践更倾向"语义化演变 + 破坏时升版本",尽量少产生多版本。

版本化的本质是在"演进"与"兼容"之间做权衡。URI 版本最直观但维护成本高(多套代码并存);媒体头版本更优雅但实现与调试复杂。选择取决于契约的变更频率与客户端生态。

#
★★★

6. REST 的 ETag、条件请求与共享缓存用于读接口时,为什么常比把所有调用改成 RPC 更有效

REST 的 ETag、条件请求与共享缓存用于读接口时,为什么常比把所有调用改成 RPC 更有效?

  • ETag 与条件请求(If-None-Match)的语义
  • 共享缓存(CDN/网关)对读接口的削减效果
  • 对比 RPC 的差异

ETag 是资源表示的实体标签,客户端在 If-None-Match 中携带,服务端比对后返回 304 Not Modified(无正文)或 200(变更)。共享缓存(CDN、反向代理)依据 ETag/Last-Modified 与 Cache-Control 在边缘缓存 GET 响应,大量读请求在网关层被拦截,无需穿透到应用与数据库。这让"读密集 + 数据变化不频繁"的接口在缓存命中时近乎零成本,优于把每个调用都直接打到后端 RPC。RPC 通常面向命令/过程调用,缺少 HTTP 的缓存语义,若把读操作全部改成 RPC 则丧失边缘缓存能力。

关键点是"缓存削减了后端压力"。共享缓存 + 条件请求把"重复读"变成"一次 304 校验",且 304 也大幅减少带宽。RPC 模型没有 HTTP 语义,无法直接复用 CDN/代理缓存,因此对于读接口,REST+缓存通常比全 RPC 更经济。

#
★★★

7. gRPC 的 deadline 传播,跨服务调用时剩余时间如何递减传递,客户端如何提前取消

gRPC 的 deadline 传播机制是怎样的?跨服务调用时剩余时间如何递减传递,客户端如何提前取消?

  • deadline 的传播(metadata 客户端头)
  • 剩余时间递减计算
  • 取消的传播链

gRPC 的 deadline 是一个绝对时间点,通过 metadata 中的 grpc-timeout 头传给服务端。跨服务调用时,服务端收到的是"总额度",其处理通常以剩余时间为准——当服务端再调用下游时,会把"当前时刻到 deadline 的剩余时间"作为新的超时传给下游,逐级递减,从而保证整条调用链的总体时间预算不被某一个环节耗尽。客户端提前取消通过 cancel() 或 deadline 到期触发,取消状态沿调用链传播,下游的 ServerCall 会收到 onCancel,流式对端也会感知取消。合理设置 deadline 可避免服务端无界等待、资源长期占用。

deadline 传播解决了"分布式超时累积"问题:每个环节都沿用全局预算,而不是各自固定超时导致总耗时长。配合取消传播,可尽早释放资源。这是 gRPC 相比"各自设置超时"的重要优势。

#
★★★

8. HTTP/2 多路复用下 REST 服务的连接池设计,与 HTTP/1.1 keep-alive 的差异

HTTP/2 多路复用下 REST 服务的连接池如何设计?与 HTTP/1.1 keep-alive 有何差异?

  • HTTP/1.1 的 keep-alive 与请求串行化
  • HTTP/2 的多路复用与单连接并发流
  • 连接池、流控与并发控制

HTTP/1.1 的 keep-alive 虽复用连接,但同一连接上请求必须串行(受队头阻塞影响),因此需要按并发度建立多条连接。HTTP/2 引入多路复用,一条连接可承载多个并发流,减少连接数,缓解队头阻塞。连接池设计上,HTTP/2 客户端通常维护少量连接(甚至单连接),通过流的并发度控制负载;同时受 MAX_CONCURRENT_STREAMS 与流控窗口约束,需要在连接数与流数之间权衡。Java HttpClient 对 HTTP/2 默认使用单连接池,通过流调度并发请求。HTTP/1.1 则需配置连接池大小以满足并发,否则并发请求会排队等待连接。

差异核心是"连接 vs 流"的并发模型。HTTP/2 把并发从连接层下沉到流层,减少 TCP 连接与 TLS 握手开销,但引入流控与并发流上限。连接池从"多多连接"转向"少连接多流"。

#
★★★

9. REST 分页设计,cursor/offset 分页在数据一致性与性能上的取舍

REST 分页设计中,cursor 分页与 offset 分页在数据一致性、性能上如何取舍?

  • offset/limit 分页的语义与缺陷
  • cursor(游标)分页的机制
  • 大数据集下的性能与一致性

offset 分页用偏移量+条数(?offset=0&limit=20),实现简单直观,适合中小数据集;但数据频繁插入/删除时会产生重复或跳页,且 OFFSET 越大数据库扫描越深、性能越差。cursor 分页用不透明游标(如编码后的排序键或 ID)定位下一页(?cursor=xxx),天然稳定,插入删除不影响已返回位置,且基于索引定位 O(log n) 高效,适合大数据集与实时变化数据。代价是游标不透明、无法随机跳页,且需要稳定的排序键。实践中高频变化或超大数据集用 cursor,简单稳定场景用 offset。

一致性上 cursor 基于"上次位置"而非"偏移量",天然免疫增删导致的位移;性能上 cursor 用索引定位避免深 offset。取舍取决于数据量、变更频率与对跳页的需求。

#
★★★

10. gRPC 负载均衡,客户端 LB 与服务器 LB(xDS)在连接池管理上的差异

gRPC 的负载均衡中,客户端 LB 与服务器 LB(xDS)在连接池管理上有何差异?

  • 客户端负载均衡(根据解析到的地址建连接)
  • xDS 控制面下发集群与端点
  • 连接池管理、子通道与粘性

客户端 LB 中,gRPC 客户端通过 name resolver 解析出后端地址列表,为每个地址建立子通道(subchannel),LB 策略(round_robin、pick_first、weighted_target 等)在这些子通道间选择请求目标。客户端直接管理连接池,连接复用率高、就近调用、无中间代理开销。服务器 LB(xDS)则由控制面(xDS server)下发集群、端点、负载均衡策略,gRPC 客户端通过 xds:/// 解析器订阅配置,支持更丰富的服务治理(如 LRS 精确负载上报、EDS 端点发现、熔断)。差异在于:客户端 LB 简单直接、连接由客户端掌控;xDS LB 将治理能力外置到控制面,便于大规模动态伸缩与 A/B、灰度,但引入控制面依赖与配置复杂度。

客户端 LB 把"连接池"牢牢握在客户端,灵活但治理能力弱;xDS 把"路由与端点"集中到控制面,适合服务网格与动态集群。选型取决于规模与治理需求。

#
★★★

11. tRPC 与 gRPC 在协议设计、服务治理与多语言生态上的对比

tRPC 与 gRPC 在协议设计、服务治理与多语言生态上如何对比?

  • 协议设计与传输层
  • 服务治理能力差异
  • 多语言生态与社区

两者都是面向 RPC 的高性能框架。gRPC 基于 HTTP/2 + Protobuf,协议标准、生态庞大(跨语言、Envoy/service mesh 原生支持),服务治理依赖外部组件(xDS、负载均衡)。tRPC(腾讯)同样基于 Protobuf,但更强调服务治理的内建能力(服务发现、路由、熔断、限流、链路追踪、配置中心一体化),协议设计偏重工程落地与多语言(C++/Go/Java/Python 等)的 SDK 对齐。差异:gRPC 赢在标准化与生态(云原生、网格、工具链),tRPC 赢在腾讯系内部的治理一体化与定制化。选型取决于团队技术栈与生态偏好。

gRPC 是"标准协议 + 生态",tRPC 是"工程化治理 + 一体化"。对云原生/开源生态敏感选 gRPC,对强治理诉求与特定语言栈选 tRPC。

#
★★★

12. gRPC 的健康检查协议(grpc.health.v1)与 Kubernetes 探针的集成

gRPC 的健康检查协议(grpc.health.v1)如何与 Kubernetes 探针集成?

  • grpc.health.v1 服务定义
  • Kubernetes 探针类型(liveness/readiness/startup)
  • gRPC 探针与 exec/TCP 探针的差异

gRPC 官方的健康检查协议定义 Health 服务,提供 Check 与 Watch 方法,返回 SERVING/NOT_SERVING 等状态。Kubernetes 提供原生 gRPC 探针(grpcProbe),kubelet 通过 grpc_health_probe 或原生机制调用 Health 服务,无需额外 exec。liveness 探针判断是否需要重启,readiness 探针判断是否接收流量,startup 探针用于慢启动。相比 TCP 探针(只测端口通),gRPC 探针能反映应用真实健康状态,且能配合服务摘流(NOT_SERVING 时 Kubernetes 摘除端点)。

集成价值在于"真实健康"而非"端口存活"。通过实现 Health 服务并做就绪/存活区分,可以让 Pod 在依赖未就绪时摘流、在崩溃时重启,配合 gRPC 探针实现优雅的流量管理。

#
★★

13. REST 的缓存策略,Cache-Control、ETag 与条件请求(If-None-Match)的配合

REST 的缓存策略如何设计?Cache-Control、ETag 与条件请求(If-None-Match)如何配合?

  • Cache-Control 指令(max-age、no-store、private)
  • ETag 的强/弱校验
  • If-None-Match 条件请求与 304

Cache-Control 控制缓存的可见性与新鲜度,如 max-age=3600 表示可缓存 1 小时,no-store 表示禁止缓存,private 表示仅私有缓存可缓存。当缓存过期后,客户端用 If-None-Match 携带 ETag 向服务端做条件请求,服务端比对 ETag 未变化则返回 304(不正文),客户端继续用本地缓存;变化则返回 200 新资源。两者配合:Cache-Control 避免每次请求都穿透,ETag 保证缓存过期后仍能用条件请求验证有效性,实现"尽可能用缓存,必要时才验证"。

Cache-Control 管"何时用缓存",ETag+条件请求管"缓存过期后如何低代价验证"。配合后读接口在命中时零后端开销,验证时仅 304。这是 REST 缓存经济的核心。

#
★★

14. gRPC 反射(ServerReflection)与 grpcurl 在调试中的应用,生产环境如何关闭

gRPC 反射(ServerReflection)与 grpcurl 在调试中如何应用?生产环境如何关闭?

  • ServerReflection 服务与动态发现
  • grpcurl 调用与 schema 获取
  • 生产安全关闭与网关隔离

gRPC 反射服务让客户端动态获取服务端注册的 .proto 描述(服务、方法、消息结构),无需事先持有 proto 文件即可用 grpcurl 调用、探测接口。调试时用 grpcurl list/describe/call 即可查看服务与调用。生产环境反射会暴露接口内部结构,存在信息泄露风险,应关闭(不注册 reflection 服务),仅在内网/测试环境启用;或通过防火墙、网关把它隔离在调试端口。

反射是"用 schema 驱动调试"的利器,但也是信息泄露面。生产关闭反射、调试环境开启,通过环境隔离兼顾便利与安全。

#
★★

15. REST 接口的请求追踪,trace 上下文在网关、负载均衡与应用间的传递规范

REST 接口的请求追踪中,trace 上下文如何在网关、负载均衡与应用间传递?

  • trace 上下文头(traceparent、tracestate)
  • W3C Trace Context 规范
  • 网关/LB/应用间的传播

请求追踪依赖在调用链中传递 trace 上下文。W3C Trace Context 规范定义了 traceparent 头(版本、trace-id、parent-span-id、flags)与 tracestate 头(厂商扩展信息)。网关、负载均衡、应用收到请求后解析 traceparent,在生成 span 时以其中的 trace-id 关联,并生成子 span 的 parent id 传给下游。网关负责生成/维持 trace-id,应用在其拦截器/过滤器里读取并透传,保证聚合链路时同一 trace 的所有 span 可关联。实践上需在每个出口(HTTP 客户端、DB、消息)注入 trace 上下文,并统一协议。

追踪的本质是"上下文的端到端传播"。规范化的 traceparent 头在各组件间透传,避免各组件各自命名导致链路断裂。统一上下文是分布式追踪的基础底座。

#
★★

16. 反向代理观测应如何关联 request_id、upstream_addr、upstream_status 与各阶段耗时定位网关或应用瓶颈

反向代理观测中,如何关联 request_id、upstream_addr、upstream_status 与各阶段耗时,定位网关或应用瓶颈?

  • 关键日志字段(request_id、upstream_addr、upstream_status)
  • 各阶段耗时(request_time、upstream_response_time)
  • 日志关联与瓶颈定位

反向代理(如 Nginx)日志中的 request_id 用于关联同一次请求,upstream_addr 表示后端地址,upstream_status 为后端返回状态,request_time 为总耗时,upstream_response_time 为后端响应耗时。通过请求日志与后端应用日志以 request_id 关联,可定位瓶颈所在:若 request_time 大而 upstream_response_time 小,瓶颈在网关自身(如上游排队、网络);若 upstream_response_time 大,瓶颈在后端应用或数据库。通过 upstream_status 的 5xx 定位后端故障,配合 access log 的耗时字段做分段分析。

观测的关键是"用统一关联键把分布式日志串起来 + 用阶段耗时把耗时切分成段"。request_id 打通网关与应用,阶段耗时把"总耗时"归因到网关或后端,从而精确定位。

#
★★

17. REST 与 gRPC 双协议暴露时的序列化与错误模型统一问题

REST 与 gRPC 双协议暴露时,如何统一序列化与错误模型?

  • 双协议暴露的动机
  • 序列化方案统一(Protobuf 作为共享契约)
  • 错误码与状态码映射统一

双协议暴露时常用同一套 Protobuf 定义作为契约,REST 层把 Protobuf 消息序列化为 JSON,gRPC 层直接用二进制编码,从而保证字段结构与语义一致。错误模型方面,把 google.rpc.Status 的 code 映射为 REST 的 HTTP 状态码,让双协议客户端都能按统一错误语义处理;同时保留结构化错误详情(details)便于诊断。序列化与错误模型的统一关键是"一份契约、两套编解码",避免两套模型漂移导致语义不一致。

双协议的根本矛盾是"同一业务语义用两套表达"。用 Protobuf 作契约 + 状态码映射作桥,可最大限度保持一致性,同时降低维护成本。

#
★★

18. OpenAPI 规范驱动的契约测试,如何用 schema 校验请求与响应

OpenAPI 规范驱动的契约测试如何实现?如何用 schema 校验请求与响应?

  • OpenAPI 作为契约来源
  • schema 校验(请求参数、响应体)
  • 契约测试与 provider/consumer 验证

OpenAPI 规范(如 OpenAPI 3.1)用 JSON Schema 描述请求/响应结构。契约测试可基于 OpenAPI 文档生成校验器,对每个接口的请求(路径参数、查询参数、请求体、headers)与响应体按 schema 校验,确保实际报文符合契约。常见做法:服务端用 schema 校验入参,客户端用 schema 校验出参,双方以 OpenAPI 为唯一事实源,配合 tooling(如 pytest-schema、Dredd、Postman schema-validate)做契约测试。这样文档、实现、测试彼此对齐,避免"文档与实现漂移"。

契约测试的价值是把"接口契约"变成可执行的校验。schema 校验让契约不只是文档,而是运行时约束,有效防止不兼容变更。

#
★★

19. Connect-RPC 的拦截器(Interceptor)如何实现认证、日志与重试的横切逻辑

Connect-RPC 的拦截器(Interceptor)如何实现认证、日志与重试等横切逻辑?

  • Connect-RPC 拦截器模型
  • 认证、日志、重试的拦截实现
  • 与 gRPC 拦截器的相似性

Connect-RPC 提供拦截器机制,允许在不改业务代码的情况下在调用前后注入横切逻辑。拦截器可包装 UnaryFunc/StreamFunc,在转发调用前做认证(读取/校验 token),在调用前后记录日志(耗时、状态),在调用失败时按重试策略重放。由于 Connect 支持 Connect 协议、gRPC 与 gRPC-Web 三种协议,拦截器位置统一,横切逻辑对三种协议一致生效。实现上类似 gRPC 的 ServerInterceptor/ClientInterceptor,用函数包装器链式组合。

拦截器把"认证、日志、重试"从业务代码中剥离,复用同一套逻辑。Connect 的协议无关拦截器让横切能力跨协议统一,降低重复实现。

#
★★

20. gRPC 拦截器与 REST 过滤器在认证/限流上的职责划分

gRPC 拦截器与 REST 过滤器在认证、限流上的职责如何划分?

  • 拦截器与过滤器的角色
  • 认证与限流的实现位置
  • 统一与差异

gRPC 拦截器(ServerInterceptor/ClientInterceptor)与 REST 过滤器(如 Spring 的 Filter/HandlerInterceptor)都在"进入业务逻辑之前"提供横切能力。认证与限流通常放在最外层:REST 端用过滤器在 Servlet 层做认证、限流、日志;gRPC 端用 ServerInterceptor 在 RPC 入口做同样的处理。职责划分上,两者本质同构——把通用横切逻辑放在协议入口层,业务层只关注业务。若双协议并存,可各自实现但共享统一的认证/限流组件(如共享 Token 校验器、限流器),避免逻辑重复或漂移。

职责划分的核心是"横切逻辑放协议入口、业务逻辑放业务层"。拦截器与过滤器是各自协议的横切点,通过共享组件实现逻辑统一。

#
★★

21. REST 服务的重试安全,仅对幂等请求自动重试,非幂等请求如何用幂等键保护

REST 服务的重试安全如何保证?为什么只对幂等请求自动重试,非幂等请求如何用幂等键保护?

  • 幂等请求(GET/PUT/DELETE)可安全重试
  • 非幂等 POST 的幂等键保护
  • 重试策略与幂等键结合

自动重试只适用于幂等请求(GET、PUT、DELETE、HEAD),因为重复执行不会产生额外副作用。对非幂等的 POST,需使用幂等键:客户端为每次逻辑操作生成唯一 Idempotency-Key,服务端原子判重并缓存结果,重试时返回首次结果,从而把"非幂等"变成"可安全重试"。实践中把幂等键与自动重试结合:对所有请求在必要时重试,但非幂等请求必须携带幂等键,服务端保证同一键只执行一次。

重试安全的本质是"重复执行不产生错误副作用"。幂等请求天然安全,非幂等请求通过幂等键在服务端去重,二者结合既保证可用性又保证一致性。

#
★★

22. gRPC 流式响应的取消,客户端取消后服务端如何感知并停止计算

gRPC 流式响应的取消机制是怎样的?客户端取消后服务端如何感知并停止计算?

  • 客户端取消的传播
  • 服务端 onCancel 回调
  • 资源释放与停止计算

客户端取消流式响应(调用 cancel() 或连接关闭)后,取消状态通过 HTTP/2 RST_STREAM 传播到服务端。服务端注册的 ServerCallStreamObserver 会触发 onCancel 回调,服务端应在此回调中停止生产、释放资源、关闭队列。若服务端使用 Context(cancellation context),取消还伴随 Context 取消,长时间计算可通过监听 Context.isCancelled() 或偏向取消的 executor 提前终止。正确实现"取消感知"是流式服务避免资源泄漏的关键。

流式响应可能持续很久,客户端取消后若服务端不感知,会继续浪费计算与网络。onCancel 回调 + Context 取消检测是实现"及时停止"的标准手段。

#
★★

23. HTTP 客户端连接复用,Java HttpClient 的 HTTP/2 连接池与并发流限制

Java HttpClient 的 HTTP/2 连接池与并发流限制如何工作?连接如何复用?

  • Java HttpClient HTTP/2 连接池
  • 并发流限制与流调度
  • 与 HTTP/1.1 连接池差异

Java HttpClient 对 HTTP/2 默认使用一个连接池,同一目标主机复用单条 HTTP/2 连接,多个请求通过多路复用流并发发送。并发流受 HTTP/2 的 MAX_CONCURRENT_STREAMS 限制,超出时请求排队等待流释放。支持 HTTP/1.1 时则维护连接池控制并发连接数。java.net.http 的 HttpClient 通过 keep-alive 复用连接,并可选设置连接池参数。相比 HTTP/1.1 每请求可能需要独立连接,HTTP/2 显著减少连接与握手开销。

Java HttpClient 的 HTTP/2 连接复用关键是"单连接多流",减少连接数但需关注流并发上限。合理设置并发与超时,避免排队导致延迟。

#
★★

24. RPC 调用的超时分层,连接超时、请求超时与总 deadline 如何设置与递减

RPC 调用的超时分层如何设计?连接超时、请求超时与总 deadline 如何设置与递减?

  • 连接超时、请求超时、总 deadline 的区别
  • 超时递减与总预算
  • 分层设置实践

超时需分层:连接超时(建立 TCP/TLS 连接的上限)、请求超时(单次请求的完成上限)、总 deadline(整个调用链或一次操作的总预算)。连接超时通常最短(如 1-3s),请求超时次之(如 5-10s),总 deadline 最长并随调用链逐级递减——每个下游获得的剩余时间 = 总 deadline - 已耗时。设置原则是"总预算 > 各环节之和 + 余量",且下游各项超时之和不超过总 deadline,从而保证整体可控、避免单点无界等待。

超时分层防止"单层超时无法覆盖全链路"。总 deadline 递减保证整体预算不被累加放大,连接/请求超时提供各环节的快速失败能力。

#
★★

25. Connect-RPC 的 JSON 与二进制编码同时开放时,网关、日志和错误详情应如何避免泄露敏感字段

Connect-RPC 同时开放 JSON 与二进制编码时,网关、日志和错误详情应如何避免泄露敏感字段?

  • JSON 与二进制(Protobuf)同时开放
  • 敏感字段脱敏
  • 日志与错误详情的字段控制

同时开放 JSON 与二进制时,JSON 编码的可读性使敏感字段更容易被日志、网关、错误详情记录而泄露。应通过 schema 标注敏感字段(如字段名称、字段选项),在网关、日志过滤器、错误详情序列化时统一脱敏:对密码、token、身份证号等字段打码或剔除;错误详情只返回非敏感信息;日志落盘前过滤敏感字段。可借助 schema 驱动的脱敏规则(如按字段名/标签匹配)保证 JSON 与二进制路径一致脱敏。

敏感字段泄露的根源是"可读编码 + 日志/错误详情无条件记录"。用 schema 标注 + 统一脱敏管线,让所有出口(日志、错误、网关)遵循同一规则,避免泄露。

#
★★

26. Protobuf 的序列化性能与 Java 反射式 JSON 的对比,RPC 场景为何倾向二进制

Protobuf 的序列化性能与 Java 反射式 JSON 相比如何?RPC 场景为何倾向二进制?

  • Protobuf 的二进制编码与代码生成
  • JSON 反射序列化的开销(反射、字符串、装箱)
  • 体积、速度与 schema 演进

Protobuf 通过代码生成产生专门的序列化方法,无需反射,编码紧凑(varint、字段号),体积小、速度快,且天然带 schema 支持演进。Java 反射式 JSON(如 Jackson databind)依赖反射遍历对象、字符串拼接、装箱/拆箱,且在序列化时动态解析,开销更大。RPC 场景高频、高吞吐、对带宽与延迟敏感,且往往是内部服务间调用,二进制编码的紧凑性与高效性优势明显,故倾向 Protobuf。

二进制胜在"代码生成 + 紧凑编码 + 天然 schema",反射 JSON 胜在人类可读与灵活。RPC 内部调用对性能与带宽敏感,倾向二进制;对外/调试场景 JSON 更合适。

#
★★

27. REST 错误响应的设计,Problem Details(RFC 9457)的字段约定与客户端解析

REST 错误响应如何设计?Problem Details(RFC 9457)的字段约定与客户端解析是什么?

  • RFC 9457 的 Problem Details 结构
  • type、title、status、detail、instance 字段
  • 客户端解析与错误标准化

RFC 9457(Problem Details for HTTP APIs)定义标准错误响应结构:type(错误类型 URI)、title(人类可读标题)、status(HTTP 状态码)、detail(具体细节)、instance(出错实例 URI),并可扩展自定义字段。客户端收到该结构后按 type 分类处理错误、用 status 判断状态、用 detail 展示或记录。标准化的错误 body 让各类客户端统一解析,避免每个错误各自为政。媒体类型为 application/problem+json。

Problem Details 把"错误响应"从临时拼字段规范化为统一 schema。客户端只需解析一套结构,提升错误处理的一致性与可维护性。

#
★★

28. gRPC 服务定义(.proto)的向后兼容,字段编号、reserved 与 oneof 的演进规则

gRPC 服务定义(.proto)如何向后兼容?字段编号、reserved 与 oneof 的演进规则是什么?

  • 字段编号唯一性与不删除
  • reserved 保留编号与名称
  • oneof 的演进约束

Protobuf 向后兼容的关键是字段编号:每个字段有唯一编号,已发布的字段编号不可复用、不可改类型(否则破坏兼容)。删除字段时用 reserved 保留编号与名称,防止未来误用导致新旧数据错位。oneof 演进时,只能移入/移出字段、不能同时包含多个被设置字段,且 oneof 内字段移动需谨慎。消息可新增字段(新编号),老客户端忽略未知字段,新客户端读取时用默认值。类型兼容上,整数间可安全扩展(int32→int64),但不可改 float↔string 等。

向后兼容的核心是"字段编号契约 + 未知字段保留"。reserved 防止编号复用,oneof 规则避免互斥语义被破坏,使滚动升级期间新旧客户端互通。

#
★★

29. REST 与 RPC 的边界,什么业务适合 REST 资源模型,什么适合过程调用模型

REST 与 RPC 的业务边界如何划分?什么业务适合 REST 资源模型,什么适合过程调用模型?

  • REST 资源模型(CRUD、资源表示)
  • RPC 过程调用模型(动作、命令)
  • 选型依据

REST 以资源为建模单元,适合以"资源状态"为中心的 CRUD 业务(如用户、订单、商品),强调资源表示、HTTP 语义、缓存与幂等。RPC 以过程/动作为建模单元,适合命令式、强流程、多参数、动作语义明确的业务(如触发结算、支付、计算、内部方法调用),强调低延迟与强类型。选型上:公开、面向资源、需缓存与幂等的用 REST;内部服务、高吞吐、动作密集、需强类型契约的用 RPC。边界模糊时,资源模型与动作混合使用(如 REST 资源 + 动作端点)。

REST 与 RPC 的边界是"资源 vs 动作"。资源化、可缓存、公开的适合 REST;过程化、命令式、内部高频的适合 RPC。二者可共存。

#
★★

30. tRPC(腾讯)的协议与 gRPC 的差异,编解码、路由与治理能力的对比

tRPC(腾讯)的协议与 gRPC 有何差异?编解码、路由与治理能力如何对比?

  • 协议与编解码差异
  • 路由与服务治理
  • 治理能力集成

tRPC 与 gRPC 都基于 Protobuf 定义契约,但 tRPC 在协议层做了工程化扩展:更内建的服务发现、路由、熔断、限流、负载均衡、链路追踪、配置管理一体化 SDK,通过框架层(而非依赖外部组件)提供治理能力。gRPC 协议更标准,治理依赖 xDS/Envoy 等外部组件。编解码上两者都支持 Protobuf 二进制,tRPC 还常支持多种传输与编解码适配。路由上 tRPC 内建路由规则与灰度,gRPC 依赖外部 LB/网格。差异在于 tRPC 更"一体化治理",gRPC 更"标准生态"。

差异核心是"治理内建 vs 治理外置"。tRPC 把治理能力打进 SDK,适合自建基础设施;gRPC 依赖标准生态,适合云原生与网格演进。

#
★★

31. gRPC Server 的线程模型,同步 vs 异步执行器在吞吐与延迟上的取舍

gRPC Server 的线程模型如何设计?同步 vs 异步执行器在吞吐与延迟上的取舍?

  • 同步 stub 与阻塞线程
  • 异步执行器与回调
  • 吞吐与延迟权衡

gRPC Server 默认用阻塞式执行器处理请求,同步 stub 会占用线程直到请求完成,简单直接但在高并发/高延迟场景易耗尽线程池。异步执行器(如绑定到 EventLoop 或用非阻塞处理)让线程不被阻塞,提高吞吐,但需要回调式编程、复杂度高。取舍:同步模型实现简单、延迟直观,适合低并发或线程可扩展;异步模型吞吐高、线程占用少,适合高并发或 IO 密集。虚拟线程可缓解同步模型的线程压力,使"阻塞式 + 虚拟线程"取得类似异步的吞吐而保留简单性。

同步 vs 异步是"简单性 vs 吞吐"的权衡。同步以线程占用换易用,异步以复杂度换吞吐。虚拟线程缩短了二者差距。

#
★★

32. HTTP 网关(Envoy/APISIX)如何把 REST 请求转译为 gRPC,协议转换的代价是什么

HTTP 网关(Envoy/APISIX)如何把 REST 请求转译为 gRPC?协议转换的代价是什么?

  • 网关的 REST→gRPC 转译
  • 消息映射(JSON/Protobuf)
  • 协议转换的代价(性能、错误映射、语义损失)

网关(Envoy/APISIX)通过注册的映射规则把 REST 请求映射为 gRPC 调用:HTTP 路径/方法映射到 gRPC 方法,JSON 请求体按 proto 定义的转换规则序列化为 Protobuf,再转发给 gRPC 后端,并把响应转回 JSON。代价包括:JSON↔Protobuf 转换增加 CPU 与延迟;HTTP 状态码与 gRPC 状态码需映射(可能丢失细节);HTTP 语义(缓存、幂等、流式)与 gRPC 语义不完全对应,需额外配置;网关成为性能与正确性瓶颈。为此常用 google.api.http 注解或 envoy 的 gRPC-JSON transcoder 在网关做声明式转译。

协议转换的代价集中在"转换开销 + 语义映射损失"。网关做转译让外部用 REST、内部用 gRPC,但需权衡转换成本与语义保真。

#
★★

33. REST 的 HATEOAS 与超媒体驱动在微服务中的实际应用边界

REST 的 HATEOAS 与超媒体驱动在微服务中的实际应用边界是什么?

  • HATEOAS 概念(响应含链接)
  • 超媒体驱动的导航
  • 微服务中的实际应用边界

HATEOAS 要求响应中携带可操作的链接(如 rel/href),客户端据此动态导航状态机,无需硬编码 URL。它实现"客户端与服务器解耦",但增加了响应体积、客户端复杂度与文档难度。在微服务实践中,HATEOAS 应用边界有限:多数团队用 OpenAPI 固定契约 + 客户端硬编码路径,只有在客户端需要动态发现能力、超媒体丰富或状态机复杂时(如工作流、支付流程)才真正受益。过度使用 HATEOAS 会带来可维护性负担。

HATEOAS 的价值在"动态导航与解耦",代价是复杂度。微服务多采用固定契约,HATEOAS 只在状态流转复杂、客户端需动态能力的场景有实际价值。

#
★★

34. gRPC 连接中 HTTP/2 流数量上限(MAX_CONCURRENT_STREAMS)如何影响并发调用

gRPC 连接中 HTTP/2 流数量上限(MAX_CONCURRENT_STREAMS)如何影响并发调用?

  • MAX_CONCURRENT_STREAMS 语义
  • 并发流上限与排队
  • 连接池与流数量

HTTP/2 的 MAX_CONCURRENT_STREAMS 由服务端通告,限制单连接上同时打开的并发流数量。gRPC 调用每个对应一个流,当并发调用数超过该上限时,新调用无法立即建立流,客户端会排队等待流释放或新建连接。若单连接并发流被占满,gRPC 客户端可能创建更多连接以容纳更多并发流。因此控制并发调用与合理配置连接数,避免大量调用在单连接上排队导致延迟放大。

MAX_CONCURRENT_STREAMS 是连接级并发闸门。高并发时需"多连接 + 流控"配合,否则单连接的流上限会成为瓶颈。

#
★★

35. REST API 的限流头(RateLimit-*)与客户端退避(Retry-After)的标准实践

REST API 的限流头(RateLimit-*)与客户端退避(Retry-After)的标准实践是什么?

  • RateLimit-* 响应头(X-RateLimit-Limit/Remaining/Reset)
  • Retry-After 语义
  • 客户端退避与重试

限流时服务端返回标准响应头:RateLimit-Limit(配额)、RateLimit-Remaining(剩余)、RateLimit-Reset(重置时间),限流命中时返回 429 Too Many Requests,并带 Retry-After 头指明客户端应等待的时间。客户端按 Retry-After 退避重试,避免在限流期间反复请求。实践上服务端统一生成限流头,客户端解析后做指数退避 + 抖动,随机化避免"惊群"同时重试。

限流头把"限流状态"告知客户端,Retry-After 给出退避指引。标准化的限流头 + 退避让客户端与服务端协作,避免限流雪崩。

#
★★

36. gRPC 与 REST 在日志与追踪字段上的差异,如何统一 trace 上下文

gRPC 与 REST 在日志与追踪字段上有何差异?如何统一 trace 上下文?

  • gRPC 的 metadata 追踪头
  • REST 的 HTTP 追踪头
  • 统一 trace 上下文

REST 用 HTTP 头(traceparent/tracestate 或 X-Request-Id)传递 trace 上下文;gRPC 用 metadata 头(如 traceparent)传递,二者传输载体不同但语义相同。统一做法是标准化 trace 上下文(W3C Trace Context),在 gRPC 拦截器与 REST 过滤器中分别读取/写入同一上下文,并透传到下游。日志字段上,REST 记录 method/URL/status,gRPC 记录 method/status code,可统一为 trace-id、span-id、service、method 等通用字段,便于跨协议聚合。

差异在于"传输载体"(HTTP 头 vs metadata),统一之道是"标准上下文 + 双端拦截器透传"。日志字段统一为通用追踪字段,保证跨协议可关联。

#
★★

37. OpenAPI 3.1 与 JSON Schema 的关系,契约定义在代码生成中的作用

OpenAPI 3.1 与 JSON Schema 的关系是什么?契约定义在代码生成中的作用是什么?

  • OpenAPI 3.1 与 JSON Schema 的融合
  • 契约驱动代码生成
  • 契约作为单一事实源

OpenAPI 3.1 完全采用 JSON Schema draft 2020-12 作为其 schema 描述语言,消除了早期版本与 JSON Schema 的差异,使 OpenAPI 的 schema 部分与通用 JSON Schema 兼容。契约定义(OpenAPI/JSON Schema)作为单一事实源,可驱动代码生成:服务端生成接口骨架与模型、客户端生成降级/调用代码、文档自动生成。契约驱动让实现、文档、测试保持一致,减少手工维护导致的漂移。

OpenAPI 3.1 用标准 JSON Schema 描述数据,增加互操作性。契约驱动代码生成把"文档"变成"可执行产物",是契约测试与代码生成的基础。

#
★★

38. gRPC 的压缩(gzip 等)在 CPU 与带宽上的权衡,何时应禁用压缩

gRPC 的压缩(gzip 等)在 CPU 与带宽上如何权衡?何时应禁用压缩?

  • 压缩的带宽收益与 CPU 开销
  • 压缩粒度(per-message/per-channel)
  • 何时禁用压缩

压缩(如 gzip、snappy)减小传输体积、节省带宽,但增加 CPU 开销与延迟(压缩/解压)。对体积大、可高比压缩的消息(如大文本)收益明显;对已压缩或小消息则收益有限甚至负面。在 CPU 紧张、内网带宽充裕、或消息体小/已压缩(如图片、视频、已压缩数据)时应禁用压缩,避免无谓 CPU 消耗。gRPC 支持按 channel 或按消息配置压缩,压缩策略选型需实测 CPU 与带宽的平衡。

压缩是"带宽换 CPU"的权衡。对高压缩比大消息启用,对低压缩比/小消息/CPU 敏感场景禁用。选择取决于网络与 CPU 资源状况。

#
★★

39. REST 批量操作的实现,batch 端点 vs 多次请求在一致性与性能上的取舍

REST 批量操作的实现如何取舍?batch 端点与多次请求在一致性与性能上如何权衡?

  • batch 端点(批量请求/响应)
  • 多次请求的逐条处理
  • 一致性、原子性与性能

batch 端点把多条操作打包到一次请求,减少网络往返与开销,性能好;但多条操作是否原子(全成功或全失败)取决于服务端实现,若只部分成功则一致性需通过幂等/补偿保证。多次请求可逐条控制、独立失败重试,但网络开销大、性能差。取舍:对强一致、需原子性的批量用 batch 并保证事务(或用事务型 batch 端点);对弱一致、可独立处理的批量用多次请求或非原子 batch,配合幂等键与补偿。批量端点需明确定义失败语义(全失败 vs 部分成功+结果列表)。

batch 端点是"性能 vs 一致性"的权衡。批量减少往返但需处理部分成功,多次请求保证独立但开销大。选型取决于原子性需求。

#
★★

40. gRPC 的鉴权,拦截器校验 metadata 中的 token 与 mTLS 的配合

gRPC 的鉴权如何实现?拦截器校验 metadata 中的 token 与 mTLS 如何配合?

  • metadata 中的 token 传递
  • 拦截器校验
  • mTLS 传输层身份

gRPC 鉴权常用两层:传输层用 mTLS 建立双向认证(客户端证书验证服务端,服务端验证客户端证书),声明层用拦截器读取 metadata 中的 token(如 authorization/bearer token)并校验。拦截器在进入业务前验证 token 有效性、权限、过期时间,失败返回 UNAUTHENTICATED。mTLS 保证"连接身份",token 实现"用户/租户级授权",二者配合:mTLS 认证服务/进程身份,token 认证具体调用者。高安全场景可两者叠加。

mTLS 管"是谁/哪个服务",token 管"谁发起的操作/权限"。二者配合实现连接级与调用级双重鉴权。

#
★★

41. Protobuf/Avro 等二进制序列化在微服务(gRPC)场景与 JSON 相比的体积、速度与 schema 演进取舍

Protobuf/Avro 等二进制序列化在微服务(gRPC)场景与 JSON 相比,在体积、速度与 schema 演进上有何取舍?

  • 二进制 vs JSON 的体积与速度
  • schema 演进能力
  • 微服务场景取舍

Protobuf/Avro 等二进制序列化通过字段编号/紧凑编码减小体积、提升编解码速度,且带 schema 支持演进(字段编号、保留字段、向前/向后兼容)。JSON 体积大、解析慢,但可读易调试、无强 schema 依赖。微服务(gRPC)场景内部调用高频、对带宽与延迟敏感,倾向二进制;但二进制不利于调试、跨语言需 schema 管理。取舍:内部高频用二进制 + schema 演进,对外/调试/跨组织用 JSON。Avro 常用于 Kafka 等消息场景,Protobuf 常用于 RPC。

二进制用"紧凑 + schema 演进"换性能,JSON 用"可读 + 灵活"换易用。微服务内部倾向二进制,边界场景用 JSON。

#
★★

42. REST 的媒体类型协商,application/json 与 protobuf(application/x-protobuf)如何共存

REST 的媒体类型协商中,application/json 与 protobuf(application/x-protobuf)如何共存?

  • 媒体类型协商(Accept/Content-Type)
  • 同一资源多表示
  • 双编码共存

通过内容协商,同一端点可支持多种媒体类型:客户端用 Accept 头声明期望格式(application/json 或 application/x-protobuf),服务端按 Accept 选择序列化方式,响应用 Content-Type 标记实际格式。服务端需同时支持 JSON 与 Protobuf 的序列化/反序列化,并共享同一资源模型。这样 REST 资源既能以 JSON 供浏览器/调试,又能以二进制供高性能客户端。实现需注意:Accept 处理、默认类型、错误响应的媒体类型一致性。

媒体类型协商让"同一资源多表示"共存。客户端按需选择 JSON 或二进制,服务端按 Accept 分发,兼顾可读性与性能。

#
★★

43. RPC 服务的优雅停机,先停止注册与摘流,再排空在途请求

RPC 服务的优雅停机如何实现?为什么先停止注册与摘流,再排空在途请求?

  • 优雅停机流程
  • 停止注册与摘流
  • 排空在途请求

优雅停机顺序是:先停止服务注册/健康上报并摘流(让负载均衡不再分发新流量),再排空在途请求(等待正在处理的请求完成),最后关闭连接与资源。摘流在前,避免新请求进入正在停机的实例;排空在后,保证在途请求正常完成不被中断。实现上:注册中心摘除实例、设置 readiness 为 NOT_SERVING、等待宽限期(grace period)内请求完成,超时强制关闭。这样既确保无新流量,又让存量请求平滑完成。

优雅停机关键是"先摘流、后排空、再关闭"。摘流防止新流量,排空保证存量完成,超时兜底避免无限等待。顺序颠倒会导致请求丢失或停机不完整。

#
★★

44. WebSocket 长连接跨版本发布时,怎样让旧连接自然排空、新连接进入新 upstream 并设置最长会话期限

WebSocket 长连接跨版本发布时,如何让旧连接自然排空、新连接进入新 upstream,并设置最长会话期限?

  • 长连接排空与版本切换
  • 新旧 upstream 路由
  • 最长会话期限

WebSocket 长连接无法像短请求那样立即切换版本。做法:发布时让新连接路由到新 upstream,旧连接维持到版本老代码直到自然断开;为保证旧连接最终关闭,设置最长会话期限(如 max session duration),到期强制断开让客户端重连切到新版本。路由上可用版本识别(如子协议、路径、cookie)把新连接引到新 upstream,旧连接保留在旧 upstream 并标记摘流(不再接收新连接)。核心是"优雅排空 + 会话期限兜底"。

长连接跨版本难点是"存量连接无法立即更新"。通过"新连接走新、旧连接自然排空 + 最长会话期限强制切换"实现平滑发布,避免旧版本无限期残留。

#
★★

45. gRPC deadline、HTTP 网关超时和下游数据库超时应按什么顺序递减才能保留清理时间

gRPC deadline、HTTP 网关超时和下游数据库超时应按什么顺序递减才能保留清理时间?

  • 超时递减顺序
  • 各层超时分配
  • 保留清理时间

超时应按"外大内小"递减:最外层 gRPC deadline 最大,HTTP 网关超时次之,数据库超时最小,且各层都要留出清理/容错时间。设计原则是"内层超时 < 外层超时 - 已有耗时 - 余量",保证内层先失败、外层总能兜底,且外层超时剩余时间仍能响应错误。例如数据库查询 2s < 网关 3s < 总 deadline 5s,这样一旦数据库超时,网关仍能在 deadline 内返回超时错误,而不会出现"外层已超时内层还在跑"。

超时递减保证"内层先超时、外层兜底、留清理时间"。若内层超时大于外层,则外层超时时内层还在执行,浪费资源且无法及时响应。递减顺序是超时工程的基础。

#
★★

46. gRPC 与 REST 在浏览器可达性、流式语义、缓存、错误模型和 schema 演进方面分别有哪些协议约束

gRPC 与 REST 在浏览器可达性、流式语义、缓存、错误模型、schema 演进方面分别有哪些协议约束?

  • 浏览器可达性
  • 流式语义
  • 缓存、错误模型、schema 演进

浏览器支持:REST 基于 HTTP/1.1 原生可达,gRPC 基于 HTTP/2 需 gRPC-Web 或代理才能被浏览器调用。流式语义:gRPC 原生支持四类流式(unary/server/client/bidi),REST 流式有限(需 SSE/WebSocket/chunked)。缓存:REST 有 HTTP 缓存语义(Cache-Control/ETag/条件请求),gRPC 无标准缓存语义。错误模型:REST 用 HTTP 状态码,gRPC 用 code+message+details;schema 演进:gRPC 用 Protobuf 字段编号/保留字段,REST 用 OpenAPI 与版本化。这些约束决定了选型与落地方式。

两种协议在六大维度有不同约束:浏览器、流式、缓存、错误、schema 演进。gRPC 强在流式与契约,REST 强在浏览器与缓存。选型需综合这些约束。

#
★★

47. 代理 WebSocket 时,Upgrade、Connection、HTTP 版本、读超时与连接排空配置缺一项会出现什么现象

代理 WebSocket 时,Upgrade、Connection、HTTP 版本、读超时与连接排空配置缺一项会出现什么现象?

  • WebSocket 握手头(Upgrade、Connection)
  • HTTP/1.1 与代理
  • 读超时与连接排空

代理 WebSocket 时配置缺一不可:缺 Upgrade 头则无法升级为 WebSocket;缺 Connection: Upgrade 则握手失败;HTTP 版本须为 HTTP/1.1(WebSocket 握手基于 HTTP/1.1,HTTP/2 需特殊处理);读超时设置不当会误断长连接(长连接空闲需合理超时);连接排空配置缺失会导致代理重启时在途长连接被强制断开。缺任一项都会表现为:握手失败、连接被切断、或长连接非预期断开。需确保代理正确转发 Upgrade/Connection 头、允许长连接、配置合理读超时与优雅断开。

WebSocket 代理的坑在于"头传递 + 超时 + 长连接生命周期"。缺一个配置就可能导致握手失败或长连接被异常断开,需系统性配置。

#
★★

48. 压测 unary、server streaming 与 bidirectional streaming 时,连接数、并发流和消息大小应如何分开控制

压测 unary、server streaming 与 bidirectional streaming 时,连接数、并发流和消息大小应如何分开控制?

  • 三种模式压测参数
  • 连接数、并发流、消息大小控制
  • 压测指标差异

三种模式压测参数不同:unary 关注并发调用数(QPS)、单个调用的吞吐,需控制并发流与消息大小;server streaming 关注单连接上流数量、流式消息频率与大小,需控制流并发与消息批次;bidi streaming 关注双向并发流、消息吞吐与背压。压测时应分开控制连接数(TCP 连接数)、并发流(每连接流数)、消息大小(字节),并分别测量连接级、流级与消息级吞吐/延迟。避免混在一起导致无法定位瓶颈(是连接瓶颈、流瓶颈还是消息大小)。

压测的关键是"变量分离"。连接数、并发流、消息大小是三个独立维度,分开控制才能定位瓶颈。不同模式瓶颈维度不同,需分别设计。

#
★★

49. 基于 split_clients、map 与 upstream 权重进行灰度时,怎样保持用户粘性并排除健康检查流量

基于 split_clients、map 与 upstream 权重进行灰度时,如何保持用户粘性并排除健康检查流量?

  • split_clients/map 灰度
  • 用户粘性(一致性哈希)
  • 排除健康检查流量

灰度时用 split_clients 或 map 按用户标识(如 user_id、cookie)做确定性分流,使同一用户总是进入同一灰度组(粘性),避免抖动。用一致性哈希(如 hash 用户标识)保证粘性。健康检查流量需排除,不参与灰度分流——识别健康检查请求(如 User-Agent、专用路径、secret)并直接路由到所有实例或跳过灰度逻辑,避免健康检查流量被灰度规则干扰或污染统计。也可用 upstream 权重灰度:新版本权重小、逐步调大,配合粘性路由。

灰度核心是"确定性分流 + 粘性 + 排除干扰流量"。用用户标识哈希保证粘性,用标识识别健康检查并排除,保证灰度准确与健康探针正常。

#
★★

50. 服务同时暴露 REST 与 RPC 时,如何用一套领域错误目录生成稳定的状态码映射而不丢失可观测性

服务同时暴露 REST 与 RPC 时,如何用一套领域错误目录生成稳定的状态码映射而不丢失可观测性?

  • 领域错误目录
  • 状态码映射(REST↔gRPC)
  • 可观测性保留

建立一套"领域错误目录":定义所有领域错误码(如 ERR_ORDER_NOT_FOUND)、分类(客户端/服务端/重试性)、对应 RPC 状态码与 HTTP 状态码、以及可观测标识(错误码、trace-id)。REST 与 RPC 层都从该目录查询映射,保证同一错误在两协议下状态码一致。可观测性通过保留结构化错误详情(错误码、trace-id、详情)实现,即使映射为状态码,日志/追踪仍记录完整错误目录字段,不因映射丢失诊断信息。

单一错误目录 + 双协议映射,保证 REST 与 RPC 错误语义一致;结构化详情保留可观测性。映射是"稳定的状态码",详情是"可观测的根因"。

#
★★

51. REST 的并发控制,ETag + If-Match 实现乐观并发更新,与版本号字段的取舍

REST 的并发控制如何实现?ETag + If-Match 实现乐观并发更新与版本号字段的取舍是什么?

  • ETag + If-Match 乐观并发
  • 版本号字段
  • 取舍与实现

乐观并发控制更新时,客户端先 GET 拿到 ETag,更新时带 If-Match: ,服务端比对当前 ETag 一致才执行更新,否则返回 412 Precondition Failed,客户端需重新读取。另一种是显式版本号字段(如 version),更新时带上 version,服务端校验版本一致才更新(CAS)。两者本质相同,ETag 是资源表示的隐式版本,版本号是显式字段。取舍:ETag 不侵入资源字段、语义通用,但需服务端计算;版本号字段直观、易存储,但需在资源模型中暴露。对并发写频繁且需业务控制用版本号,通用资源用 ETag。

乐观并发用"条件更新"避免丢失更新。ETag 与版本号都是"校验标记",实现 CAS 语义,取舍在于侵入性与通用性。

#

52. gRPC 的 keepalive 参数(ping 间隔、超时)在跨机房场景的调优

gRPC 的 keepalive 参数(ping 间隔、超时)在跨机房场景如何调优?

  • keepalive ping 间隔与超时
  • 跨机房 RTT 与网络
  • 调优策略

跨机房网络 RTT 大、丢包多,keepalive 参数需调优:keepAliveTime(ping 间隔)应大于机房 RTT 的若干倍,避免频繁 ping 造成网络负担;keepAliveTimeout(ping 超时)应大于 RTT,避免因网络延迟误判连接失效;keepAliveWithoutCalls 决定无调用时是否仍 ping。跨机房还应适当放宽超时、与 deadline 配合,避免网络抖动导致连接误断或调用误超时。

keepalive 调优的核心是"与 RTT 匹配"。ping 间隔与超时都要留足 RTT 余量,避免跨机房高延迟被误判为连接失效。

#

53. REST 与 WebSocket 的选型,请求-响应 vs 双向实时通信的边界

REST 与 WebSocket 的选型如何考虑?请求-响应与双向实时通信的边界在哪里?

  • REST 请求-响应模型
  • WebSocket 双向实时
  • 选型边界

REST 是请求-响应模型,适合"客户端发起、服务端响应"的常规交互,具有缓存、幂等、无连接状态等 HTTP 语义。WebSocket 是双向全双工长连接,适合"服务端主动推送、实时双向"场景(如聊天、实时行情、协作编辑)。选型边界:低频、按需、可缓存用 REST;实时、服务端推送、双向高频用 WebSocket。若仅需服务端推送可用 SSE 替代(单向、更简单)。WebSocket 复杂在长连接状态管理、连接排空、心跳与横向扩展。

边界是"交互模式":请求-响应用 REST,实时双向用 WebSocket。单向推送可用 SSE。选型要权衡长连接复杂度与实时性收益。

#

54. gRPC 的反射与健康检查在 Service Mesh 中的角色

gRPC 的反射与健康检查在 Service Mesh 中扮演什么角色?

  • Service Mesh 与 sidecar
  • 反射与健康检查
  • 在网格中的角色

在 Service Mesh 中,sidecar(Envoy)劫持服务流量。gRPC 健康检查服务(grpc.health.v1)让网格/控制面获知服务真实健康状态,用于端点摘除与负载均衡;反射服务让网格或调试工具动态获取服务 schema,用于协议理解、流量镜像与调试。反射用于"了解服务"(协议发现),健康检查用于"服务可用性"(生命周期管理)。生产网格中反射通常关闭以安全,健康检查用于就绪/存活判定。

网格中健康检查管"能否接流量",反射管"能否理解协议"。二者配合让网格具备服务可用性感知与协议感知能力。

#

55. HTTP 客户端超时配置,connectTimeout/readTimeout/responseTimeout 的分层设置

HTTP 客户端超时配置如何分层?connectTimeout/readTimeout/responseTimeout 如何设置?

  • 连接超时、读超时、响应超时
  • 分层设置
  • 各超时语义

HTTP 客户端超时分层:connectTimeout(建立 TCP/TLS 连接的上限,如 1-3s)、readTimeout(读取单次数据块/等待响应的上限,如 5-10s)、responseTimeout(整个请求完成的总上限,如 10-30s)。连接超时保证快速失败于不可达,读超时防止服务端无响应时无限等待,响应超时兜底整体调用。设置原则是"连接 < 读 < 响应",且响应超时 > 读超时 + 服务端处理时间,避免过早超时。

分层超时对应不同阶段(连接、读、整体)。连接超时管不可达,读超时管无响应,响应超时管整体。设置需保证后层 > 前层。

#

56. REST 请求体大小限制与流式上传,multipart 与 chunked 编码的差异

REST 请求体大小限制与流式上传如何处理?multipart 与 chunked 编码的差异是什么?

  • 请求体大小限制
  • 流式上传
  • multipart 与 chunked 差异

请求体大小限制由服务端配置(如 Spring 的 max-http-form-post-size、max-request-size),防止超大请求体耗尽内存。流式上传用 chunked 编码逐块传输,无需知道总大小,适合大文件。multipart 是"多部分"格式,用于一个请求携带多个字段与文件(如表单+文件),每个部分有独立 Content-Type 与边界。差异:chunked 是传输层分块(流式),multipart 是应用层多部分(结构化多个字段/文件)。流式上传需要流式读取避免一次加载全量到内存。

请求体限制防内存耗尽,流式上传用 chunked 逐块传输。multipart 是"多部分结构化",chunked 是"传输分块"。理解二者避免内存与解析问题。

#

57. Connect-RPC 如何在同一 Protobuf 服务上提供 Connect、gRPC 与 gRPC-Web 协议,并协商内容类型

Connect-RPC 如何在同一 Protobuf 服务上提供 Connect、gRPC 与 gRPC-Web 协议,并协商内容类型?

  • Connect 多协议支持
  • 内容类型协商
  • 同一服务多协议

Connect-RPC 的核心能力是"同一 Protobuf 服务同时支持 Connect、gRPC、gRPC-Web 三种协议"。通过请求的 Content-Type 协商:Connect 协议用 application/connect+json 或 application/proto,gRPC 用 application/grpc+proto,gRPC-Web 用 application/grpc-web+proto。Connect 服务端根据 Content-Type 自动选择对应协议的编解码与传输,对客户端透明。这样一份服务定义可服务浏览器(gRPC-Web)、移动端(Connect/gRPC)与高性能后端(gRPC),无需多套服务。

Connect 用"内容类型协商"识别协议,一份 Proto 服务三协议并存。这是 Connect 相比纯 gRPC 的互操作优势,解决浏览器可达性问题。

#

58. gRPC 的 DNS 解析与负载均衡,dns:/// 与 xds:/// 解析器的差异

gRPC 的 DNS 解析与负载均衡中,dns:/// 与 xds:/// 解析器有何差异?

  • name resolver 概念
  • dns:/// 解析器
  • xds:/// 解析器

gRPC 通过 name resolver 把服务名解析为后端地址列表。dns:/// 解析器用 DNS 查询 A/AAAA 或 SRV 记录获取地址,配合客户端 LB 策略(round_robin 等)做负载均衡,简单直接、无外部依赖。xds:/// 解析器从 xDS 控制面获取集群、端点、LB 策略与治理配置,支持更动态的端点发现(EDS)、精确负载上报、熔断与灰度,但需部署 xDS 控制面。差异:dns 简单、静态,xds 动态、治理强、适合网格。

dns:/// 是"DNS 解析 + 客户端 LB",xds:/// 是"控制面驱动的动态端点 + 治理"。选型取决于是否需要动态治理与网格。

#

59. REST API 文档生成,springdoc-openapi 与手写契约的维护成本对比

REST API 文档生成中,springdoc-openapi 与手写契约的维护成本如何对比?

  • springdoc-openapi 自动生成
  • 手写契约
  • 维护成本与漂移

springdoc-openapi 从注解与代码自动生成 OpenAPI 文档,与实现保持同步,减少"文档与代码漂移",但需注解维护、复杂契约(如条件、多态)表达受限。手写契约(手写 OpenAPI 文件)完全可控、可作单一事实源,但需手工维护,易与实现漂移,需契约测试兜底。springdoc 适合快速同步、以实现为主;手写契约适合"契约先行"、跨团队协作、需严格控制的场景。对比核心是"自动同步 vs 可控但易漂移"。

springdoc 用代码生成文档降低漂移,手写契约用契约先行提高可控但需额外维护。选型取决于是否"契约先行"。

#

60. Protobuf 在 Java 中的使用,protoc 生成代码与 runtime 依赖的管理

Protobuf 在 Java 中如何使用?protoc 生成代码与 runtime 依赖如何管理?

  • protoc 编译 .proto
  • 生成 Java 代码
  • runtime 依赖管理

Protobuf 在 Java 中通过 protoc 编译器把 .proto 文件编译为 Java 类(消息、builder、service),构建时用 protobuf-maven-plugin 或 protobuf-gradle-plugin 自动调用 protoc。生成代码依赖 protobuf-java runtime(proto 运行库)与 protobuf-java-util(JSON 转换)。runtime 依赖通过 Maven/Gradle 管理,版本需与生成代码匹配。增量构建可缓存中间产物,多模块下需管理 proto 依赖与生成目录。也可用 protoblique 等纯 Java 实现避免 protoc。

Protobuf 使用流程是"proto 定义 → protoc 生成 → runtime 依赖 → 业务使用"。构建工具自动管理 protoc 与生成,runtime 依赖用构建工具锁定版本。

#

61. Protobuf 字段编号、reserved、unknown fields 与 oneof 演进规则如何保证滚动升级期间新旧 gRPC 客户端互通

Protobuf 字段编号、reserved、unknown fields 与 oneof 演进规则如何保证滚动升级期间新旧 gRPC 客户端互通?

  • 字段编号与 unknown fields
  • reserved 与 oneof
  • 滚动升级互通

滚动升级期间新旧客户端并存,互通依赖 Protobuf 演进规则:字段编号不可复用(新字段用新编号),老客户端遇到新字段存为 unknown fields 并原样透传,新客户端读老数据时默认值兜底;删除字段用 reserved 防止编号复用;oneof 演进需注意互斥与字段移动。这样滚动升级时,新客户端与旧客户端能在同一消息上互不破坏地读写。unknown fields 保留机制是向后兼容的关键。

互通的核心是"字段编号契约 + unknown fields 保留 + reserved 防复用"。这些规则让新旧协议版本在滚动升级期间无缝共存。

#

62. HTTP/3(QUIC)对 HTTP 客户端的影响,连接迁移与 0-RTT 的语义

HTTP/3(QUIC)对 HTTP 客户端有何影响?连接迁移与 0-RTT 的语义是什么?

  • HTTP/3 基于 QUIC
  • 连接迁移
  • 0-RTT 重连

HTTP/3 基于 QUIC(UDP),相比 TCP 有:连接迁移(连接 ID 使 IP 变化不中断连接,适用于移动网络切换)、0-RTT(首次连接后可缓存凭据,重连时无需握手即发数据,减少延迟)、多路复用无队头阻塞。对 HTTP 客户端影响:连接迁移提升弱网体验,0-RTT 降低重连延迟,但 QUIC 需 UDP 支持(代理/防火墙可能阻挡)、0-RTT 存在重放风险需谨慎。Java 客户端生态对 HTTP/3 支持逐步完善。

HTTP/3 用 QUIC 换连接迁移与 0-RTT 等能力,对弱网与重连场景收益明显,但需注意 UDP 穿越与 0-RTT 安全。

#

63. gRPC 流式接口的测试,如何用 stub 模拟流并验证时序

gRPC 流式接口的测试如何进行?如何用 stub 模拟流并验证时序?

  • 流式 stub 测试
  • 模拟流与响应
  • 验证时序

gRPC 流式接口测试可用 mock stub(如 mockito 的 StreamObserver)或真实 stub 连接测试服务器。验证时序时,用 mock StreamObserver 捕获 onNext 序列,用 ArgumentCaptor 断言收到的消息顺序与数量;模拟服务端用 StreamRecorder 或自定义 observer 记录响应顺序。对 server streaming 用流式 stub 并收集 onNext 的顺序,对 bidi 用请求/响应流 observer 验证交互时序。时序验证核心是"断言 onNext 的调用顺序与内容"。

流式测试的关键是"验证消息序列与顺序"。用 mock/recorder 的 StreamObserver 捕获 onNext 调用,断言时序满足预期。