引言:一场真实的雪崩事故

2018年某电商平台的双11大促,凌晨2点,一场噩梦开始了:

2:00 - 商品详情服务的MySQL数据库出现慢查询,响应时间从10ms飙升到5秒

2:02 - 商品服务的Tomcat线程池被耗尽(200个线程全部阻塞在数据库查询上)

2:03 - 订单服务调用商品服务超时,订单服务的线程池也被耗尽

2:04 - 用户服务调用订单服务超时,用户服务也挂了

2:05 - 整个系统瘫痪,首页无法访问,交易全部失败

损失:10分钟内损失订单超过5000万元

这就是微服务雪崩效应的真实案例。

一个数据库的慢查询,像多米诺骨牌一样,导致整个系统崩溃。

今天我们就来学习如何用Sentinel防止这种雪崩的发生。


场景描述:一个典型的调用链路

系统架构

我们有一个典型的微服务架构:

用户请求
    ↓
[用户服务 User-Service]
    ↓ 调用
[订单服务 Order-Service]
    ↓ 调用
[商品服务 Product-Service]
    ↓ 查询
[MySQL数据库]

调用关系

  • 用户服务 → 订单服务:查询用户的订单列表
  • 订单服务 → 商品服务:查询订单中的商品详情
  • 商品服务 → MySQL:查询商品信息

正常流程

  1. 用户访问"我的订单"页面
  2. 用户服务调用订单服务的/order/list接口
  3. 订单服务返回订单ID列表
  4. 订单服务调用商品服务的/product/detail接口,批量查询商品信息
  5. 商品服务查询MySQL数据库
  6. 层层返回,用户看到订单列表

正常情况下的响应时间

  • 商品服务查询数据库:10ms
  • 订单服务调用商品服务:50ms
  • 用户服务调用订单服务:100ms
  • 用户看到页面:200ms

问题分析:雪崩是如何发生的?

故障起点:商品服务的数据库慢查询

某天凌晨,商品服务的MySQL数据库出现了慢查询:

-- 慢查询:全表扫描,耗时5秒
SELECT * FROM product WHERE category = 'phone' AND price > 5000 ORDER BY sales DESC;

直接影响:商品服务的响应时间从10ms飙升到5秒。

第一层传播:商品服务线程池耗尽

商品服务:
- Tomcat最大线程数:200
- 每个请求等待5秒
- QPS:100(假设)

结果:
- 5秒内到达的请求 = 100 * 5 = 500个
- 线程池只有200个,300个请求排队等待
- 线程池被耗尽!

症状

  • 商品服务的所有接口都变慢
  • CPU使用率不高(大部分线程在等待数据库)
  • 内存使用率不高
  • 但响应时间极长,接近于不可用

第二层传播:订单服务被拖垮

订单服务调用商品服务,设置的超时时间是3秒:

// 订单服务调用商品服务
@GetMapping("/order/list")
public List<Order> getOrderList(Long userId) {
    List<Order> orders = orderDao.findByUserId(userId);

    // 批量查询商品信息(10个订单,每个订单1个商品)
    for (Order order : orders) {
        Product product = productClient.getProduct(order.getProductId()); // 超时3秒
        order.setProduct(product);
    }

    return orders;
}

问题

  • 每个订单查询一次商品,10个订单 = 10次调用
  • 每次调用超时3秒
  • 10次调用 = 30秒
  • 订单服务的线程被阻塞30秒!

结果

  • 订单服务的线程池也被耗尽
  • 订单服务自身的其他接口也变慢
  • 雪崩继续向上传播

第三层传播:用户服务崩溃

用户服务调用订单服务:

// 用户服务调用订单服务
@GetMapping("/user/orders")
public List<Order> getUserOrders(Long userId) {
    return orderClient.getOrderList(userId); // 超时5秒
}

问题

  • 调用订单服务超时5秒
  • 用户服务的线程也被阻塞
  • 用户服务崩溃

雪崩路径图

         MySQL慢查询(5秒)
                ↓
      商品服务线程池耗尽
                ↓
    订单服务调用超时(3秒 × 10次)
                ↓
      订单服务线程池耗尽
                ↓
    用户服务调用超时(5秒)
                ↓
      用户服务线程池耗尽
                ↓
           整个系统崩溃

