30 · API 与服务通信选型
一句话点题:API 不是「REST 还是 gRPC」的格式选择,而是边界选择。同步还是异步、内部还是外部、强契约还是灵活查询、一次请求还是持续流,这些才决定你该怎么通信。
🧰 技术栈选型篇第 4 章 · 本章只练一件事
04 章 讲过分层、微服务、事件驱动;29 章 讲过异步。现在我们把视角放到服务边界上:两个系统一旦要说话,你就必须选择通信方式、契约、版本、失败处理和权限边界。
开场:通信方式决定耦合方式
同样是「订单告诉库存扣减」,可以有很多做法:
A. 订单同步调用库存 REST API
B. 订单同步调用库存 gRPC
C. 订单发布 OrderCreated 事件,库存异步消费
D. 库存暴露 GraphQL,订单按需查询
E. 库存回调订单 Webhook每种都能工作,但耦合方式完全不同:
- 同步调用:结果清楚,但调用方会被被调方拖慢或拖死。
- 异步事件:解耦,但结果不立即可知,一致性更复杂。
- GraphQL:客户端灵活,但服务端治理和性能更难。
- Webhook:适合外部通知,但重试、签名、幂等必须做。
**架构判断:**先定交互语义,再定协议。不要先说「我们用 gRPC」,而要先说「这条链路是否必须同步知道结果」。
一、第一把刀:同步还是异步
| 通信方式 | 适合 | 代价 |
|---|---|---|
| 同步请求/响应 | 用户正在等结果、需要立即校验、失败要立刻反馈 | 调用链变长,尾延迟(P99)叠加,依赖故障会扩散 |
| 异步消息/事件 | 可稍后完成、要削峰、多个下游订阅 | 状态推进复杂,需要幂等、补偿、积压处理 |
| 流式通信(Streaming) | 持续输出、实时状态、长任务进度 | 连接管理、背压(Backpressure,下游处理不过来时减速)、断线恢复 |
经验规则:
用户必须马上知道「能不能继续」 → 同步
用户只需要知道「已经受理」 → 异步
用户要持续看见变化 → 流式比如抢票系统(案例 01 StarArena):「能不能进入等候室」要同步;「出票通知」可以异步;「排队位置变化」适合流式或轮询。
二、REST、gRPC、GraphQL 不是谁替代谁
| 方式 | 更适合 | 不适合 |
|---|---|---|
| REST(基于资源的 HTTP API) | 对外开放 API、普通 Web / SaaS、易调试、生态通用 | 高频内部调用、强类型契约要求极高的场景 |
| gRPC(高性能远程过程调用) | 内部服务间调用、低延迟、高吞吐、强 IDL(接口定义语言) | 浏览器直连、公开 API、调试门槛低的需求 |
| GraphQL(客户端按需查询) | 多端聚合查询、字段变化频繁、前端需要灵活组合 | 写操作复杂、缓存/权限/限流治理弱的团队 |
| Webhook(反向回调) | 第三方事件通知、支付回调、外部集成 | 需要同步强结果的核心链路 |
| MCP(Model Context Protocol,模型上下文协议) | 给 AI Agent 暴露工具、资源和上下文 | 普通业务服务间通信,或没有 Agent 语义的场景 |
重点不是「哪个先进」,而是边界:
- 对外 API 要优先易理解、稳定、可版本化。
- 内部高频调用可以优先强契约和性能。
- 前端多端聚合可以考虑 GraphQL,但要有治理能力。
- 第三方回调必须考虑签名、幂等、重放攻击。
- Agent 工具接口要把权限和人审写进协议边界(23 章)。
三、契约比协议更重要
API 最大的风险不是 HTTP 还是 protobuf,而是契约不清楚:
| 契约点 | 要写清楚 |
|---|---|
| 输入输出 | 字段含义、必填/可选、单位、枚举 |
| 错误语义 | 哪些可重试?哪些是用户错误?哪些是系统错误? |
| 幂等性 | 同一个请求重放两次会怎样?幂等键在哪里? |
| 版本策略 | 字段怎么新增/废弃?旧客户端多久兼容? |
| 限流与配额 | 谁可以调多少?超过后返回什么? |
| 安全边界 | 鉴权、授权、签名、审计怎么做? |
没有契约治理,REST 会变成乱七八糟的 URL,GraphQL 会变成随意暴露数据库,gRPC 会变成强类型的泥球。
四、内部通信:别让调用链无限变长
微服务系统最常见的性能问题不是单个服务慢,而是调用链扇出:
用户请求
└─ A
├─ B
│ ├─ D
│ └─ E
└─ C
├─ F
└─ G每多一跳,都会增加:
- 网络延迟。
- 超时和重试风暴。
- 依赖故障传播。
- trace 排障成本。
所以内部 API 要配套三件事:
- 超时预算:上游 500ms,不能给每个下游都 500ms。
- 重试纪律:只重试幂等请求,加退避和抖动。
- 降级策略:非核心依赖失败时,返回部分结果或兜底。
这和 12 章 的韧性工程是同一个问题。
五、外部 API:稳定性比优雅更重要
对外 API 一旦发布,就是承诺。要特别关注:
- 向后兼容:新增字段通常安全,删除/改含义危险。
- 错误码稳定:客户会写逻辑依赖你的错误语义。
- 文档与示例:外部开发者看不懂,再优雅也没用。
- 签名与防重放:特别是支付、Webhook、Agent 工具调用。
- 审计与速率限制:出问题后能追踪,被滥用时能限制。
如果是平台型产品,API 不是实现细节,而是产品的一部分。它的版本和兼容策略就是架构边界。
六、一个选型 ADR
md
### ADR-030:内部订单与库存使用 gRPC,支付回调用 Webhook + 幂等
- 背景:订单与库存都在内部网络,调用频繁且需要强契约;支付来自第三方,只能由对方异步通知。
- 选择:内部订单-库存使用 gRPC + protobuf 定义契约;外部支付结果通过 Webhook 接收,签名校验后按 payment_id 幂等推进状态机。
- 放弃:内部接口不直接暴露给浏览器;支付结果不追求同步完成。
- 换来:内部链路契约清晰、性能稳定;外部集成符合支付系统的异步现实。
- 风险:Webhook 重放和乱序需要处理;内部调用链要设置超时预算和 trace。🎯 随堂检验
选择 REST、gRPC、GraphQL、Webhook 之前,最应该先判断什么?
- A哪个名字更流行
- B这条交互是同步还是异步、内部还是外部、需要强契约还是灵活查询、失败后怎么恢复
- C哪个写起来代码最少
第三方支付系统通知你用户已支付,最常见、最合理的通信方式是?
- A让你的订单服务一直同步阻塞等待支付完成
- B支付平台回调 Webhook,你的系统做签名校验、幂等和状态机推进
- C让前端告诉后端已经支付,后端直接相信
本章小结
- 通信方式决定耦合方式:同步、异步、流式分别适合不同交互语义。
- REST、gRPC、GraphQL、Webhook、MCP 各有边界:不是替代关系,而是面向不同场景。
- 契约比协议更重要:字段、错误、幂等、版本、限流、安全必须写清楚。
- 内部调用要防扇出和级联失败:超时预算、重试纪律、降级、trace 是基本功。
- 外部 API 是产品承诺:兼容、文档、错误语义、签名和审计都要严肃对待。
承上启下:到这里,我们已经选了服务怎么写、数据怎么放、中间层怎么协作、服务怎么通信。下一章 31 · 云原生与部署平台选型,问题变成:这些东西到底部署在哪里,谁来扩容、发布、隔离和兜底?
相关链接
- 方法论本体:04 · 十大核心架构模式 · 12 · 为失败而设计 · 16 · 安全与多租户架构
- AI 协同:23 · 规格即架构 · 26 · 协作决策树
- 案例对照:StarArena · CodePilot · SyncRoom
💬 评论