微服务核心01:服务治理
服务治理不是组件清单,而是在微服务调用链变长之后,用发现、控制、观测和发布机制把故障限制在可恢复范围内。
服务治理解决什么问题
服务治理不是”上 Nacos、接网关、配 Sentinel”这么简单。它真正要解决的是:当一个系统被拆成多个服务后,调用关系变长、网络变成常态、局部故障会沿链路传播,系统还能不能被控制住。
服务治理可以拆成四个核心问题:
- 服务在哪里,调用方能不能找到稳定实例
- 调用出问题时,等待、重试和失败能不能被限制住
- 链路变慢时,能不能看清楚到底卡在哪一跳
- 配置和代码变更时,能不能灰度、回滚、止损
这篇不绑定具体框架版本。无论项目用 Spring Cloud、Dubbo、Kubernetes Service,还是公司内部 RPC 框架,底层问题都绕不开这几件事。
更进一步说,服务治理不是请求链路上的某一个组件,而是一套控制回路:先收集运行状态,再根据规则做决策,最后把决策落实到路由、限流、熔断、降级和发布动作里。
场景:支付变慢如何拖垮下单
用户提交订单,请求先到网关,再到订单服务。订单服务要查库存、锁优惠券、创建支付单、写订单表。某天支付服务依赖的第三方接口变慢,从 200ms 抖到 5s。
如果没有治理,订单服务线程会被支付调用占满,网关还在继续放流量,调用方还在不断重试。最后表现出来的可能不是”支付慢”,而是”下单整体不可用”,甚至库存、优惠券这些本来健康的服务也一起被拖垮。
这就是分布式系统最常见的连锁反应:故障不是孤立发生的,故障会沿着调用链传播。
所以服务治理的目标不是保证永远不失败,而是让失败可预期、可隔离、可观测、可恢复。
治理的六大核心机制
| 机制 | 解决的问题 | 关键能力 |
|---|---|---|
| 注册发现 | 服务实例在哪里,调用方怎么找到它 | 服务注册、健康检查、实例摘除 |
| 配置治理 | 超时、开关、降级策略能不能动态调整 | 版本管理、灰度发布、快速回滚 |
| 流量入口 | 外部流量如何统一收口 | 路由转发、认证鉴权、限流灰度 |
| 稳定性保护 | 下游故障时如何止损 | 超时、重试、限流、熔断、隔离、降级 |
| 可观测性 | 出问题时能不能定位 | 日志、指标、链路追踪 |
| 发布治理 | 变更时如何控制风险 | 灰度发布、蓝绿发布、金丝雀发布 |
这些能力可以按”控制面”和”数据面”来理解。控制面负责管理规则,比如服务实例列表、配置版本、限流阈值、熔断开关、灰度比例;数据面负责处理真实流量。很多系统的问题在于控制面看起来很完整,数据面却没有真正执行规则。
治理控制闭环
一次请求经过治理后的路径,不能只画成”用户到服务”这一条线。更完整的视角要同时看控制面和数据面:
真正落地时,不要只盯平均耗时。平均值会掩盖尾部延迟。一个接口平均 80ms,但 P99 到了 3s,在线程池和连接池里已经足够制造排队。
在 Java 项目里,治理配置应该成体系:
order:
remote-call:
timeout-ms: 800
retry: 1
retry-on: ["connect-timeout"]
bulkhead-size: 40
fallback: "accept-order-and-confirm-later"
这不是某个框架的标准配置,只是表达治理思路:先限制等待时间,再控制重试次数,用隔离保护主线程池,最后准备业务能接受的降级路径。
超时与调用链预算
这里面最容易被低估的是”超时”。
- 没有超时,调用方会一直等
- 超时太长,线程池会被占住
- 超时太短,正常抖动也会被误判成失败
超时通常要结合接口耗时分布、业务容忍度和下游容量一起定。
更重要的是,超时还要遵守调用链预算。假设网关给下单接口的整体预算是 1200ms,订单服务自己处理要 200ms,库存和优惠券各 150ms,支付调用就不应该再给 1000ms。否则每一跳都觉得自己”留得不多”,合起来就超过用户能接受的等待时间。
客户端等待:3000ms
网关转发:2500ms
订单服务处理:2200ms
支付单次调用:700ms
支付重试:只允许连接失败重试 1 次
预算要从入口一路分配到下游,越往下越要保守。重试也要区分错误类型:连接失败可以重试,业务失败不能重试;读接口可以谨慎重试,写接口必须先确认幂等。
治理策略之间会互相影响
服务治理不能按组件清单逐个接入,因为策略之间会互相放大或抵消。
最典型的是超时和重试。如果订单服务调用支付服务,单次超时 2s,重试 2 次,那么一次用户请求最坏可能占住线程 6s 以上。入口网关如果超时只有 3s,用户已经拿到失败响应,但订单服务可能还在后台继续重试支付。
另一个容易冲突的是限流和降级。入口限流挡住了请求,但降级逻辑如果还要访问数据库或另一个远程服务,可能把压力转移到新的资源上。所谓”兜底”必须是便宜的、可靠的、可解释的,不然它只是第二条故障链路。
治理策略落地时,每条核心链路都应该写成一张表:
| 调用 | 超时 | 重试 | 隔离资源 | 失败动作 |
|---|---|---|---|---|
| 订单→库存 | 500ms | 0 | stock-pool | 返回库存不足或稍后重试 |
| 订单→支付 | 700ms | 连接失败1次 | pay-pool | 进入异步确认 |
| 订单→积分 | 300ms | 0 | async-queue | 下单成功后补偿 |
这张表比”我们接了 Sentinel / Nacos / SkyWalking”更能说明治理是否真的可控。
常见误区
误区一:上了注册中心就等于完成服务治理
注册发现只解决”找谁调用”。它不解决调用慢、不解决雪崩、不解决灰度发布,也不解决出问题后怎么看。很多团队一开始只接入注册中心和配置中心,等线上故障出现才发现缺的是限流、熔断、链路追踪和告警规则。
误区二:重试总是好事
重试只能处理短暂抖动,不能处理下游已经扛不住的场景。下游慢的时候,无脑重试会把一次失败变成多次压力。尤其是写接口,重试前必须先考虑幂等。
误区三:降级就是返回假数据
降级的本质是保核心链路。比如推荐服务不可用时,可以返回默认列表;积分服务慢时,可以先完成下单,后续异步补偿;但支付确认这种强一致链路不能随便”假成功”。降级策略要按业务后果设计,而不是按技术组件套模板。
项目实践:先补治理底座再拆服务
如果是一个从单体拆出来的 Java 系统,不要第一步就拆很多服务。更稳的顺序是:
- 先把边界拆清楚:订单、库存、用户、支付这些职责是否真的独立
- 再补治理底座:注册发现、配置中心、网关、日志规范、指标规范
- 对核心调用设置超时、限流、熔断和隔离
- 把 traceId 从网关一路传到日志、消息和数据库审计字段
- 发布策略从全量发布改成灰度发布,失败能快速回滚
服务拆得越细,治理成本越高。一个接口如果只是内部方法调用,拆成远程调用后会多出网络延迟、序列化、实例选择、超时、重试、链路追踪和兼容性问题。微服务的价值来自团队边界、弹性扩展和独立发布,不来自”服务数量很多”。
落地时可以准备四张”治理清单”:
- 调用清单:哪些服务调用哪些下游,是否同步、是否写操作、是否允许重试
- 依赖清单:数据库、缓存、消息队列、第三方接口的容量和故障影响面
- 降级清单:每个非核心能力失败时返回什么,是否需要补偿任务
- 发布清单:哪些接口支持灰度,哪些配置能回滚,回滚后数据是否兼容
这几张清单比”用了哪个组件”更能反映治理成熟度。真正的治理不是组件接入完成,而是事故发生时团队知道先关哪个开关、看哪个指标、回滚哪一版。
排查路径:先找哪一段失控
线上出现”接口变慢”时,通常按这条线看:
- 入口流量有没有突增,网关限流是否触发
- 订单服务自己的 CPU、线程池、连接池是否饱和
- 下游调用的 P95 / P99 是否异常,而不是只看平均耗时
- 错误码是业务失败、超时、连接失败,还是熔断后的快速失败
- 同一个 traceId 里,耗时到底卡在哪一跳
- 最近是否有配置变更、发布、扩容、数据库变更或第三方接口波动
这个顺序的核心是先定位”慢在哪里”,再判断”为什么慢”。
指标上不要只看服务是否存活,而要看四类信号:流量、错误、延迟、饱和度。流量说明是不是入口突增;错误说明失败类型;延迟尤其要看 P95/P99;饱和度看线程池、连接池、队列、CPU、数据库连接是否接近水位。