关键问题

  1. 同步阻塞:每次调用都是同步等待,线程被阻塞
  2. 无熔断机制:明知道下游服务已经很慢,还继续调用
  3. 资源耗尽:线程池被耗尽,无法处理新请求
  4. 连锁反应:故障沿着调用链向上传播

解决方案:多层熔断隔离

熔断策略设计

核心思想:在每一层服务中,对下游服务进行熔断保护。

[用户服务]
    ↓ 熔断保护
[订单服务]
    ↓ 熔断保护
[商品服务]
    ↓
[MySQL](故障点)

熔断规则

  1. 订单服务对商品服务的熔断

    • 策略:慢调用比例
    • 阈值:RT > 1秒算慢调用,慢调用比例 > 50%
    • 熔断时长:10秒
    • 降级方案:返回默认商品信息
  2. 用户服务对订单服务的熔断

    • 策略:慢调用比例
    • 阈值:RT > 2秒算慢调用,慢调用比例 > 50%
    • 熔断时长:15秒
    • 降级方案:返回"订单服务繁忙"

完整代码实现

1. 商品服务(Product-Service)

商品服务本身不做熔断,但可以做限流保护,防止被压垮。

@RestController
@RequestMapping("/product")
public class ProductController {

    @Autowired
    private ProductService productService;

    @GetMapping("/detail/{id}")
    @SentinelResource(value = "getProductDetail")
    public Product getProductDetail(@PathVariable Long id) {
        // 查询数据库(可能很慢)
        return productService.getProductById(id);
    }
}

限流规则

@Configuration
public class SentinelConfig {

    @PostConstruct
    public void initFlowRule() {
        List<FlowRule> rules = new ArrayList<>();

        // 限流:QPS不超过200
        FlowRule rule = new FlowRule();
        rule.setResource("getProductDetail");
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setCount(200);
        rules.add(rule);

        FlowRuleManager.loadRules(rules);
    }
}

2. 订单服务(Order-Service)

订单服务对商品服务进行熔断保护

@Service
public class OrderService {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private RedisTemplate<String, Product> redisTemplate;

    /**
     * 查询订单列表
     */
    public List<OrderVO> getOrderList(Long userId) {
        // 1. 查询订单
        List<Order> orders = orderDao.findByUserId(userId);

        // 2. 批量查询商品信息(带熔断保护)
        List<OrderVO> orderVOList = new ArrayList<>();
        for (Order order : orders) {
            OrderVO vo = new OrderVO();
            vo.setOrderId(order.getId());
            vo.setProductId(order.getProductId());

            // 调用商品服务(带熔断)
            Product product = getProductWithCircuitBreaker(order.getProductId());
            vo.setProduct(product);

            orderVOList.add(vo);
        }

        return orderVOList;
    }

    /**
     * 调用商品服务(带熔断保护)
     */
    @SentinelResource(
        value = "callProductService",
        blockHandler = "productServiceBlockHandler",
        fallback = "productServiceFallback"
    )
    public Product getProductWithCircuitBreaker(Long productId) {
        // 调用商品服务
        String url = "http://product-service/product/detail/" + productId;
        return restTemplate.getForObject(url, Product.class);
    }

    /**
     * 熔断降级处理:返回缓存数据
     */
    public Product productServiceBlockHandler(Long productId, BlockException ex) {
        System.out.println("商品服务熔断,返回缓存数据");

        // 从Redis获取缓存
        String key = "product:" + productId;
        Product product = redisTemplate.opsForValue().get(key);

        if (product != null) {
            return product;
        }

        // 缓存也没有,返回默认数据
        Product defaultProduct = new Product();
        defaultProduct.setId(productId);
        defaultProduct.setName("商品信息暂时无法获取");
        defaultProduct.setPrice(0.0);
        return defaultProduct;
    }

    /**
     * 异常降级处理
     */
    public Product productServiceFallback(Long productId, Throwable ex) {
        System.out.println("商品服务调用异常:" + ex.getMessage());
        return productServiceBlockHandler(productId, null);
    }
}

熔断规则配置

@Configuration
public class OrderSentinelConfig {

