系统自适应保护:Load、CPU、RT全方位防护

引言:当整个系统都在危险边缘 在前面的文章中,我们学习了限流和熔断,它们都是针对单个资源的保护: 限流:保护单个接口不被流量压垮 熔断:保护自己不被依赖服务拖垮 但在实际生产中,还有一种更危险的场景: 某天晚上8点,你的电商系统突然收到一波超大流量(可能是爬虫、攻击或者真实用户)。 虽然你对每个接口都做了限流,但这波流量分散在各个接口上: 首页接口:QPS 500(限流阈值600)✅ 搜索接口:QPS 300(限流阈值400)✅ 商品详情:QPS 400(限流阈值500)✅ 下单接口:QPS 200(限流阈值300)✅ 每个接口都没有超过限流阈值,但整个系统的负载已经爆炸了: CPU使用率:95% 系统Load:8.0(4核机器) 平均响应时间:5秒 内存使用率:90% 系统已经濒临崩溃边缘! 这就是单个资源限流的盲区:无法感知系统整体的健康状况。 Sentinel的系统自适应保护(System Adaptive Protection)就是为了解决这个问题。 系统保护的必要性 为什么需要系统级别的保护? 问题1:单个资源限流无法保护整体 ┌─────────────────────────────────────┐ │ 服务器(4核8G) │ ├─────────────────────────────────────┤ │ 接口A:QPS 500/600 ✅ (83%) │ │ 接口B:QPS 300/400 ✅ (75%) │ │ 接口C:QPS 400/500 ✅ (80%) │ │ 接口D:QPS 200/300 ✅ (67%) │ ├─────────────────────────────────────┤ │ 但是... │ │ CPU使用率:95% ❌ │ │ Load:8.0 ❌ (4核机器) │ │ 平均RT:5秒 ❌ │ └─────────────────────────────────────┘ 问题2:流量来源不均匀 ...

2025-11-20 · maneng

实战:防止微服务雪崩传播

引言:一场真实的雪崩事故 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:查询商品信息 正常流程 用户访问"我的订单"页面 用户服务调用订单服务的/order/list接口 订单服务返回订单ID列表 订单服务调用商品服务的/product/detail接口,批量查询商品信息 商品服务查询MySQL数据库 层层返回,用户看到订单列表 正常情况下的响应时间: 商品服务查询数据库:10ms 订单服务调用商品服务:50ms 用户服务调用订单服务:100ms 用户看到页面:200ms 问题分析:雪崩是如何发生的? 故障起点:商品服务的数据库慢查询 某天凌晨,商品服务的MySQL数据库出现了慢查询: -- 慢查询:全表扫描,耗时5秒 SELECT * FROM product WHERE category = 'phone' AND price > 5000 ORDER BY sales DESC; 直接影响:商品服务的响应时间从10ms飙升到5秒。 ...

2025-11-20 · maneng

熔断恢复机制:半开状态详解

引言:熔断器如何"自愈"? 在前面几篇文章中,我们学习了熔断的原理、策略和降级处理。我们知道,当依赖服务出现故障时,熔断器会开启(Open),停止对服务的调用。 但问题来了:熔断器什么时候恢复? 如果依赖服务的故障已经修复了,熔断器还一直开启,那就失去了熔断的意义——我们不能让系统永久降级! 另一方面,如果熔断器贸然恢复,而依赖服务还没完全恢复,可能会再次被拖垮。 Sentinel是如何优雅地解决这个两难问题的? 答案就是:半开状态(Half-Open)。 熔断器的完整生命周期 三种状态回顾 我们在第12篇文章中学习过,熔断器有三种状态: 关闭(Closed):正常状态,所有请求通过 开启(Open):熔断状态,所有请求快速失败 半开(Half-Open):探测状态,试探性地发送请求 状态转换流程 初始状态:Closed(关闭) ↓ 检测到故障(慢调用/异常超过阈值) ↓ Closed → Open(开启) ↓ 等待熔断时长(如10秒) ↓ Open → Half-Open(半开) ↓ 发送探测请求 ↓ 探测请求成功? ├─ 是 → Half-Open → Closed(恢复正常) └─ 否 → Half-Open → Open(继续熔断) 关键点: 熔断器开启后,不会永久熔断 等待一段时间(timeWindow),自动进入半开状态 在半开状态下,只允许一个探测请求通过 根据探测请求的结果,决定是恢复还是继续熔断 半开状态的作用 为什么需要半开状态? 问题1:如果没有半开状态,熔断器开启后永不恢复 结果:服务永久降级,即使依赖服务已经恢复 问题2:如果熔断器直接恢复(Open → Closed) 结果:大量请求涌入,可能再次拖垮刚恢复的服务 半开状态的作用: 用一个探测请求试探依赖服务是否恢复,避免大量请求直接涌入。 就像"壮士试毒"——先派一个人尝试,安全了大家再上。 半开状态的特点 只允许一个请求通过:Sentinel会选择一个请求作为探测请求 其他请求快速失败:在探测期间,其他请求继续降级 快速决策:根据这一个请求的结果,立即决定后续策略 半开状态的触发条件 触发时机 半开状态的触发非常简单:熔断时长到期。 配置参数: DegradeRule rule = new DegradeRule(); rule.setResource("callRemoteService"); rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); rule.setCount(1000); rule.setSlowRatioThreshold(0.5); rule.setTimeWindow(10); // ← 熔断时长:10秒 工作流程: ...

