上个月帮一个团队做接口审查,发现他们的“RESTful 接口”几乎所有请求都是 POST,HTTP 状态码一律返回 200,错误信息全部塞在响应体里。更麻烦的是,同一套服务内部调用用的却是 Dubbo RPC,两套接口各自为政,文档对不上,排查线上问题全靠人肉翻日志。这个场景我相信不少做微服务的同学都遇到过。
微服务接口设计这件事,难的不是某个单一技术点,而是你需要在 RESTful 和 RPC 这两套风格之间做取舍,还要让它们在一个系统里长期共存、平滑演进。这篇文章我结合自己多年微服务项目的实际经验,把接口规范设计、兼容方案、生产环境排障和治理方法一次性讲透,适合在微服务架构里做后端开发、架构设计、以及正在从单体转微服务的团队参考。
1. 别急着选框架:先分清 RESTful 与 RPC 的适用边界
很多团队一上来就纠结“我们到底用 RESTful 还是 RPC”,然后开始争论哪个更先进。说实话,这个争论多半是伪命题。RESTful 和 RPC 是两种不同心智模型的接口风格,解决的不是同一类问题的两套方案,选型的第一步应该是想清楚你的调用方是谁、调用频率多高、跨团队边界在哪。
1.1 相同的业务,两种完全不同的表达方式
同样一个“查询订单”的操作,RESTful 的表达方式是面向资源的:
bash复制GET /api/v1/orders/{orderId}
RPC 的表达方式是面向方法的:
protobuf复制service OrderService {
rpc QueryOrder(QueryOrderRequest) returns (QueryOrderResponse);
}
RESTful 把业务对象抽象成资源,用 HTTP 动词表达对这个资源的操作意图。RPC 则是把业务过程抽象成方法调用,客户端像是调用本地函数一样调用远端方法。这两种思维方式决定了后续所有的接口命名、参数设计、错误处理方式。
我见过不少团队用“伪 RESTful”的方式写接口,路径是 /api/order/getOrderById 或者 /api/order/queryOrderList,这本质上是用 RESTful 的壳套 RPC 的思维。不是说这种写法完全不能用,而是它既享受不到 RESTful 的语义化好处,也没有 RPC 的契约严谨性,两边不讨好。
1.2 从协议栈看差异:RESTful 建立在 HTTP 语义上,RPC 建立在二进制管道上
RESTful 接口直接依赖 HTTP 协议——请求方法、状态码、Header、缓存机制都是 HTTP 原生能力。这意味着网关、负载均衡器、监控系统、浏览器这些基础设施天然就能理解你的接口语义。一个 GET 请求可以被 CDN 缓存,一个 404 状态码能被监控系统直接识别,这是 RESTful 的巨大优势。
RPC 框架则通常走自定义协议,传输效率和序列化效率优先。以 gRPC 为例,虽然它底层用的是 HTTP/2,但它的请求路径是方法名而不是资源 URI,状态码也是自定义的 gRPC 状态码,不是 HTTP 状态码。Dubbo 默认的 Dubbo 协议更是完全自定义的 TCP 二进制协议,HTTP 基础设施完全无法感知。
这里有个常见的误解:有人觉得“gRPC 也走 HTTP,所以它也是 RESTful”,这是不对的。判断标准不是底层是不是 HTTP,而是接口是否围绕资源建模、是否使用 HTTP 方法语义。gRPC 本质上是方法调用,即使跑在 HTTP/2 上也是 RPC。
1.3 一张表看清选型边界
| 维度 | RESTful | RPC(gRPC / Dubbo) |
|---|---|---|
| 心智模型 | 资源 + HTTP 动词 | 方法 + 消息契约 |
| 序列化 | JSON 为主 | Protobuf、Hessian 等二进制 |
| 跨语言支持 | 天然支持 | gRPC 好,Dubbo 偏 Java |
| 浏览器调用 | 直接支持 | gRPC 需 gRPC-Web 代理 |
| 调试便捷度 | curl 直接调 | 需要专用工具 |
| 性能 | 中(JSON 解析开销) | 高(二进制 + 连接复用) |
| 契约管理 | OpenAPI 文档 | IDL 文件强约束 |
| 典型场景 | 开放 API、BFF、跨组织对接 | 内部服务间高频调用 |
我的观点很明确:对外暴露的 API(尤其是面向第三方或前端的)优先 RESTful,内部服务间高频调用链路优先 RPC。这不是技术洁癖,而是工程现实——外部调用方不确定会用什么样的技术栈,RESTful 的通用性最好;内部链路性能敏感、调用关系复杂,RPC 的契约和性能优势能显著降低沟通成本。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. RESTful 规范落地:资源建模与状态码语义
选定了 RESTful 之后,真正考验经验的是细节规范的落地。一个团队的接口规范如果只写了“用 RESTful 风格”,那相当于什么都没写。我在这里给出一个可以直接抄的规范框架。
2.1 URI 设计:名词复数、层级嵌套和查询参数边界
资源路径统一用名词复数,不用动词。资源和资源之间的关系用层级嵌套表达,层级不要超过两层。例如:
bash复制# 正确
GET /api/v1/orders
GET /api/v1/orders/{orderId}
GET /api/v1/orders/{orderId}/items
POST /api/v1/orders
PUT /api/v1/orders/{orderId}
DELETE /api/v1/orders/{orderId}
# 错误
GET /api/order/getOrder
POST /api/order/deleteOrder
当查询条件极为复杂时,比如结合了工作流引擎的自定义查询场景(Activiti 这类流程引擎经常需要按流程变量、任务状态、发起人等多维度组合查询),路径上没法穷举,这时候要把这些条件全部放到查询参数里,而不是设计一堆专用 URI:
bash复制GET /api/v1/workflow/tasks?status=approved&assignee=zhangsan&processDefinitionKey=leave&page=1&pageSize=20
查询参数有几个细节需要注意。分页参数要约定上限,比如 pageSize 最大 100,防止有人一次性拉全表。排序字段要做白名单校验,不能直接把前端传的字段名拼到 SQL 里,否则容易出现 SQL 注入或异常报错。时间过滤条件建议用 startTime 和 endTime 这种语义明确的命名,并且统一格式为 ISO 8601 标准。
2.2 方法语义与状态码,别装作没看见
HTTP 方法语义是 RESTful 的根基,但很多团队用得很随意。这里有一个我自己实践的约束清单:
GET:只读查询,必须有幂等性,绝不允许有副作用。POST:创建资源或触发复杂动作,非幂等。PUT:全量更新,幂等。PATCH:局部更新,幂等。DELETE:删除资源,幂等。
动作类操作建议用 POST,路径可以包含动作词,但放在资源名之后,比如取消订单用 POST /api/v1/orders/{orderId}/cancel,而不是 GET /api/v1/orders/cancelOrder。
状态码的使用是 RESTful 接口最容易出问题的地方。很多团队所有请求一律返回 200,业务错误靠响应体里的自定义 code 区分。这种做法的恶果是:网关、监控、ELK 里的日志检索全部失去意义,你没法快速统计错误率,也没法用标准工具做告警。
状态码和业务错误码应该分工明确。HTTP 状态码表达的是“这个请求在协议层面成功还是失败”,业务错误码表达的是“业务逻辑为什么失败”。当业务逻辑失败时,HTTP 状态码用 400(参数错误)、404(资源不存在)、409(冲突)、422(语义错误)这类 4xx 或 5xx,同时响应体里带业务错误码,这样监控和业务排查两边都不耽误。
2.3 错误响应体与幂等性设计的统一约定
错误响应体最好统一结构,这样客户端可以用一套代码处理所有接口的错误。我常用的结构:
json复制{
"code": "ORDER_NOT_FOUND",
"message": "订单不存在",
"requestId": "8f6f9f2e-1234-4f0a-9e91-2267abf9ac31",
"timestamp": "2025-03-15T10:30:00Z",
"details": {}
}
requestId 是排查问题时的关键纽带,日志、指标、链路追踪里都要带上它,客户端反馈问题时只需要报一个 requestId,后端就能精准定位到整条调用链。
幂等性设计是 RESTful 接口生产级必须处理的问题。POST 本身非幂等,所以创建类接口要支持幂等控制。最简单的方式是客户端在请求头里带一个 Idempotency-Key,服务端用这个键做去重,同一个键重复提交只返回第一次的处理结果。PUT、DELETE 天然幂等,但要注意“删除一个不存在的资源”应该返回 404 还是 200,建议统一返回 404,语义更清晰。
2.4 常见“伪 RESTful”写法的危害
这里展开说说伪 RESTful 的危害,因为我在实际项目里踩过太多次。
第一种是 GET 请求带请求体。HTTP 规范不支持 GET 带 body,虽然有些框架能容忍,但网关和代理可能会直接丢弃,最终表现为线上偶发 405 或请求丢失。有人是通过“GET 传 JSON 字符串参数”这种方式绕过,这会让 URL 变得极长,超出某些服务器默认的 URL 长度限制。
第二种是把查询参数做成嵌套 JSON。比如 GET /api/v1/orders?filter={"status":"paid"},看着灵活,实际上服务端解析成本高,也没有标准校验方案,客户端想要构造合法请求需要先读文档。更好的方式是展开成 status=paid 这种扁平参数。
第三种是滥用嵌套资源。资源层级超过三层,比如 /api/v1/projects/{projectId}/modules/{moduleId}/files/{fileId}/comments,这种设计会让路径极其笨重。更合理的做法是把最内层资源设计成顶层资源,通过查询参数定位上级资源:GET /api/v1/comments?fileId=xxx。
3. RPC 框架选型与生产配置
RPC 这块的选型比较集中,Java 生态以 Dubbo 为主,云原生和跨语言场景以 gRPC 为主。等选型定了之后,真正的战斗是在超时、重试、连接管理这些生产配置上。
3.1 gRPC、Dubbo、Thrift 横向对比
| 维度 | gRPC | Dubbo | Thrift |
|---|---|---|---|
| 底层协议 | HTTP/2 | Dubbo 协议 / HTTP / gRPC | 自定义 TCP 协议 |
| 序列化 | Protobuf | Hessian / JSON / Protobuf | Thrift 二进制 |
| 跨语言 | 好 | 一般(主要 Java) | 好 |
| 流式通信 | 支持四种模式 | 有限支持 | 支持 |
| 服务治理 | 需搭配 Envoy 等外部组件 | 内置完善(注册中心、负载均衡、熔断) | 需自行实现 |
| 生态成熟度 | 云原生事实标准 | Java 微服务国内主流 | 较低 |
Dubbo 在国内 Java 微服务里的地位不用多说,Spring Cloud Alibaba + Dubbo 的组合在大量企业里是标准配置。如果你在用“若依微服务”这类脚手架,大概率也是 Dubbo 或 Feign 混用的场景。gRPC 则更适合多语言团队、云原生环境,或者需要流式传输的场景。
我个人的建议是:团队以 Java 为主、治理需求重,选 Dubbo;团队跨语言、或已经上了 Kubernetes + Service Mesh,选 gRPC。两者也并不是水火不容,后面第 4 章我会讲共存方案。
3.2 Protobuf 的消息兼容规则
用 gRPC 必须接受一个事实:Protobuf 的兼容性规则是整个 RPC 契约管理的基石。这里有几条铁律:
- 字段编号一旦分配,永不修改、永不删除。删除字段要用
reserved关键词标记。 - 新增字段使用全新的编号,不要复用旧编号。
- 不要修改已有字段的类型。比如把
int32改成int64在二进制层面可能能解析,但语义会发生不可预测的变化。 - 枚举值不能改编号,
enum里新增值要位运算预留空间。 oneof里的字段编号不能与其他字段冲突。
为什么这些规则重要?因为微服务滚动发布时,服务提供方和消费方可能同时存在两个版本。如果新版 proto 在语义上不兼容,老版本服务端收到新版本客户端的请求,轻则解析失败,重则静默丢字段。Protobuf 的设计哲学是容错,未知字段会被保留透传,这给了你滚动发布的缓冲时间。
3.3 超时、重试与连接池配置
RPC 调用的超时配置,是我在咨询和排障中见到问题最多的区域。很多人直接使用框架默认配置,结果线上出现“请求卡死 30 秒才报错”的惨状。cannot finish rpc call in 30 seconds 这类错误就是典型表现。
超时配置要分三层:
- 连接超时(connect):建立 TCP 连接的最大等待时间,建议 500ms 以内。
- 请求超时(request):整个 RPC 调用的最大处理时间,核心链路建议 1-3 秒,非核心链路可以放宽到 5-10 秒。
- 读取超时(read):等待对端返回数据的最大时间,通常和请求超时配成一致。
重试策略的核心原则:只有幂等请求才能自动重试,重试次数最多 1 次,且必须配合指数退避。很多线上故障是重试风暴引起的——一个下游服务慢了,上游所有调用方同时开始重试,直接把下游打垮。正确的做法是默认不重试,对查询类型接口允许最多一次重试,对写接口一律不重试,靠业务补偿或人工介入。
连接池的配置也容易被忽视。gRPC 的 Channel 是一个连接池,不要每次请求都新建 Channel,这是性能大忌。Dubbo 的连接数默认配置在大多数场景下够用,但要关注消费者连接数上限,避免一个消费者把提供者连接数打满。
3.4 服务发现与负载均衡的落地方案
RPC 框架的服务发现基本上都是“客户端发现”模式:服务提供方启动时向注册中心注册自己的地址,消费方从注册中心订阅可用节点列表,然后在本地做负载均衡。注册中心选择上,Nacos 在国内 Java 生态里接受度最高,支持 AP 与 CP 模式切换;Zookeeper 也是老牌选择,但 CP 模型在高并发场景下容易有性能问题。
负载均衡策略里,一致性哈希适合带状态的服务(比如基于本地缓存的节点),但扩容缩容时会有流量倾斜的问题,需要结合虚拟节点和预热机制。最少活跃调用数策略(LeastActive)在多数业务场景下表现最稳定,因为它能让慢节点自动少接流量。如果你的服务是无状态的,默认的随机或轮询就够了,不用为了追求高端而引入不必要的复杂度。
4. 双栈共存:RESTful 与 RPC 的兼容与平滑演进
现实中的微服务系统很少是单一风格贯穿到底的。历史包袱、团队差异、外部依赖限制,这些都会让一个系统里同时存在 RESTful 和 RPC 接口。我在下面给出几种经过验证的共存方案。
4.1 为什么一个系统里会出现两套接口风格
比较典型的原因有三个。第一是系统从单体演进而来,老模块对外提供的是 RESTful API,新拆分的服务之间为了性能走了 RPC。第二是团队历史上用了两套技术栈,一部分用 Spring Cloud 全家桶(Feign 走 HTTP),一部分用 Dubbo。第三是外部合作方只接受 HTTP 协议,你内部再想用 gRPC 也没用,对外必须提供 RESTful 门面。
共存本身不是问题,问题在于怎么保证两套协议下业务行为一致。如果两个入口各写一套逻辑,代码重复不说,后续维护也一定会出现一个改了另一个没改的情况。
4.2 协议转换网关与双发布策略
方案一:协议转换网关。所有外部请求统一走 RESTful API 网关,网关内部根据路由规则把请求转成内部 RPC 调用。这种模式适合“对外开放 RESTful,对内优化为 RPC”的场景。Spring Cloud Gateway 可以配合 Dubbo 的泛化调用实现协议桥接,gRPC 场景则可以用 Envoy 做 HTTP/1.1 到 HTTP/2 的转换再转发到 gRPC 服务。
方案二:双发布。服务同时暴露 RESTful 和 RPC 两套接口,内部服务间调用优先走 RPC,外部访问走 RESTful。这是我在大型项目里用得最多的方案。关键约束是:RESTful Controller 层和 RPC Provider 层都不能写业务逻辑,业务逻辑统一放 Service 层,两个入口只是协议适配层,这样行为一致性就有了结构上的保障。
text复制外部调用方 -> API 网关 -> Controller(协议适配) \
-> Service(业务逻辑) -> 存储
内部服务方 -> RPC Consumer -> RPC Provider(协议适配)/
4.3 gRPC-Web 与 JSON-RPC 的兜底方案
如果前端需要直接调 gRPC 后端的接口,浏览器环境下没法直接用 gRPC 的二进制协议,常见兜底是 gRPC-Web。gRPC-Web 通过 Envoy 代理做协议转换,前端可以用支持 gRPC-Web 的客户端库发起调用。需要注意 gRPC-Web 不支持所有 gRPC 特性,服务端流式通信能用,但双向流在浏览器里支持有限。
JSON-RPC 是另一个轻量方案。当调用方不愿意引入 protobuf 编译链,又想要 RPC 风格的接口时,JSON-RPC 2.0 是个务实选择。它的语义和 RESTful 完全不同,请求体是 {"jsonrpc": "2.0", "method": "order.query", "params": {...}, "id": 1}。这套方案适合内部工具类接口,或者团队还没有完整 proto 管理体系的过渡期。但我不建议把它作为对外标准接口,因为错误码、幂等、版本管理这些生态都不够成熟。
4.4 版本管理与兼容性守则
接口版本管理,我按场景分三种方式,各有适用场景:
- URI 版本:
/api/v1/orders、/api/v2/orders。最直观,公开 API 首选。缺点是 URL 会膨胀,废弃时需要重定向。 - Header 版本:
Accept: application/vnd.company.v1+json。适合内部接口精确控制,缺点是不直观,调试时容易忽略。 - 请求体/参数版本:
?version=1或 body 里带version字段。适合复杂领域对象的平滑演进,但语义最弱。
无论用哪种方式,兼容性守则都是一样的:只增不改不删,新增字段必须有默认值兜底,废弃字段先标记 deprecated 再在若干版本后移除,给调用方留足迁移时间。服务端接收未知版本号时,建议默认按最新版本处理,并返回一个 Warning Header 提示客户端升级,而不是直接报错。
5. 生产环境真实故障排查:从一次 RPC 超时说起
接口设计得再规范,生产故障总是会来。这一章我用两个真实高发问题演示排查链路,你会发现很多问题的种子在接口设计阶段就已埋下。
5.1 “cannot finish rpc call in 30 seconds”的定位过程
线上环境某个服务每隔一段时间就会出现这类报错。第一次遇到时,团队的第一反应是“网络有问题”,但网络团队排查后说一切正常。这里的问题核心在于:这个报错只是结果,不是原因,你要顺着调用链一层层看耗时分布。
我的排查链路一般是这样的:
- 看链路追踪系统,找到这批超时请求在哪个服务节点上耗时最长。
- 确认是消费方等待还是提供方处理慢。如果提供方处理时间正常但消费方等到了 30 秒,问题在网络或消费方本身的线程调度。
- 登录提供方服务器,看 GC 日志、线程池活跃线程数、数据库连接池使用率、慢查询记录。
- 查中间件集群的健康状态,比如 Redis、MQ 是否有阻塞。
当时定位下来的根因是:数据库连接池配置偏小,某个高峰期慢查询把连接池占满,服务端线程全部阻塞在等待数据库连接上,客户端的重试机制又叠加了额外压力。解决方式是三管齐下:数据库连接池扩容、慢查询优化、RPC 超时从 10 秒降到 3 秒并加上熔断,让故障尽早暴露而不是拖到超时才爆发。
这里有一个经验:超时时间不是越大越好。超时设得长,等于默认接受系统可以有很长的故障时间窗口;超时设得短,能让故障快速暴露触发熔断降级,反而保护了下游。生产环境不能用默认配置,要根据实际调用链路的 P99 延迟留出合理余量。
5.2 另一类高频问题:curl 56 openssl ssl_read 证书错误
error: rpc failed; curl 56 openssl ssl_read 这类错误在调用外部 HTTPS 接口时很常见。它本身不是微服务特有的问题,但在微服务架构里,一个服务调用另一个服务的 HTTPS 接口时,这类 TLS 错误会表现得非常诡异——有的请求成功有的失败,或者某个时间点之后全部失败。
排查思路要按顺序来:
- 先用
openssl s_client -connect host:port -servername host -showcerts验证服务端证书链是否完整、是否过期。 - 用
curl -v看完整的 TLS 握手过程,确认是握手失败还是读数据阶段失败。 - 确认客户端服务器的系统时间和实际时间一致,证书有效期校验依赖客户端时钟。
- 确认双方 TLS 版本和密码套件是否匹配。比如服务端只支持 TLS 1.2,客户端配置了强制 TLS 1.3,就会握手失败。
我在项目里实际遇到的根因是客户端使用了 JDK 自带 CA 证书库,但目标服务端换用了新 CA 机构签发的证书,JDK 的 truststore 还没更新。这类问题在微服务大规模调用的场景下极其折磨人,因为服务端证书“看起来是没问题的”,浏览器访问也正常,但服务端之间调用却失败。
建议在微服务的基础设施层统一管理 HTTPS 证书和 truststore,用配置中心下发 CA 证书内容,别让每个服务自己各自维护一套。
5.3 流量治理在接口层的落地:熔断、降级、限流
接口设计到这一步,已经不只是“接口长什么样”的问题,而是运行时的自我保护。熔断器我用得最多的是 Sentinel 和 Resilience4j。Sentinel 在国内 Java 生态里表现很出色,和 Dubbo、Spring Cloud 都能无缝集成;Resilience4j 更轻量,适合中大规模项目中做细粒度控制。
熔断有三个状态:关闭、打开、半开。关闭时正常放行;失败率超过阈值进入打开状态,快速失败,不再发起真实调用;经过冷却时间后进入半开状态,放少量请求试探,成功后恢复关闭。熔断配置建议关注三个核心参数:失败率阈值(默认 50%)、滑动窗口大小(统计最近 N 次请求)、打开持续时间。
降级是兜底逻辑。非核心链路的降级方案要提前设计好,比如推荐列表失败返回空列表、配置读取失败使用本地缓存、下单流程里优惠券计算服务挂了直接按无优惠处理。降级的核心是:核心链路必须有降级后的可运行路径,而不是放任异常一路往上抛。
限流主要在网关层做,令牌桶算法是默认选择。Sentinel 支持 QPS 限流、并发线程数限流、热点参数限流。分布式限流可以基于 Redis 实现,但要注意 Redis 本身就是潜在的故障点,需要配上降级策略,比如限流组件不可用时放行还是拒绝,要提前决策。
6. 接口治理方法论:从契约到线上监控
接口做得多了,你会发现接口治理的功夫在接口之外。这一章聊的是支撑接口规范长期有效的方法论。
6.1 契约先行:OpenAPI、Protobuf 与契约测试
接口契约的维护,如果用“写文档”的方式去管,几乎 100% 会腐烂。正确姿势是代码生成:定义文件作为唯一事实源,代码和文档都从定义文件生成。
RESTful 接口用 OpenAPI 3.x 管理契约,写一个 openapi.yaml,用 OpenAPI Generator 生成服务端接口骨架和客户端 SDK。这样服务端接口签名变化时,客户端组件的编译错误会第一时间暴露问题,而不是等联调时才发现。
RPC 接口的契约管理是 proto 文件单独建一个 git 仓库,用 buf 或 protolint 做 lint 和 breaking change 检查,CI 里禁止不兼容变更直接合并。很多团队把 proto 文件跟服务代码放同一个仓库,跨团队协作时改字段编号冲突频繁,问题暴露很晚。
契约测试的意义在于保证“提供方和消费方对接口的理解一致”。Spring Cloud Contract 是 Java 生态的常用方案,Pact 则适合跨语言场景。它们都是消费方先把期望的请求/响应定义成契约,提供方按契约验证并满足这些期望。
6.2 接口评审清单与常见反模式
我建议每个团队在迭代计划里加一道“接口评审”关卡,不需要走很重的流程,用一份清单快速过一遍就行:
- URI 是否表达资源而非操作?
- HTTP 方法语义是否正确?是否有 GET 带请求体的情况?
- 状态码是否按协议语义使用?业务错误码是否统一?
- 写接口是否有幂等控制?
- 是否有分页、排序、过滤参数的上限校验?
- 接口是否有超时、限流、熔断配置?
- 敏感字段是否脱敏?返回体是否包含多余字段?
- 是否考虑兼容性?新字段是否有默认值?
常见的反模式有几个我再强调一下:接口返回整个数据库实体对象、错误信息直接透传数据库异常原文、分页无上限、循环调用远程接口(应该改成批量接口)、接口内部串行调用大量下游但总超时设得很短。
6.3 线上健康度指标:延迟、成功率、错误码分布
接口设计的好坏,最终要用线上数据说话。我目前每个接口都在监控大盘上关注这组指标:
- 延迟分布:P50、P95、P99。P95 能反映大多数用户的体感,P99 反映长尾问题。
- 成功率:按 HTTP 状态码或业务 code 统计,2xx 算成功,业务错误码可以单独归类。
- 错误码分布:同一个 HTTP 500 背后可能有很多种业务错误码,要能下钻分析。
- 调用量变化:接口调用量突增或突降都是告警信号。
日志要结构化输出,格式统一为 JSON,包含 requestId、serviceName、interfaceName、costMs、statusCode、traceId。全量日志在高峰期会打爆磁盘,所以要有采样策略:核心接口全量记录,非核心接口按 1% 采样,错误日志永远全量。链路追踪系统接入是排查问题的基础设施,如果现在还没有,建议优先补齐,它能帮你把一次请求跨多个服务的时间消耗完整串起来。
这组监控体系建好之后,接口设计就不再是“靠 review 讨论出来的主观偏好”,而是有数据支撑的迭代闭环。哪个接口 P99 持续走高,哪个接口错误码分布异常,一眼就能看出来,相应地去调整超时配置、优化查询逻辑、做定向限流,整个系统才会越跑越稳。我自己在实际项目里的经验是:接口规范文档一定要轻,能落成清单就绝不用长篇大论,配合 CI 检查去约束团队行为,比任何形式的“代码规范动员会”都有效。如果你所在团队目前接口风格还比较混乱,建议从本周开始挑一个核心链路,先把 RESTful 的错误码统一和 RPC 的超时重试配置治理起来,这两个动作的投入产出比最高。