    @PostConstruct
    public void initDegradeRule() {
        List<DegradeRule> rules = new ArrayList<>();

        // 对商品服务的熔断规则
        DegradeRule rule = new DegradeRule();
        rule.setResource("callProductService");
        rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); // 慢调用比例
        rule.setCount(1000); // RT > 1秒算慢调用
        rule.setSlowRatioThreshold(0.5); // 慢调用比例50%
        rule.setMinRequestAmount(5);
        rule.setStatIntervalMs(10000);
        rule.setTimeWindow(10); // 熔断10秒

        rules.add(rule);
        DegradeRuleManager.loadRules(rules);
        System.out.println("✅ 订单服务熔断规则已加载");
    }
}

3. 用户服务(User-Service)

用户服务对订单服务进行熔断保护

@Service
public class UserService {

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 获取用户订单列表
     */
    @SentinelResource(
        value = "callOrderService",
        blockHandler = "orderServiceBlockHandler",
        fallback = "orderServiceFallback"
    )
    public List<OrderVO> getUserOrders(Long userId) {
        // 调用订单服务
        String url = "http://order-service/order/list?userId=" + userId;
        return restTemplate.getForObject(url, List.class);
    }

    /**
     * 订单服务熔断降级
     */
    public List<OrderVO> orderServiceBlockHandler(Long userId, BlockException ex) {
        System.out.println("订单服务熔断,返回友好提示");

        // 返回空列表 + 提示信息
        List<OrderVO> emptyList = new ArrayList<>();
        OrderVO tip = new OrderVO();
        tip.setMessage("订单服务繁忙,请稍后重试");
        emptyList.add(tip);
        return emptyList;
    }

    /**
     * 订单服务调用异常
     */
    public List<OrderVO> orderServiceFallback(Long userId, Throwable ex) {
        System.out.println("订单服务调用异常:" + ex.getMessage());
        return orderServiceBlockHandler(userId, null);
    }
}

熔断规则配置

@Configuration
public class UserSentinelConfig {

    @PostConstruct
    public void initDegradeRule() {
        List<DegradeRule> rules = new ArrayList<>();

        // 对订单服务的熔断规则
        DegradeRule rule = new DegradeRule();
        rule.setResource("callOrderService");
        rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
        rule.setCount(2000); // RT > 2秒算慢调用
        rule.setSlowRatioThreshold(0.5);
        rule.setMinRequestAmount(5);
        rule.setStatIntervalMs(10000);
        rule.setTimeWindow(15); // 熔断15秒

        rules.add(rule);
        DegradeRuleManager.loadRules(rules);
        System.out.println("✅ 用户服务熔断规则已加载");
    }
}

故障注入测试

测试目标

验证当商品服务出现慢查询时,熔断机制能否有效防止雪崩。

测试步骤

1. 模拟商品服务慢查询

在商品服务中添加一个开关,可以模拟慢查询:

@RestController
@RequestMapping("/product")
public class ProductController {

    // 故障注入开关
    private volatile boolean slowQuery = false;

    @GetMapping("/detail/{id}")
    @SentinelResource(value = "getProductDetail")
    public Product getProductDetail(@PathVariable Long id) throws InterruptedException {
        // 模拟慢查询
        if (slowQuery) {
            Thread.sleep(5000); // 5秒
        }

        return productService.getProductById(id);
    }

    // 故障注入接口
    @PostMapping("/fault/slow")
    public String enableSlowQuery(@RequestParam boolean enable) {
        this.slowQuery = enable;
        return "慢查询已" + (enable ? "开启" : "关闭");
    }
}

2. 压测工具

使用JMeter或wrk进行压测:

# 使用wrk压测
wrk -t10 -c100 -d60s http://localhost:8080/user/orders?userId=1

3. 开启故障注入

# 开启商品服务慢查询
curl -X POST "http://localhost:8001/product/fault/slow?enable=true"

4. 观察日志

商品服务日志

⚠️  慢查询:耗时5000ms
⚠️  慢查询:耗时5000ms
⚠️  慢查询:耗时5000ms

订单服务日志

⚠️  调用商品服务,耗时5100ms
⚠️  调用商品服务,耗时5100ms
🔴 商品服务熔断,返回缓存数据  ← 熔断生效!
🔴 商品服务熔断,返回缓存数据
🔴 商品服务熔断,返回缓存数据

用户服务日志