2025-11-20 · maneng

降级规则与自定义降级处理

引言:降级不等于"失败" 在前面两篇文章中,我们学习了熔断的原理和三种熔断策略。我们知道,当熔断器开启时,Sentinel会停止调用依赖服务,快速返回。 但问题来了:快速返回什么呢? 很多初学者会认为:熔断就是"快速失败",返回一个错误。 这种理解是片面的! 想象这样一个场景: 你在电商平台上浏览商品详情页。此时商品服务的推荐模块因为故障被熔断了。 糟糕的降级:页面直接显示"服务异常,请稍后重试" 用户体验差 用户可能直接离开 业务受损 优雅的降级:推荐模块不显示,但商品详情、库存、价格等核心信息正常展示 用户看不出异常 核心功能不受影响 业务损失最小 这就是优雅降级的艺术:让系统在故障时仍然能提供有限但可用的服务。 本文将深入讲解如何实现优雅的降级处理。 默认降级处理:快速失败 Sentinel的默认行为 当熔断器开启时,Sentinel的默认行为是: 拦截对资源的访问 抛出DegradeException(继承自BlockException) 业务代码需要捕获这个异常并处理 代码示例 import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException; public class DefaultFallbackDemo { public static void main(String[] args) { // 假设已经配置了熔断规则,并且熔断器已开启 try (Entry entry = SphU.entry("queryProductDetail")) { // 业务逻辑:查询商品详情 System.out.println("查询商品详情成功"); } catch (DegradeException e) { // 熔断降级 System.out.println("服务降级:" + e.getClass().getSimpleName()); } catch (BlockException e) { // 其他限流、系统保护等 System.out.println("被限流或保护:" + e.getClass().getSimpleName()); } } } 输出: ...

2025-11-20 · maneng

熔断策略:慢调用比例、异常比例、异常数

引言:不同的"病症"需要不同的"药方" 在上一篇文章中,我们学习了熔断降级的原理和熔断器的状态机。我们知道,熔断的本质是检测故障 → 快速失败 → 自动恢复。 但问题来了:如何判断依赖服务"生病"了? 就像医生诊断疾病,需要看不同的指标: 体温高不高?(响应时间) 心率正常吗?(异常比例) 咳嗽了几声?(异常次数) Sentinel也提供了三种熔断策略,分别针对不同的故障模式: 慢调用比例(Slow Request Ratio):依赖服务变慢了 异常比例(Exception Ratio):依赖服务频繁抛异常 异常数(Exception Count):依赖服务在短时间内抛了太多异常 本文将深入讲解这三种策略的原理、配置方法和适用场景。 策略一:慢调用比例(Slow Request Ratio) 什么是慢调用? 慢调用是指响应时间(RT)超过设定阈值的请求。 例如: 设置慢调用阈值为1000ms 某个请求的响应时间是1200ms 这个请求就被认为是"慢调用" 慢调用比例的触发条件 熔断触发条件:在统计时长内,慢调用的比例超过设定阈值,且请求数达到最小请求数。 公式: 慢调用比例 = 慢调用数 / 总请求数 如果:慢调用比例 >= 设定阈值,且 总请求数 >= 最小请求数 则:触发熔断 示例: 统计时长:10秒 慢调用RT阈值:1000ms 慢调用比例阈值:50% 最小请求数:5 场景1:10秒内有10个请求,其中6个RT > 1000ms 慢调用比例 = 6/10 = 60% > 50% ✅ 请求数 = 10 >= 5 ✅ 结论:触发熔断 场景2:10秒内有3个请求,其中2个RT > 1000ms 慢调用比例 = 2/3 = 67% > 50% ✅ 请求数 = 3 < 5 ❌ 结论:不触发熔断(请求数太少,可能是偶发) 配置参数详解 DegradeRule rule = new DegradeRule(); rule.setResource("callRemoteService"); rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); // 策略:慢调用比例 // 核心参数 rule.setCount(1000); // 慢调用RT阈值:1000ms rule.setSlowRatioThreshold(0.5); // 慢调用比例阈值:50% rule.setMinRequestAmount(5); // 最小请求数:5 rule.setStatIntervalMs(10000); // 统计时长:10秒 rule.setTimeWindow(10); // 熔断时长:10秒 参数说明: ...

2025-11-20 · maneng

熔断降级原理:断臂求生的艺术

引言:当服务"生病"时 还记得我们在前面学过的限流吗?限流是对流量的主动防御——就像景区设置的日接待量上限,人满了就不让进。 但在微服务世界里,除了流量洪峰,还有另一个更隐蔽的威胁:依赖服务的故障传播。 想象这样一个场景: 你的订单服务依赖商品服务查询商品信息。某天凌晨,商品服务的数据库出现了慢查询问题,导致商品查询接口响应时间从10ms飙升到5秒。 订单服务不知情,继续调用商品服务。每次调用都要等5秒才超时。大量请求线程被阻塞,线程池很快被耗尽。 于是,订单服务也"挂了"。接着,依赖订单服务的购物车服务、支付服务也接连崩溃… 一场雪崩就这样发生了。 这就是微服务架构的阿喀琉斯之踵:故障会沿着调用链传播,一个服务的问题可能导致整个系统瘫痪。 熔断降级就是为了解决这个问题而生的。它的核心思想是: 当依赖服务出现故障时,主动"断开"对它的调用,避免故障传播。就像壮士断腕,牺牲局部,保全整体。 这就是"断臂求生的艺术"。 熔断的本质:防止故障蔓延 什么是熔断? 熔断(Circuit Breaking)的本质是故障隔离机制。 当系统检测到某个依赖服务出现异常(如响应超时、异常率过高),就主动切断对该服务的调用,快速返回一个"降级结果"(如默认值、缓存数据、友好提示),避免调用方线程被长时间阻塞。 熔断的三个关键要素: 故障检测:持续监控依赖服务的健康状况 快速失败:检测到故障后,快速返回而不是继续等待 自动恢复:故障恢复后,自动恢复正常调用 为什么需要熔断? 在微服务架构中,服务之间通过网络调用相互依赖,形成了复杂的调用链路。这种架构有一个天然的脆弱性: 单个服务的故障会迅速扩散,导致雪崩效应。 举个例子: 用户服务 → 订单服务 → 商品服务 → 库存服务 ↓ 支付服务 如果库存服务出现故障(如数据库慢查询),会发生: 库存服务:响应变慢,每个请求耗时5秒 商品服务:调用库存服务被阻塞,线程池占满 订单服务:调用商品服务超时,自己的线程池也被占满 用户服务:调用订单服务失败,无法提供服务 整个系统:因为一个小问题而全面瘫痪 熔断的作用就是切断这条传播链: 商品服务检测到库存服务故障,熔断对库存服务的调用 商品服务返回降级结果(如显示"暂时无法查询库存") 商品服务自身保持正常运行 订单服务、用户服务不受影响 类比:家用电路熔断器 熔断(Circuit Breaker)这个名字来源于我们日常生活中的电路熔断器。 电路熔断器的工作原理 想象你家的配电箱: 正常工作:电流正常,熔断器保持闭合,电路正常供电 过载保护:当电流过大(如同时开太多电器),熔断器检测到异常 自动断开:熔断器立即跳闸,切断电路,避免电线过热引发火灾 手动恢复:排除故障后,可以手动合上开关,恢复供电 软件中的熔断器工作原理完全一样: 电路熔断器 软件熔断器 电流过大 服务响应慢/异常多 自动断开电路 停止调用服务 避免火灾 防止线程耗尽 手动合闸 自动尝试恢复 熔断器状态机:三种状态 Sentinel的熔断器是一个有状态的对象,它有三种状态: 1. 关闭状态(Closed) 这是熔断器的初始状态和正常工作状态。 特征: ...

2025-11-20 · maneng

为什么需要熔断限流?从一次生产事故说起

核心观点: 熔断限流不是性能优化,而是系统稳定性的生命线。本文从一次真实生产事故出发,深度剖析为什么微服务架构必须要有熔断限流机制。 引子:2024年11月11日,一次惊心动魄的生产事故 事故背景 某电商平台,日常订单量10万单/天,流量1000 QPS。2024年双十一活动,预计流量增长3-5倍。技术团队提前扩容服务器,增加数据库连接池,准备迎接流量高峰。 然而,谁也没想到,活动开始仅10分钟,整个系统就崩溃了。 时间线复盘 10:00:00 - 活动正式开始 运营团队启动促销活动,用户涌入。前端监控显示流量快速攀升: 10:00:00 → 1,000 QPS(正常) 10:02:00 → 3,000 QPS(预期内) 10:05:00 → 8,000 QPS(超预期) 10:08:00 → 12,000 QPS(远超预期) 10:05:30 - 第一个异常信号 监控系统开始报警: [ALERT] 订单服务响应时间: 50ms → 800ms (P99) [ALERT] 订单服务线程池占用: 50/200 → 180/200 技术团队看到告警,以为是正常的流量压力,决定继续观察。 10:07:15 - 雪崩的开始 [CRITICAL] 订单服务响应时间: 800ms → 5000ms (P99) [CRITICAL] 订单服务线程池占用: 200/200 (100%,线程池耗尽) [ERROR] 用户服务调用超时: connection timeout after 5s 技术团队意识到不对劲,开始排查。日志显示: java.net.SocketTimeoutException: Read timed out at UserServiceClient.getUser(UserServiceClient.java:45) at OrderService.createOrder(OrderService.java:87) ... 原因找到了:用户服务的数据库出现慢查询(某个未加索引的查询,平时10ms,高峰期变成5秒)。 ...

2025-11-03 · maneng

熔断降级实战:从Hystrix到Sentinel

核心观点: 熔断器是微服务架构的安全气囊,通过快速失败隔离故障,防止雪崩。Sentinel是当前生产环境的最佳选择。 熔断器核心原理 什么是熔断器? 熔断器(Circuit Breaker)借鉴了电路中的断路器概念: 电路断路器: 正常 → 短路 → 断路器跳闸 → 保护电路 服务熔断器: 正常 → 下游故障 → 熔断器打开 → 保护上游 核心思想: 当下游服务故障时,快速失败优于漫长等待。 熔断器状态机 熔断器有三种状态: 错误率>阈值 Closed ───────────→ Open ↑ │ │ │ 等待恢复时间 │ ↓ │ Half-Open │ │ │ 探测成功 │ 探测失败 └──────────────────┘ 状态说明: Closed(闭合): 正常状态 放行所有请求 统计错误率 错误率>阈值 → 转Open Open(打开): 熔断状态 拒绝所有请求,快速失败 不调用下游服务 等待一段时间 → 转Half-Open Half-Open(半开): 探测状态 放行少量请求(探测) 成功 → 转Closed 失败 → 转Open 手写熔断器 /** * 简化版熔断器 */ public class SimpleCircuitBreaker { // 状态枚举 enum State { CLOSED, OPEN, HALF_OPEN } private State state = State.CLOSED; private int failureCount = 0; private int successCount = 0; private long lastFailureTime = 0; // 配置 private final int failureThreshold; // 失败阈值 private final long timeoutMillis; // 熔断时长 private final int halfOpenSuccessThreshold; // 半开成功阈值 public SimpleCircuitBreaker(int failureThreshold, long timeoutMillis) { this.failureThreshold = failureThreshold; this.timeoutMillis = timeoutMillis; this.halfOpenSuccessThreshold = 3; } /** * 执行受保护的调用 */ public <T> T execute(Callable<T> callable, Function<Exception, T> fallback) { // 检查状态 if (state == State.OPEN) { // 检查是否可以进入半开状态 if (System.currentTimeMillis() - lastFailureTime >= timeoutMillis) { state = State.HALF_OPEN; successCount = 0; } else { // 快速失败,调用降级 return fallback.apply(new CircuitBreakerOpenException()); } } try { // 调用实际方法 T result = callable.call(); // 调用成功 onSuccess(); return result; } catch (Exception e) { // 调用失败 onFailure(); return fallback.apply(e); } } private synchronized void onSuccess() { failureCount = 0; if (state == State.HALF_OPEN) { successCount++; if (successCount >= halfOpenSuccessThreshold) { // 连续成功,恢复闭合 state = State.CLOSED; } } } private synchronized void onFailure() { lastFailureTime = System.currentTimeMillis(); if (state == State.HALF_OPEN) { // 半开状态失败,立即打开 state = State.OPEN; successCount = 0; } else if (state == State.CLOSED) { failureCount++; if (failureCount >= failureThreshold) { // 失败次数达到阈值,打开熔断器 state = State.OPEN; } } } public State getState() { return state; } } // 使用示例 public class UserService { private SimpleCircuitBreaker breaker = new SimpleCircuitBreaker(5, 10000); // 5次失败,熔断10秒 public User getUser(Long userId) { return breaker.execute( // 实际调用 () -> userServiceClient.getUser(userId), // 降级方法 (ex) -> { log.warn("用户服务熔断,返回默认用户"); return User.defaultUser(userId); } ); } } 工作流程: ...

2025-11-03 · maneng

服务治理:注册发现、负载均衡与熔断降级

引子:一次服务雪崩事故 2020年双11,某电商平台因评论服务故障导致整个系统瘫痪3小时,损失上亿。 故障链路: 用户下单 → 订单服务 → 评论服务(响应慢,20秒超时) → 订单服务线程池耗尽 → 用户服务调用订单服务失败 → 整个系统崩溃 问题根源:缺乏有效的服务治理机制 一、服务注册与发现 1.1 为什么需要服务注册中心? 问题:微服务架构下,服务IP动态变化 订单服务 → 库存服务(192.168.1.10:8080) 问题: 1. 库存服务重启,IP可能变化 2. 库存服务扩容,新增实例 3. 库存服务下线,需要摘除 解决方案:服务注册中心 订单服务 → 注册中心 → 获取库存服务列表 ↓ [192.168.1.10:8080, 192.168.1.11:8080, 192.168.1.12:8080] 1.2 Nacos服务注册与发现 服务提供者:注册服务 /** * 库存服务:自动注册到Nacos */ @SpringBootApplication @EnableDiscoveryClient public class InventoryServiceApplication { public static void main(String[] args) { SpringApplication.run(InventoryServiceApplication.class, args); } } # application.yml spring: application: name: inventory-service # 服务名 cloud: nacos: discovery: server-addr: 127.0.0.1:8848 namespace: dev group: DEFAULT_GROUP 服务消费者:发现服务 ...

2025-11-03 · maneng

哨兵Sentinel:Redis的自动故障转移

引言 主从复制虽然实现了数据备份,但主节点宕机后需要手动切换。哨兵(Sentinel)提供了自动故障检测和故障转移能力,实现真正的高可用。 一、哨兵概述 1.1 什么是哨兵? 监控 ↓ [哨兵1] ←→ [哨兵2] ←→ [哨兵3] (多个哨兵相互通信) ↓ ↓ ↓ 监控 监控 监控 ↓ ↓ ↓ [主节点] ← [从节点1] ← [从节点2] 功能: 1. 监控:检查主从节点是否正常 2. 通知:故障通知管理员 3. 故障转移:自动提升从节点为主节点 4. 配置中心:客户端通过哨兵发现主节点 1.2 核心功能 监控(Monitoring) 检查主从节点健康状态 周期性发送PING命令 故障检测(Failure Detection) 主观下线(SDOWN):单个哨兵判断 客观下线(ODOWN):多数哨兵同意 自动故障转移(Failover) 选举领导者哨兵 选择新主节点 通知从节点切换 通知客户端 配置中心(Configuration Provider) 客户端从哨兵获取主节点地址 主节点变更自动通知客户端 二、哨兵部署 2.1 最小配置 # sentinel.conf port 26379 sentinel monitor mymaster 127.0.0.1 6379 2 # 2个哨兵同意才判定下线 sentinel down-after-milliseconds mymaster 5000 # 5秒无响应判断下线 sentinel failover-timeout mymaster 180000 # 故障转移超时时间 sentinel parallel-syncs mymaster 1 # 同时同步的从节点数量 2.2 启动哨兵 # 方式1: redis-sentinel sentinel.conf # 方式2: redis-server sentinel.conf --sentinel # 查看哨兵状态 127.0.0.1:26379> SENTINEL masters 127.0.0.1:26379> SENTINEL slaves mymaster 127.0.0.1:26379> SENTINEL sentinels mymaster 2.3 推荐部署架构 机器1:Redis Master + Sentinel1 机器2:Redis Slave1 + Sentinel2 机器3:Redis Slave2 + Sentinel3 好处: - 3个哨兵互相监控,防止误判 - 分布式部署,避免单点故障 三、故障检测机制 3.1 主观下线(SDOWN) 哨兵1每秒向主节点发送PING: PING → 主节点 PING → 主节点 PING → (超时5秒无响应) 判断:主节点主观下线(SDOWN) 配置: ...

2025-01-21 · maneng

如约数科科技工作室

浙ICP备2025203501号

👀 本站总访问量 ...| 👤 访客数 ...| 📅 今日访问 ...