gRPC独立面试题:Java内部原理、HTTP/2、Protobuf与生产排查
本页只保存标准回答、追问和精确原理跳转。基础协议见gRPC从零到生产级,Java源码执行链、双版本Demo和生产Runbook见gRPC Java内部原理与生产治理。
版本口径:JDK 7只位于gRPC Java 1.41.x维护分支;官方1.82.2样本支持Java 8+;现代工程以Java 17+、Boot 3.x和受支持依赖为主。协议原理相同,不代表JDK语法、Spring Starter和依赖组合可以混用。
一、定位、分层与代码生成
1. gRPC是什么
标准回答:gRPC是基于IDL、代码生成和HTTP/2的RPC框架,常用Protobuf作为消息契约与编解码格式,提供类型安全Stub、四种调用模式、Deadline、Status、拦截器、服务发现与负载均衡能力。它降低调用样板代码,但不会消除网络失败、事务、幂等和结果未知。
常见追问:为什么说gRPC调用仍不是本地方法?
原理入口:源码分层。
2. gRPC、HTTP/2和Protobuf是什么关系
标准回答:gRPC定义RPC方法、调用模式、Status、Deadline和拦截等上层语义;HTTP/2提供连接、Stream、Frame、多路复用和流控;Protobuf提供Schema、字段编号、二进制编解码和代码生成。三者不能互相替代。
常见追问:gRPC是否只能使用Protobuf?
原理入口:四层职责。
3. 官方所说Stub、Channel、Transport三层分别做什么
标准回答:Stub是生成的类型安全调用入口;Channel负责拦截、方法配置、名称解析、负载均衡、Call和连接管理;Transport负责TLS、HTTP/2和网络字节。业务事务、幂等、权限和补偿位于更上层。
常见追问:哪个层真正建立TCP连接?
原理入口:源码对象地图。
4. .proto会生成哪些Java代码
标准回答:除请求响应消息类外,还会生成方法描述符、Blocking/Future/Async Stub、服务端ImplBase、方法分发器和bindService()。Stub调用不需要每次反射解析.proto。
常见追问:java_multiple_files有什么影响?
原理入口:代码生成。
5. MethodDescriptor保存什么
标准回答:它保存完整方法名、Unary或Streaming调用类型、请求Marshaller、响应Marshaller及部分方法Schema信息。Channel依靠它创建Call,Transport依靠Marshaller把对象转换为消息字节。
常见追问:一个HTTP/2 DATA Frame是否等于一个Protobuf消息?
原理入口:MethodDescriptor。
6. Blocking、Future与Async Stub本质区别
标准回答:它们最终都通过Channel.newCall进入ClientCall,区别主要是调用方等待和观察结果的方式。Blocking让当前线程等待;Future返回可组合Future;Async用Observer并支持流式调用。Blocking不代表网络I/O在当前线程,Async也不保证业务代码非阻塞。
常见追问:Async回调里能直接做慢SQL吗?
原理入口:Stub生成、回调Executor。
7. 服务端为什么继承ImplBase就能接收请求
标准回答:生成的bindService()把完整方法名、MethodDescriptor、ServerCallHandler和方法分发器注册为ServerServiceDefinition。Server按完整方法名查表,再由Handler调用重写的业务方法。
常见追问:方法没注册返回什么Status?
二、Channel、Resolver与负载均衡
8. ManagedChannel是什么
标准回答:ManagedChannel是长期存在的逻辑通信入口,内部维护NameResolver、LoadBalancer、Subchannel、真实Transport、方法配置和连接状态。它不等于一条固定TCP连接,也不应每次RPC创建。
常见追问:一个Channel能管理多条连接吗?
原理入口:源码对象地图。
9. 为什么每次RPC创建Channel是反模式
标准回答:会重复执行名称解析、TCP/TLS和HTTP/2握手,创建更多线程、连接和文件描述符,丢失连接复用与LB状态。Web服务应把Channel做成长生命周期Bean,在应用关闭时统一shutdown。
常见追问:Stub是否也需要单例?
10. Target URI与NameResolver怎样协作
标准回答:Channel根据target URI的scheme选择NameResolverProvider;Resolver异步返回地址组、Attributes、Service Config和可选配置选择器。它不是把字符串简单替换成一个IP。
常见追问:dns:///inventory中的三个斜杠怎样理解?
原理入口:NameResolver。
11. NameResolver除地址还能返回什么
标准回答:还能返回Service Config、解析级Attributes和配置选择信息。Service Config可控制LB、timeout、wait-for-ready、消息大小、retry/hedging和重试节流。
常见追问:地址解析成功但Service Config错误会怎样?
原理入口:Service Config。
12. 为什么第一次调用可能成为PendingCall
标准回答:首次解析和配置选择尚未完成时,RealChannel会退出IDLE并暂存PendingCall;解析结果到达后再reprocess为真实ClientCall。没有Deadline时,Resolver长期故障可能让Pending调用持续堆积。
常见追问:这时是否已经建立HTTP/2 Stream?
原理入口:PendingCall。
13. LoadBalancer怎样参与每次调用
标准回答:LB接收地址与连接状态,维护Subchannel,并发布轻量的SubchannelPicker。每次调用使用当前Picker得到可用Subchannel、buffer、error或drop结果,不是每次重新扫描全部地址并临时建连接。
常见追问:Picker返回无结果时调用去哪了?
原理入口:LoadBalancer与Picker。
14. DelayedClientTransport解决什么问题
标准回答:当Resolver、Picker或连接尚未就绪时,它保存延迟Stream;新的Picker发布后执行reprocess,再绑定真实Transport或失败。它是内存等待机制,不是持久消息队列。
常见追问:wait-for-ready为何必须配Deadline?
原理入口:DelayedTransport。
15. fail-fast与wait-for-ready区别
标准回答:fail-fast在Channel当前不可用时通常快速失败;wait-for-ready允许调用在Deadline内等待Channel恢复。Drop等控制面明确拒绝不应被无限等待掩盖;没有Deadline的wait-for-ready可能积压内存。
常见追问:wait-for-ready能保证请求最终成功吗?
原理入口:Picker结果、DelayedTransport。
16. Subchannel是什么
标准回答:Subchannel是LB管理的逻辑连接单元,可关联一组地址并维护真实Transport与IDLE、CONNECTING、READY、TRANSIENT_FAILURE、SHUTDOWN状态。它不是业务服务对象,也不等于永久固定Socket。
常见追问:全部地址连接失败后如何恢复?
原理入口:Subchannel状态机。
17. READY是否证明业务服务健康
标准回答:不能。READY只表示有可用Transport;方法仍可能不存在,身份可能无权限,数据库可能异常,业务可能拒绝。连接状态和业务健康必须分层观测。
常见追问:Health SERVING和Channel READY区别是什么?
原理入口:Channel状态边界。
18. pick_first与round_robin区别
标准回答:pick_first倾向优先使用地址序列中的可用Subchannel;round_robin在多个READY Subchannel间按RPC选取。前提是Resolver返回多个后端,只有一个ClusterIP时改策略也看不到Pod列表。
常见追问:为什么扩容Pod后round_robin仍不均衡?
原理入口:负载策略、Kubernetes长连接。
三、ClientCall、Deadline与重试
19. 一次Unary客户端调用怎样走
标准回答:生成Stub调用ClientCalls;Channel经过拦截器和方法配置,必要时等待解析;ClientCallImpl应用Deadline、压缩和消息限制;Picker选择Subchannel Transport;Transport创建HTTP/2 Stream并发送Headers和消息。
常见追问:哪一步真正发生网络I/O?
原理入口:客户端源码主链。
20. ClientCallImpl.start做什么
标准回答:校验调用状态,应用方法配置,准备压缩和Headers,计算有效Deadline,创建ClientStream,设置Authority/消息大小/Deadline,启动Listener,并安装Context取消与Deadline定时器。
常见追问:调用开始时Deadline已经过期会怎样?
原理入口:ClientCall启动。
21. CallOptions Deadline与Context Deadline如何选择
标准回答:取更早者;Service Config方法timeout也只能进一步缩短,不能把上游剩余预算重新放大。这样下游共享同一个端到端时间预算。
常见追问:为什么每层重新设置800ms是错误的?
原理入口:Deadline取最早值。
22. DEADLINE_EXCEEDED证明服务端没执行吗
标准回答:不能。它只证明调用方没有在Deadline内取得完成结果;服务端可能尚未收到、正在执行、已经提交但响应丢失。写请求必须按稳定request_id查询事实。
常见追问:库存预留超时后是否直接重试?
23. 客户端取消为何不一定停止服务端
标准回答:Transport会传播取消或RST_STREAM,但服务端业务、JDBC和外部调用需要协作检查Context并设置下游剩余超时。数据库已提交时,取消无法跨进程回滚。
常见追问:服务端在哪里检查取消?
原理入口:Deadline与取消。
24. 逻辑RPC、Attempt和HTTP/2 Stream是什么关系
标准回答:一次ClientCall是逻辑RPC;RetriableStream可为它创建多个Substream Attempt;每个Attempt通常对应一个Transport上的HTTP/2 Stream。客户端最终只上报一个逻辑结果,但服务端可能看到多个Attempt。
常见追问:指标应统计逻辑调用还是Attempt?
原理入口:逻辑RPC与Attempt、可观测性。
25. RetriableStream为什么要缓冲请求
标准回答:winner提交前,新Attempt需要重放start、request、消息、flush、halfClose、压缩和Deadline等操作,因此保存BufferEntry。大请求、多并发重试和Hedging会扩大内存消耗。
常见追问:提高最大消息后为何重试更容易OOM?
26. commit winningSubstream是什么意思
标准回答:选定不能再切换的winner,取消计划重试和其他不需要的Substream,后续操作直接交给winner,最终只向上层Listener报告一次。其他Attempt可能已经执行副作用,因此不等于Exactly Once。
常见追问:Hedging为什么只适合只读或严格幂等?
原理入口:RetriableStream提交。
27. 透明重试与配置重试区别
标准回答:透明重试针对特定传输失败窗口,由框架根据调用是否到达服务端处理阶段等进度决定;配置重试由Service Config显式声明状态码、次数和退避。两者都受Deadline、Buffer、Throttle和提交点约束。
常见追问:UNAVAILABLE是否一定被透明重试?
原理入口:透明与配置重试。
28. maxAttempts=3表示什么
标准回答:包含第一次调用,最多三个物理Attempt,不是首次加三次重试。还要计算SDK、Mesh、Gateway和业务循环的总尝试数,避免乘法放大。
常见追问:如何监控重试风暴?
29. Hedging是什么
标准回答:前一个Attempt尚未失败时,延迟发起并行Attempt,以降低尾延迟;代价是更多流量、连接和重复执行概率。只能用于只读或服务端强幂等操作,并设置并发、预算、Status和Throttle。
常见追问:Hedging与普通Retry差别是什么?
原理入口:RetriableStream。
30. 为什么所有层都开启重试会雪崩
标准回答:每层看见上一层的失败再独立重试,最大物理Attempt近似相乘;3层各3次可能达到27次。应指定唯一重试Owner,统一Deadline、幂等、次数、退避和Retry Budget。
常见追问:代理与SDK重试谁负责更合适?
四、服务端、线程与流控
31. 服务端收到新Stream后怎样进入业务方法
标准回答:Netty Handler将新Stream交给ServerImpl;它处理解压、查完整方法名、创建可取消Context和ServerCallImpl,切到应用SerializingExecutor,执行拦截器和ServerCallHandler,最后进入生成的ImplBase方法。
常见追问:方法查找发生在业务线程还是EventLoop?
原理入口:服务端主链。
32. Netty EventLoop和应用Executor怎样分工
标准回答:EventLoop负责Socket、TLS和HTTP/2协议事件;应用Executor处理ServerCall回调和业务逻辑。慢SQL不能占EventLoop;应用Executor也必须有界,否则Deadline过期的请求会继续排队占内存。
常见追问:CPU低但gRPC延迟高可能是什么?
原理入口:服务端Executor、Runbook。
33. SerializingExecutor保证了什么
标准回答:保证单个Call的回调按顺序串行,避免并发重入;不保证所有Call只有一个线程,也不允许慢回调无限阻塞。不同RPC可并行,同一个RPC的后续事件会等待前一个回调。
常见追问:StreamObserver是否可以任意多线程并发调用?
原理入口:回调串行化。
34. Unary业务方法为什么在onHalfClose触发
标准回答:服务端先接收唯一请求消息,客户端发送完请求后half-close请求方向;Unary Handler在onHalfClose确认请求完整,再调用业务方法。客户端发送多个请求消息属于协议错误。
常见追问:half-close是否关闭整个双向Stream?
原理入口:ServerCall与Observer。
35. gRPC四种调用模式是什么
标准回答:Unary是一请求一响应;Server Streaming是一请求多响应;Client Streaming是多请求一响应;Bidirectional Streaming是双方独立发送多个消息。Streaming是在线连接,不自动提供离线持久和断点恢复。
常见追问:双向流能否替代Kafka?
原理入口:基础调用模式、服务端Observer。
36. HTTP/2流控和业务背压区别
标准回答:HTTP/2窗口限制连接和Stream可发送网络字节;业务背压还要控制对象生产、内存队列、数据库处理和持久确认。只靠窗口不能阻止应用把消息复制到无界队列后OOM。
常见追问:isReady()返回true能连续发送多少条?
原理入口:流控与背压。
37. request(n)有什么作用
标准回答:表示当前接收方准备再接收n条消息。客户端用ClientCall.request(n)控制响应交付,服务端用ServerCall.request(n)控制请求交付。关闭自动入站流控后,应用必须在真正处理完成后继续request。
常见追问:忘记request会怎样?
原理入口:消息级流控。
38. isReady()为什么不是永久许可
标准回答:它只反映当前发送状态,检查后窗口和队列可能变化。发送循环应限制单批数量、每轮重新检查,并由有界业务队列兜底;不可写时等待onReady通知。
常见追问:为什么无视isReady会OOM?
原理入口:出站背压。
五、HTTP/2、连接与Kubernetes
39. HTTP/2多路复用是什么
标准回答:一条HTTP/2连接承载多个逻辑Stream,各Stream的Frame可交错发送,减少为每个请求建连接的成本。但底层仍是TCP,一个丢包可能阻塞后续字节并影响同连接多个Stream。
常见追问:HTTP/2是否完全消除了队头阻塞?
原理入口:Transport链、HTTP/2基础。
40. 一个Protobuf消息等于一个DATA Frame吗
标准回答:不等于。gRPC消息有压缩标记和长度前缀,一个消息可以跨多个HTTP/2 DATA Frame;Frame也只是传输分片,不能直接当业务消息边界。
常见追问:最大消息限制作用在哪一层?
原理入口:Transport链、消息大小。
41. grpc-netty-shaded与grpc-netty区别
标准回答:两者都使用Netty;shaded版本重定位依赖,降低应用中Netty版本冲突,通常更易升级。未shaded版本便于与统一Netty栈集成,但必须严格管理Netty BOM和传递依赖。
常见追问:用了shaded就不需要关注EventLoop了吗?
原理入口:Netty Transport。
42. Keepalive、TCP Keepalive和Health区别
标准回答:TCP Keepalive在内核层探测连接;gRPC Keepalive通常使用HTTP/2 PING探测连接往返;Health服务表达指定应用服务是否愿意接流量。三者不能互相替代。
常见追问:PING成功是否证明数据库健康?
原理入口:Keepalive与Health。
43. Keepalive越短越可靠吗
标准回答:不是。大量客户端高频PING会制造网络和服务端负载,并可能触发服务端GOAWAY。应结合服务端最小允许间隔、NAT空闲超时、客户端数量和连接规模配置。
常见追问:为什么生产偶发too_many_pings?
原理入口:Keepalive风险。
44. GOAWAY是什么意思
标准回答:该HTTP/2连接不再接受更大Stream ID的新调用,已接受Stream可能继续;Channel应把后续调用迁移到新连接。它不等于所有在途业务都失败,也不能触发所有客户端无抖动同时重连。
常见追问:发布下线时如何利用GOAWAY?
原理入口:GOAWAY与优雅停机。
45. Kubernetes扩容后gRPC流量为何仍集中
标准回答:客户端若只解析到ClusterIP,会建立少量到VIP的HTTP/2连接;连接在建连时被转到少数Pod,后续RPC都在旧连接内复用。新Pod只能获得新连接,不能接管旧Stream。
常见追问:Headless Service怎样改善?
原理入口:Kubernetes长连接负载。
46. 怎样改善gRPC在Kubernetes中的负载分布
标准回答:让Resolver获取Pod Endpoint,例如Headless Service、自定义Resolver、gRPC xDS或Mesh;使用理解HTTP/2的L7代理;受控设置连接年龄和抖动重连;观察每Endpoint RPC数。不能无限增加连接数量。
常见追问:为什么只观察连接数不够?
原理入口:Kubernetes治理。
六、契约、安全、版本与生产场景
47. Protobuf字段编号为什么不能复用
标准回答:编号是wire上的字段身份,删除后复用会让新旧程序把同一字节解释成不同含义。删除字段后应使用reserved保留编号和名称。
常见追问:字段改名是否二进制兼容?
原理入口:Protobuf兼容、契约治理。
48. mTLS是否等于业务授权
标准回答:不是。mTLS验证工作负载身份并加密传输,不能自动证明当前用户可以访问某个订单或患者。用户认证、租户隔离、资源授权和审计仍由业务层负责。
常见追问:Token应放在哪里?
49. JDK 7、JDK 8和Java 17如何区分
标准回答:官方1.82.2 README支持Java 8+,JDK 7留在1.41.x维护分支;Java 17/Boot 3可使用现代语法和观测生态。协议不因Java版本改变,但JDK 8不能使用Record、文本块、Map.of和String.isBlank,第三方Starter还必须匹配Boot版本。
常见追问:grpc-java支持Java 8是否代表任意Boot 2 Starter都支持1.82.2?
原理入口:版本边界。
50. Spring Boot 3的Jakarta迁移会改变gRPC协议吗
标准回答:不会改变HTTP/2和Protobuf wire协议,因为gRPC核心不是Servlet请求模型;但Spring生命周期、第三方Starter、Actuator、Micrometer、Netty和依赖管理会变化,必须按具体Starter和BOM验证。
常见追问:为何推荐先用公开gRPC API理解生命周期?
原理入口:Boot 2/3边界、Boot 3 Demo。
七、生产场景题
51. 库存预留收到DEADLINE_EXCEEDED,是否重试
标准回答:不能直接用新ID重试。先用原request_id查询库存事实;服务端以request_id唯一键和扣减同事务保存。确认未执行且还有总Deadline和Retry Budget时,才用同ID有限重试;长时间UNKNOWN交给对账补偿。
52. gRPC服务CPU不高但大量超时怎么查
标准回答:先分解Resolver等待、连接、客户端Executor、网络、服务端应用Executor排队、数据库连接池和业务耗时。CPU低可能是队列、锁、连接池或远程I/O等待,不能据此排除容量问题。
原理入口:服务端Executor、Deadline Runbook。
53. RESOURCE_EXHAUSTED应该直接调大限制吗
标准回答:先根据description区分消息过大、配额、并发、Header或内存,再核对客户端、服务端和代理限制。大文件应改分块或对象存储;直接调高会放大Protobuf对象、压缩缓冲、重试Buffer和并发内存。
54. 流式采集卡住怎样定位
标准回答:检查双方连接和Stream、最后发送/接收/持久确认序号、half-close和取消、HTTP/2窗口、isReady/onReady、业务队列与消费者。恢复必须基于持久位点,不能只重连后从头猜测。
55. 怎样证明一次写RPC端到端成功
标准回答:最终OK Status只是重要证据,还应关联request_id、物理Attempt、服务端幂等记录和业务数据库事实;客户端超时或UNAVAILABLE时必须通过查询接口或对账确认。关键操作不能只凭一条客户端日志下结论。
八、项目回答模板
我们先用
.proto稳定字段编号和兼容规则,生成Stub、MethodDescriptor与ImplBase。客户端复用ManagedChannel,Resolver返回Endpoint和Service Config,LoadBalancer维护Subchannel并用Picker选READY Transport;每个逻辑ClientCall可能由RetriableStream产生多个物理Attempt。调用统一传播端到端Deadline,写接口携带稳定request_id,服务端用唯一键和业务修改同事务提交。Netty EventLoop只做网络协议,业务在有界应用Executor执行;流式调用用request(n)、isReady、有界队列和持久确认位点。Kubernetes中避免只解析ClusterIP导致长连接倾斜。排障按Resolver、Picker、Subchannel、Transport、Attempt、服务端Executor、数据库事实逐层取证。
