Files
zhuangang/topics/backend/grpc-microservices/interview.md
T

86 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: backend.grpc-microservices.interview
title: gRPC 与微服务面试题
type: interview
domain: backend
stack: [grpc-microservices]
tags: [gRPC, 微服务, 服务治理, 分布式事务, 面试]
level: advanced
related: [backend.grpc-microservices.grpc-proto, backend.grpc-microservices.governance]
week: [14, 16]
---
# gRPC 与微服务面试题
## 基础
### 【基础】gRPC 和 REST 有什么区别?
**参考答案**:gRPC 基于 HTTP/2,用 Protobuf 二进制序列化,有 IDL 和代码生成,强类型契约,支持流式调用;REST 通常基于 HTTP/1.1 + JSON,可读性好、浏览器原生支持、生态通用。内部服务之间、追求性能和契约时用 gRPC;对外开放、前端直接调用用 REST。
**追问**:浏览器能直接调用 gRPC 吗?→ 不能直接调用(浏览器无法完全控制 HTTP/2 帧),需要 gRPC-Web + 代理,或者网关把 gRPC 转成 HTTP/JSON(如 grpc-gateway)。
### 【基础】gRPC 有哪几种调用方式?
**参考答案**:一元调用(一请求一响应)、服务端流(一请求多响应,如推送进度)、客户端流(多请求一响应,如分片上传)、双向流(如聊天)。
**追问**:流式调用适合什么场景?→ 服务端流适合推送进度、大模型逐字输出;客户端流适合上传大量数据;双向流适合实时交互。流式调用的负载均衡和重试比一元调用复杂。
### 【基础】Protobuf 字段编号有什么规则?
**参考答案**:编号决定序列化后的字段标识,发布后不能修改、不能复用;删除字段用 `reserved` 保留编号和名字;新增字段使用新编号,对老客户端兼容(老客户端会忽略未知字段)。1~15 编码只占一个字节,留给高频字段。
**追问**:删除一个字段要怎么做?→ 不要复用它的编号和名称,用 `reserved` 声明保留,防止以后被误用导致新旧版本解析错乱。
### 【基础】微服务相比单体有什么优缺点?
**参考答案**:优点是独立部署、独立扩容、技术栈可以不同、故障隔离、团队可以独立开发。缺点是网络调用带来的延迟和失败、分布式事务和数据一致性、运维和监控复杂度、调试和排查困难。团队小、业务早期通常模块化单体更合适。
**追问**:团队只有几个人,适合微服务吗?→ 一般不适合。先做模块化单体(模块边界清晰、独立的数据访问层),等团队和业务规模需要独立部署和扩展时再拆。
## 进阶
### 【进阶】什么是服务雪崩?怎么防止?
**参考答案**:一个下游服务变慢,调用方的线程 / 连接被占住,进而调用方也变慢,最终一层层传导导致整个系统不可用。防止:所有调用设超时;熔断故障的下游;限流保护自己;降级非核心功能;隔离资源(不同下游用不同的连接池 / 并发限制);重试要有限次数并退避。
**追问**:限流和熔断的区别?→ 限流保护自己,限制进入的请求量;熔断保护调用方,下游故障时快速失败,不再持续调用。
### 【进阶】讲一下熔断器的状态机。
**参考答案**:关闭状态正常放行并统计失败;失败达到阈值(次数或比例)后打开,直接返回失败;冷却时间后进入半开,放少量请求试探;试探成功则关闭,失败则重新打开。只把下游故障(超时、不可用)计为失败,业务错误不算。
**追问**:半开状态放行多少请求?→ 少量探测请求(一个或几个),成功率达标才关闭熔断器,否则重新打开;具体阈值根据业务配置。
### 【进阶】重试要注意什么?
**参考答案**:只重试可重试的错误(如 `Unavailable`)和幂等操作;指数退避加随机抖动;限制次数,并受整体截止时间约束;只在一层做重试,避免逐层放大;熔断打开时不重试;非幂等操作需要幂等键。
**追问**:什么样的请求可以重试?→ 幂等的请求,以及明确可重试的错误(超时、连接失败、`UNAVAILABLE`);非幂等请求要配合幂等键才能重试。
### 【进阶】分布式事务有哪些方案?怎么选?
**参考答案**:2PC 强一致但性能差、协调者单点,很少用于互联网业务;TCC 通过 Try/Confirm/Cancel 预留资源,一致性较好但业务侵入大;Saga 把长事务拆成本地事务 + 补偿,适合流程长的业务;可靠消息(发件箱 + MQ + 幂等)实现最终一致,最常用。一般优先“可靠消息 + 对账”,对资金等强一致场景再考虑 TCC。
**追问**:Saga 的补偿失败怎么办?→ 补偿操作本身要幂等、可重试;多次失败后进入人工处理队列并告警,所有步骤记录状态便于对账。
## 场景题
### 【场景题】订单服务调用库存服务,库存服务偶尔超时,应该怎么处理?
**参考答案**:设置合理的超时(小于订单接口自身的超时);扣库存接口要求幂等(带订单号作为幂等键),这样超时后可以安全重试一两次;超时结果不确定时,不能直接认为失败——通过查询接口或后续对账确认;持续超时时熔断,下单接口返回“系统繁忙”;排查库存服务本身的慢查询或资源问题。
**追问**:超时时间怎么设置?→ 根据下游的 P99 延迟设定,并且上游总超时要大于下游超时之和(或通过 deadline 传递剩余时间),避免上游已经放弃,下游还在执行。
### 【场景题】团队想把单体系统改成微服务,你怎么推进?
**参考答案**:先明确要解决的问题,能用模块化、读写分离、缓存解决的不急于拆;选边界清晰、需求明确的模块先拆;用绞杀者模式:先在单体内抽接口,再实现远程服务,用开关灰度切流,随时可回退;数据分库通过双写或 CDC 迁移并校验;同时建设超时熔断、链路追踪、集中日志等基础设施。详见[单体拆分场景](scenarios/monolith-split.md)。
**追问**:拆分时数据怎么迁移?→ 先拆代码不拆库,再拆库:新服务通过接口访问数据;数据迁移用双写或 CDC 同步,校验一致后切换读,再切换写。
### 【场景题】一个请求经过 5 个服务,用户反馈偶尔很慢,怎么定位?
**参考答案**:靠链路追踪:入口生成 trace ID,各服务通过 OpenTelemetry 记录 span 并传递上下文,在追踪系统里按耗时筛选慢请求,看是哪一个 span 慢;再结合该服务的日志(带 trace ID)和指标(CPU、GC、连接池、数据库慢查询)找原因。没有追踪系统时,至少要在日志里统一打印请求 ID。
**追问**:没有链路追踪系统怎么办?→ 至少在入口生成请求 ID,所有服务透传并写进日志,用日志平台按请求 ID 搜索,并在每个服务记录调用耗时。