活动高峰时,库存服务先慢了。
订单服务调用库存服务,原本 100ms 返回,现在变成 2 秒。订单服务等不住,开始超时;客户端看到失败,又发起重试;订单服务自己也配置了重试;库存服务本来已经吃紧,又被更多重复请求打上来。
几分钟后,问题已经不只是库存服务慢了。订单线程池被占满,网关错误率升高,数据库连接也开始紧张。
这就是服务高并发里很典型的连锁反应:一个局部依赖变慢,最后把整条链路拖下水。
服务保护这件事,平时最容易被忽略。
系统流量不大的时候,限流、熔断、降级看起来像是“以后再补”的东西。直到某个接口被打爆,线程池耗尽,连正常用户都进不来,大家才发现保护策略不是锦上添花。
我现在更愿意提前问一句:如果这个接口突然慢了,谁会被拖住?
如果答案是“整个链路都会等它”,那它就不能裸奔。
真正危险的不是失败,而是大家都在等、都在重试、都在占资源。
一、限流是先承认系统有边界
限流听起来像是在拒绝用户,但它本质上是在保护系统。
任何系统都有容量边界。连接数、线程数、数据库连接池、CPU、IO、下游接口,都不是无限的。如果入口完全不限制流量,超过容量后系统可能不是慢一点,而是整体雪崩。
限流可以按接口、用户、IP、租户、业务场景做。常见算法有固定窗口、滑动窗口、令牌桶、漏桶。
我更关心的是限流后的体验:
- 返回什么错误?
- 能不能提示稍后再试?
- 核心用户和普通流量是否要分级?
- 后台任务和前台请求是否要隔离?
限流不是为了简单拒绝请求,而是把系统能力花在更重要的请求上。
二、熔断和降级是在给系统止损
熔断解决的是下游持续失败或持续变慢的问题。
如果库存服务已经明显不可用,订单服务还不断调用它,只会浪费线程和时间。熔断的作用是短时间内停止调用,让请求快速失败或走备用逻辑。
降级更偏业务取舍。
比如推荐服务不可用时,首页先展示默认内容;评论数暂时查不到时,先隐藏计数;活动排行榜晚一点刷新;非核心统计延迟处理。
这里的关键是提前定义“哪些功能可以降级”。不要等故障发生时才讨论哪个按钮能关、哪个模块能不显示。
成熟系统里,降级开关应该可配置、可灰度、可回滚。
三、超时控制比无限等待更重要
很多系统不是死在失败上,而是死在等待上。
一个接口调用下游,如果没有合理超时,线程可能长时间被占住。高并发下,这会迅速耗尽线程池和连接池。
超时时间不能随便拍脑袋。它要结合接口重要性、下游 P99 延迟、用户体验和整体链路预算。
比如一个页面请求总预算是 1 秒,里面有 5 个下游调用,就不能每个调用都设置 3 秒超时。否则任何一个下游慢了,整个页面都会被拖住。
服务治理里有个很朴素的原则:失败要快,等待要有边界。
四、重试可能会放大故障
重试是为了提高成功率,但在高并发下,它也可能变成事故放大器。
库存服务已经慢了,上游超时后马上重试,相当于给已经吃紧的服务再加一倍压力。如果网关、订单服务、库存客户端每一层都重试,流量会被层层放大。
更稳的做法是:
- 只对幂等操作重试。
- 控制重试次数。
- 使用退避策略。
- 不在每一层都重试。
- 对明显过载的下游先熔断。
重试不是越多越可靠。很多时候,少重试、慢一点重试,甚至不重试,反而更保护系统。
服务层事故经常不是单点失败,而是等待、重试和资源占用互相放大。
线程和连接一直被占着,正常请求也进不来。
下游已经慢了,上游又叠了一层重复流量。
非核心链路跑满资源,把核心交易一起拖住。
五、无状态和隔离让故障有边界
高并发服务想要扩容,最好保持无状态。
无状态意味着请求不依赖某台固定机器,服务实例可以水平扩展,故障实例也能快速摘除。会话、缓存、任务状态这些东西,尽量放到外部存储或专门组件里。
服务隔离也很重要。
核心下单链路和后台报表,不应该共用同一批线程池、连接池和队列。否则一个低优先级任务跑满资源,可能把核心交易接口也拖慢。
隔离不是洁癖,而是为了让故障有边界。
六、可以带走的判断清单
- 入口流量超过系统容量时,要限流。
- 下游持续失败或变慢时,要熔断。
- 非核心功能要提前设计降级开关。
- 所有外部调用都要有合理超时。
- 重试必须有次数、退避和幂等前提。
- 核心链路和非核心链路要做资源隔离。
服务治理的目标不是让每个调用都成功,而是防止一个局部问题拖垮整个系统。
本系列:
- 上一篇:分布式一致性:系统越拆,正确性越贵
- 下一篇:热点和突发流量:真正难的是不均匀
- 系列目录:高并发和海量数据,不只是背几个架构名词
暂无评论