Skip to content

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 要配套三件事:

  1. 超时预算:上游 500ms,不能给每个下游都 500ms。
  2. 重试纪律:只重试幂等请求,加退避和抖动。
  3. 降级策略:非核心依赖失败时,返回部分结果或兜底。

这和 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 · 云原生与部署平台选型,问题变成:这些东西到底部署在哪里,谁来扩容、发布、隔离和兜底?


相关链接

💬 评论