✅ 调用订单服务成功,耗时150ms  ← 订单服务因为熔断了商品服务,自身正常
✅ 调用订单服务成功,耗时150ms
✅ 调用订单服务成功,耗时150ms

测试结果对比

场景无熔断有熔断
商品服务响应时间5秒5秒
订单服务响应时间30秒(超时)150ms ✅
用户服务响应时间超时200ms ✅
用户体验白屏/报错正常(部分数据降级) ✅
系统稳定性全链路崩溃 ❌故障隔离 ✅

效果验证:雪崩被阻止了

验证点1:订单服务未被拖垮

虽然商品服务响应慢,但订单服务:

  • 检测到商品服务响应慢(RT > 1秒)
  • 慢调用比例超过50%
  • 自动熔断,停止调用商品服务
  • 返回缓存数据或默认值
  • 订单服务自身保持正常响应时间

验证点2:用户服务未受影响

用户服务调用订单服务:

  • 订单服务因为熔断了商品服务,响应时间正常(150ms)
  • 用户服务不需要熔断
  • 用户体验良好

验证点3:故障自动恢复

当商品服务的慢查询问题解决后:

  • 熔断时长到期(10秒)
  • 熔断器进入半开状态
  • 探测请求成功
  • 自动恢复正常调用

完整的防御体系

         MySQL慢查询(5秒)
                ↓
      商品服务响应慢(5秒)
                ↓
    订单服务检测到慢调用
                ↓
         🛡️ 熔断器开启  ← 雪崩被阻止!
                ↓
    订单服务返回降级数据
                ↓
      订单服务保持正常(150ms)
                ↓
    用户服务调用订单服务正常
                ↓
           用户体验良好

最佳实践总结

1. 多层防护原则

第一层:限流(保护自己)
第二层:熔断(保护自己不被依赖服务拖垮)
第三层:降级(提供有限服务)
第四层:超时(避免无限等待)

2. 熔断规则配置建议

层级熔断阈值熔断时长理由
靠近底层更严格(RT 1秒)更短(10秒)故障源头,快速隔离
靠近上层更宽松(RT 2秒)更长(15秒)给下游更多恢复时间

3. 降级数据准备

  • 优先级1:缓存(Redis、本地缓存)
  • 优先级2:静态数据(配置、数据库)
  • 优先级3:默认值/空对象
  • 优先级4:友好提示

4. 监控与告警

关键指标

  • 熔断次数:sentinel_degrade_total
  • 慢调用比例:sentinel_slow_ratio
  • 降级返回率:sentinel_fallback_ratio

告警规则

# 熔断频繁告警
- alert: HighDegradeRate
  expr: rate(sentinel_degrade_total[5m]) > 10
  annotations:
    summary: "服务熔断频繁"

5. 压测验证

必做的压测

  1. 正常压测:验证服务承载能力
  2. 故障注入:模拟依赖服务故障
  3. 雪崩测试:验证熔断是否生效
  4. 恢复测试:验证熔断器自动恢复

总结

本文我们通过一个完整的微服务调用链路案例,学习了如何防止服务雪崩:

  1. 雪崩原理:故障沿着调用链向上传播,导致连锁反应
  2. 核心问题:同步阻塞、无熔断机制、资源耗尽
  3. 解决方案:多层熔断隔离,每一层对下游进行熔断保护
  4. 降级策略:返回缓存、默认值、友好提示
  5. 故障注入测试:模拟真实故障,验证熔断效果
  6. 最佳实践:多层防护、监控告警、压测验证

核心要点

熔断降级不是"放弃",而是"断臂求生"。通过主动熔断故障服务,可以保护整个系统的稳定性,避免雪崩效应。

第三阶段完结

恭喜你完成了Sentinel熔断降级篇的学习!我们系统地学习了:

  • 熔断原理和状态机
  • 三种熔断策略(慢调用比例、异常比例、异常数)
  • 降级规则和自定义降级处理
  • 半开状态和恢复机制
  • 防止微服务雪崩的完整方案

下一阶段预告

在第四阶段(进阶特性篇),我们将学习:

  • 系统自适应保护:Load、CPU、RT全方位防护
  • 热点参数限流:保护热点数据
  • 黑白名单与授权规则
  • 集群流控:跨实例的流量管理
  • 网关流控:统一流量入口的防护
  • 动态规则配置:从硬编码到配置中心

敬请期待!