#服务雪崩与熔断降级
2019年双十一当天,某电商平台的商品服务因为数据库连接池耗尽开始变慢。
一开始只有部分接口超时,但很快所有依赖商品服务的模块都开始超时,进而拖垮了订单服务、支付服务。
30分钟后,整个系统完全不可用。
技术团队排查后发现:商品服务的慢查询导致线程堆积,线程堆积导致连接池耗尽,连接池耗尽导致所有请求失败。商品服务的故障像滚雪球一样,拖垮了整个系统。
这次故障导致全站宕机2小时,直接损失约2000万元。
【面试官手记】
服务雪崩是分布式系统最危险的场景之一。我面试过的候选人里,能说清楚"雪崩原因"的有50%,能说清楚"熔断机制"的有30%,能说清楚"降级策略"的有20%。服务雪崩的关键词是快速失败 + 快速恢复。
#一、服务雪崩的原因 🔴
#1.1 三大原因
服务雪崩三大原因:
1. 服务提供者不可用
- 硬件故障:服务器宕机、网络抖动
- 软件故障:OOM、Full GC、连接池耗尽
- 流量过大:压垮服务
2. 大量请求积压
- 同步调用阻塞
- 线程池耗尽
- 请求队列满
3. 连锁故障
- A服务慢 → B服务等待 → B服务也慢
- A服务挂 → B服务超时 → B服务也挂
- 级联放大,最终全挂#1.2 雪崩形成过程
雪崩形成过程:
时刻1:商品服务数据库查询变慢(100ms → 10s)
↓
时刻2:商品服务线程池堆积
↓
时刻3:线程池耗尽,新请求被拒绝
↓
时刻4:调用商品服务的订单服务开始超时
↓
时刻5:订单服务线程池也开始堆积
↓
时刻6:订单服务也挂
↓
时刻7:所有服务互相影响,最终全站宕机
耗时:30分钟#1.3 快速失败 vs 等待
快速失败 vs 等待:
等待策略(错误):
- 线程一直等待,不释放
- 导致线程池堆积
- 最终拖垮调用方
快速失败策略(正确):
- 超时立即返回
- 释放线程
- 调用方可以降级处理
建议配置:
- HTTP调用超时:100-500ms
- 数据库查询超时:1-3s
- Redis操作超时:50-200ms#二、熔断器原理 🔴
#2.1 熔断器状态
熔断器三状态:
1. CLOSED(关闭状态)
- 正常请求通过
- 统计失败率
- 失败率超阈值 → OPEN
2. OPEN(打开状态)
- 所有请求直接失败
- 不调用下游服务
- 经过冷却时间 → HALF_OPEN
3. HALF_OPEN(半开状态)
- 允许部分请求通过
- 测试下游服务是否恢复
- 成功 → CLOSED
- 失败 → OPEN#2.2 熔断器实现
// 熔断器实现
@Service
public class CircuitBreaker {
private volatile State state = State.CLOSED;
private AtomicInteger failureCount = new AtomicInteger(0);
private AtomicInteger successCount = new AtomicInteger(0);
private long lastFailureTime;
// 配置
private static final int FAILURE_THRESHOLD = 5; // 失败5次
private static final int SUCCESS_THRESHOLD = 3; // 成功3次恢复
private static final long RECOVERY_TIMEOUT = 60000; // 60秒冷却
public enum State { CLOSED, OPEN, HALF_OPEN }
/**
* 调用服务
*/
public <T> T call(Callable<T> callable) throws Exception {
if (state == State.OPEN) {
// 熔断中,检查是否超时
if (System.currentTimeMillis() - lastFailureTime > RECOVERY_TIMEOUT) {
state = State.HALF_OPEN;
} else {
throw new CircuitBreakerOpenException("熔断器打开,拒绝请求");
}
}
try {
T result = callable.call();
onSuccess();
return result;
} catch (Exception e) {
onFailure();
throw e;
}
}
private void onSuccess() {
failureCount.set(0);
if (state == State.HALF_OPEN) {
if (successCount.incrementAndGet() >= SUCCESS_THRESHOLD) {
state = State.CLOSED;
successCount.set(0);
}
}
}
private void onFailure() {
lastFailureTime = System.currentTimeMillis();
if (state == State.HALF_OPEN) {
// 半开状态失败,重新打开
state = State.OPEN;
} else {
if (failureCount.incrementAndGet() >= FAILURE_THRESHOLD) {
state = State.OPEN;
}
}
}
public State getState() {
return state;
}
}#2.3 Sentinel熔断配置
// Sentinel熔断配置
@Configuration
public class SentinelConfig {
@Bean
public InitFunc> sentinelInitFunc() {
return (element, configProvider, configuration) -> {
// 配置熔断规则
List<DegradeRule> rules = Arrays.asList(
new DegradeRule("orderService")
.setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType())
.setCount(0.5) // 50%错误率触发
.setSlowRatioThreshold(1.0) // 慢请求比例
.setMinRequestAmount(10) // 最小请求数
.setStatIntervalMs(10000) // 10秒窗口
.setTimeWindow(10), // 10秒熔断时间
new DegradeRule("productService")
.setGrade(CircuitBreakerStrategy.SLOW_RATIO.getType())
.setSlowRatioThreshold(0.5) // 50%慢请求触发
.setMinRequestAmount(10)
.setStatIntervalMs(10000)
.setTimeWindow(30) // 30秒熔断时间
);
DegradeRuleManager.loadRules(rules);
};
}
}
// 使用Sentinel
@RestController
public class OrderController {
@Autowired
private SentinelConfigManager sentinelConfig;
@GetMapping("/order/{id}")
@SentinelResource(value = "getOrder",
blockHandler = "getOrderBlockHandler",
fallback = "getOrderFallback")
public Order getOrder(@PathVariable Long id) {
return orderService.getOrder(id);
}
// 降级处理
public Order getOrderFallback(Long id, Throwable ex) {
log.warn("Order服务降级,orderId={}, error={}", id, ex.getMessage());
return new Order(id, "降级订单");
}
// 限流/熔断处理
public Order getOrderBlockHandler(Long id, BlockException ex) {
return new Order(id, "系统繁忙,请稍后再试");
}
}#三、隔离策略 🟡
#3.1 线程池隔离
// 线程池隔离
@Service
public class IsolatedService {
// 商品服务线程池(独立)
private final ExecutorService productExecutor = new ThreadPoolExecutor(
20, 20, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000),
new ThreadFactoryBuilder().setNameFormat("product-pool-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
// 用户服务线程池(独立)
private final ExecutorService userExecutor = new ThreadPoolExecutor(
20, 20, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000),
new ThreadFactoryBuilder().setNameFormat("user-pool-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
public ProductVO getProduct(Long productId) {
return productExecutor.submit(() -> {
return productService.getProduct(productId);
}).get(3, TimeUnit.SECONDS);
}
public UserVO getUser(Long userId) {
return userExecutor.submit(() -> {
return userService.getUser(userId);
}).get(3, TimeUnit.SECONDS);
}
}
// Hystrix线程池隔离
@HystrixCommand(
groupKey = "productGroup",
threadPoolKey = "productPool",
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "20"),
@HystrixProperty(name = "maxQueueSize", value = "1000"),
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "800"),
@HystrixProperty(name = "keepAliveTimeMinutes", value = "1")
}
)
public ProductVO getProduct(Long productId) {
return productService.getProduct(productId);
}#3.2 信号量隔离
// 信号量隔离(轻量级)
@Service
public class SemaphoreIsolationService {
// 信号量隔离:限制并发数
private final Semaphore semaphore = new Semaphore(100, true);
public String callService() {
if (!semaphore.tryAcquire(100, TimeUnit.MILLISECONDS)) {
// 获取失败,快速失败
return "系统繁忙";
}
try {
return remoteService.call();
} finally {
semaphore.release();
}
}
}
// Resilience4j信号量隔离
@Configuration
public class Resilience4jConfig {
@Bean
public BulkheadRegistry bulkheadRegistry() {
return BulkheadRegistry.ofDefaults();
}
@Bean
public RegistryEventPublisher publisher() {
// 配置信号量隔离规则
BulkheadConfig config = BulkheadConfig.custom()
.maxConcurrentCalls(100) // 最大并发数
.maxWaitDuration(Duration.ofMillis(500)) // 等待时间
.build();
return publisher -> publisher
.addConfiguration("default", config);
}
}#四、降级方案 🟡
#4.1 降级策略
降级策略:
1. 读取降级
- 服务不可用 → 返回缓存数据
- 服务慢 → 返回默认值
2. 写入降级
- 服务不可用 → 写入消息队列
- 服务慢 → 异步写入
3. 功能降级
- 非核心功能 → 关闭
- 核心功能 → 简化处理
4. 页面降级
- 静态页面代替动态内容
- 友好错误提示#4.2 降级实现
// 降级实现
@Service
public class ProductService {
@Autowired
private ProductFeignClient productFeign;
@Autowired
private ProductCacheService cacheService;
/**
* 降级方案:缓存兜底
*/
public ProductVO getProduct(Long productId) {
try {
// 尝试调用远程服务
ProductVO product = productFeign.getProduct(productId);
// 更新缓存
cacheService.set(productId, product);
return product;
} catch (Exception e) {
log.warn("获取商品失败,尝试降级,productId={}", productId, e);
// 降级:读缓存
ProductVO cached = cacheService.get(productId);
if (cached != null) {
return cached;
}
// 降级:返回默认值
return getDefaultProduct(productId);
}
}
/**
* 降级:默认值兜底
*/
private ProductVO getDefaultProduct(Long productId) {
ProductVO product = new ProductVO();
product.setId(productId);
product.setName("商品信息加载中");
product.setStatus("UNAVAILABLE");
return product;
}
}
// 使用@FeignClient的fallback
@FeignClient(
name = "product-service",
fallback = ProductFeignClientFallback.class
)
public interface ProductFeignClient {
@GetMapping("/product/{id}")
ProductVO getProduct(@PathVariable("id") Long id);
}
@Component
public class ProductFeignClientFallback implements ProductFeignClient {
@Override
public ProductVO getProduct(Long id) {
ProductVO product = new ProductVO();
product.setId(id);
product.setName("商品信息加载中");
return product;
}
}#五、生产避坑 🟡
#5.1 服务雪崩的五大坑
坑1:没有熔断机制
问题:服务慢的时候仍然调用
场景:依赖服务超时,调用方一直等待
解决方案:
- 使用熔断器
- 配置熔断规则坑2:超时设置过长
问题:超时10秒,导致线程长时间阻塞
场景:网络抖动
解决方案:
- HTTP调用超时:500ms
- 数据库查询:3s
- Redis操作:200ms坑3:没有降级方案
问题:服务不可用时无法降级
场景:商品服务挂,订单页完全不可用
解决方案:
- 提供降级接口
- 返回缓存数据或默认值坑4:线程池不隔离
问题:所有服务共用一个线程池
场景:一个服务慢,影响所有服务
解决方案:
- 按服务隔离线程池
- 使用信号量隔离坑5:没有监控告警
问题:熔断发生时不知道
场景:系统已降级,但用户投诉才发现
解决方案:
- 监控熔断状态
- 监控降级次数
- 设置告警#5.2 雪崩防护检查清单
设计规范:
- [ ] 配置合理的超时时间
- [ ] 使用熔断器
- [ ] 线程池隔离
- [ ] 提供降级方案
配置规范:
- [ ] 熔断错误率阈值
- [ ] 熔断恢复时间
- [ ] 线程池核心线程数
- [ ] 队列容量
监控规范:
- [ ] 监控熔断状态变化
- [ ] 监控降级次数
- [ ] 监控超时次数
- [ ] 监控线程池状态#六、真实面试回放 🟡
面试官:什么是服务雪崩?怎么防止?
候选人(小张):服务雪崩是连锁故障。
A服务挂了,B服务调用A超时,B服务的线程堆积,B服务也挂,最终全挂。
防止方案:
一是熔断器。服务失败率超过阈值,打开熔断,直接返回失败。
二是超时设置。HTTP调用设置500ms超时,不要等太久。
三是降级方案。服务不可用时返回缓存或默认值。
四是线程隔离。按服务分配独立线程池。
面试官:熔断器的状态有哪些?
小张:三个状态:
CLOSED关闭状态,正常请求通过。
OPEN打开状态,所有请求失败。
HALF_OPEN半开状态,放行部分请求测试是否恢复。
面试官:降级方案有哪些?
小张:读取降级返回缓存,写入降级写入MQ,功能降级关闭非核心功能。
【面试官手记】
小张这场面试的亮点:
知道雪崩形成过程
知道熔断器三状态
知道多种降级方案
服务雪崩是P7工程师必备知识点,能完整回答的候选人,说明有高可用架构经验。
服务雪崩防护的核心是快速失败 + 快速恢复。记住三个要点:
- 熔断器:失败率超阈值打开熔断,保护下游
- 降级方案:服务不可用时返回兜底数据
- 隔离策略:线程池隔离,防止故障蔓延
服务雪崩是分布式系统的生死劫,必须提前规划。