回顾:当前链路的风险
到上一篇为止,我们的微服务调用链路是这样的:
外部请求 → Gateway(8080) → order-service(8082) → [Feign] → payment-service(8081/8083)
这条链路跑起来很顺畅,但它有一个致命假设:payment-service 永远可用。
真实生产环境不是这样的。payment-service 可能因为数据库慢查询、第三方支付接口超时、或者单纯流量太大而响应变慢。一旦它慢了,order-service 调用它的线程就会卡住;新的请求还在不断进来,线程越积越多,最终 order-service 也被拖垮。然后调用 order-service 的上游跟着遭殃——雪崩来了。
雪崩效应
想象一个供应链系统:采购服务调入库服务,入库服务调校验服务。如果校验服务因为某个外部接口超时,响应从 50ms 变成 5s:
- 入库服务调用校验的线程开始堆积
- 入库服务的线程池用完,开始拒绝新的请求
- 采购服务调用入库也失败了
- 整个系统从前到后全部瘫痪
问题的根源不是某一个服务挂了,而是挂掉的那一个没有被隔离——它的故障沿着调用链传染给了所有依赖它的服务。
Sentinel 就是来切断这种传染的。
Sentinel 是什么
Sentinel 是阿里巴巴开源的流量控制与熔断降级组件。它的名字直译就是「哨兵」——站在流量入口,决定哪些请求放行、哪些请求拒绝、哪些请求降级处理。
它提供三种核心能力:
| 能力 | 问题 | 手段 |
|---|---|---|
| 熔断 | 下游服务不可用时还不断发请求 | 检测失败率 → 打开断路器 → 快速失败 |
| 限流 | 流量超过系统承载能力 | 设定 QPS 阈值,超出的直接拒绝 |
| 降级 | 服务不可用时要给用户一个交代 | 返回预设的兜底结果,不抛异常 |
更关键的是:Sentinel 提供了一个可视化的 Dashboard 控制台,你可以在上面实时修改规则,不需要重启服务。熔断阈值从 50% 调到 30%,点一下发布,立刻生效。
核心概念
熔断(Circuit Breaker)
断路器有三种状态:
- 关闭(Closed):正常状态,请求正常通过。Sentinel 在后台统计每个资源的失败比例
- 打开(Open):失败比例达到阈值,断路器打开,所有对该资源的调用直接拒绝,不再发给下游
- 半开(Half-Open):一段时间后,断路器放一个请求过去试探。如果成功,回到关闭状态;如果失败,继续打开
这就是物理世界中保险丝的逻辑:正常导通,电流过大时断开,过一会试探性恢复。
限流(Rate Limiting)
设定每秒钟最多通过多少个请求,超过的直接拒绝。比如支付接口最多承受 100 QPS,你设置 80 QPS 作为限流阈值,留 20 的缓冲。限流的关键在于:不是等系统已经扛不住了再动手,而是主动控制流量,让系统始终在安全区间内运行。
降级(Fallback)
当熔断或者限流发生时,你不能直接给用户返回 500 错误。降级就是准备一个「兜底结果」:比如支付服务熔断了,返回「支付系统繁忙,请稍后重试」,并记录订单稍后补扣。用户体验虽然打了折扣,但比直接报错好得多。
动手:给 order-service 加 Sentinel 保护
引入依赖与 Dashboard
在 order-service 的 pom.xml 中加入:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
添加 Sentinel Dashboard 连接配置(application.yml):
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
下载并启动 Sentinel Dashboard(一个独立的 Spring Boot 应用):
java -jar sentinel-dashboard.jar --server.port=8080
默认用户名密码都是 sentinel。
配置熔断规则
在 Feign 接口上配置 fallback:
@FeignClient(name = "payment-service", fallback = PaymentClientFallback.class)
public interface PaymentClient {
@GetMapping("/pay")
String pay();
}
编写 fallback 实现:
@Component
public class PaymentClientFallback implements PaymentClient {
@Override
public String pay() {
return "Payment service is currently unavailable. Please try again later.";
}
}
现在 @FeignClient 的 fallback 会自动生效:当 payment-service 调用失败比例达到阈值,Sentinel 会自动切到 PaymentClientFallback 的兜底实现。
配置熔断规则
在 Sentinel Dashboard 中找到 payment-service 资源,添加降级规则:
- 资源名:
GET:http://payment-service/pay - 降级策略:慢调用比例
- 最大 RT(响应时间):100ms
- 比例阈值:0.5
- 熔断时长:10s
- 最小请求数:5
这条规则的意思是:最近 5 个请求中,如果超过 50% 的响应时间大于 100ms,触发熔断,10 秒内所有请求走 fallback。
验证
启动 payment-service、order-service 和 Sentinel Dashboard。
正常访问 http://localhost:8082/order:
Order created. Payment successful from port: 8081
现在停掉 payment-service,再次访问:
Order created. Payment service is currently unavailable. Please try again later.
注意:返回的不是 500 异常,而是 fallback 里预设的兜底消息。order-service 本身没有崩溃。
打开 Sentinel Dashboard,可以看到 payment-service 的实时流量、熔断状态、以及触发的规则。
小结与下篇预告
这一篇我们给微服务装上了三道保险:
- 理解雪崩效应的传播机制——一个服务慢,所有依赖者一起遭殃
- Sentinel 的三种核心能力:熔断(断路器状态机)、限流(主动控流)、降级(兜底响应)
- 动手:Feign 集成 Sentinel,写 fallback,Dashboard 实时调整规则
- 验证:停掉 payment-service,order-service 不崩溃,返回优雅的兜底信息
现在服务是稳了。但稳只解决了一半的问题:如果 order-service 创建订单和 payment-service 扣款这两件事需要保证原子性怎么办?订单创建成功了但扣款失败了,这笔账怎么算?
下一篇,我们进入分布式事务,用 Seata AT 模式解决跨服务的数据一致性问题。