当前位置: 首页 > news >正文

分布式系统设计的容错机制

目录

1、熔断

1.1、介绍

1.2、原理/状态机

1.3、常用指标 & 参数

1.4、常见实现/库

2、降级

2.1、介绍

2.2、分类

2.3、实现方式

3、限流

3.1、定义

3.2、常见算法

4、隔离(Bulkhead / 资源隔离)

4.1、概念

4.2、实现方式

5、四者组合与实际策略

6、监控与指标(必须)


背景

        分布式系统中任一服务或下游依赖变慢或宕掉,可能产生级联故障(请求积压、线程耗尽、连接尽、资源竞用),导致系统整体不可用或雪崩。

        熔断(Circuit Breaker)避免对不健康依赖不断重试/请求;

        降级(Fallback/Degrade)在功能不可用或超载时提供替代方案;

        限流(Rate Limit)控制进入系统或单个服务的请求速率,保护资源;

        隔离(Bulkhead/隔离)将故障影响局限在某个隔间,避免资源争抢扩散。

        这几种方式是分布式系统中应对高并发、依赖故障的核心容错机制,‌共同解决服务雪崩风险,保障系统可用性。


1、熔断

1.1、介绍

断路保护。

        比如 A 服务调用 B 服务,由于网络问题或 B 服务宕机了或 B 服务的处理时间长,导致请求的时间超长,如果在一定时间内多次出现这种情况,就可以直接将 B 断路了(A 不再请求B)。

        而调用 B 服务的请求直接返回降级数据,不必等待 B 服务的执行。因此 B 服务的问题,不会级联影响到 A 服务。

  • 作用‌:‌快速失败止损‌,当服务失败率超过阈值时自动切断调用链路。
  • 实现逻辑‌:
    • 监控请求失败率(如10秒内失败率>50%)
    • 触发熔断后,后续请求直接走降级逻辑,不再访问故障服务
    • 定期进入“半开状态”试探服务恢复情况
  • 典型场景‌:支付服务持续超时后,网关层直接熔断,避免请求堆积

1.2、原理/状态机

基本三态:

CLOSED(闭合,正常请求通过并收集成功/失败指标);

OPEN(打开,短路,不再调用下游,直接失败或走降级);

HALF_OPEN(半开,允许少量试探请求以探测依赖是否恢复)。

触发条件通常基于:

        在滑动窗口内的失败率、失败次数、响应时延、吞吐量等。达到阈值触发 OPEN,过一段时间后进入 HALF_OPEN,若试探请求成功则回到 CLOSED,否则继续 OPEN。

1.3、常用指标 & 参数

  • failureRateThreshold(失败率阈值,例如 50%)
  • minimumNumberOfCalls(最小采样请求数,避免样本量太小)
  • slidingWindowSize(统计窗口大小,基于时间或计数)
  • waitDurationInOpenState(打开态保持时间,过后转 HALF_OPEN)
  • permittedNumberOfCallsInHalfOpenState(半开允许的试探请求数)

1.4、常见实现/库

  • Netflix Hystrix(已停止维护,思想仍然有价值)
  • Resilience4j(现代、轻量、功能丰富)
  • Sentinel(阿里,支持流控、熔断、降级、热点限流)

1.5、Java 示例:Resilience4j(maven)

  • 依赖:
    • org.resilience4j:resilience4j-circuitbreaker
  • 简单示例(同步调用):
import io.github.resilience4j.circuitbreaker.*;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.SlidingWindowType;CircuitBreakerConfig config = CircuitBreakerConfig.custom().failureRateThreshold(50) // 50%.minimumNumberOfCalls(10).slidingWindowType(SlidingWindowType.TIME_BASED).slidingWindowSize(10) // 10 seconds window (if TIME_BASED).waitDurationInOpenState(Duration.ofSeconds(30)).permittedNumberOfCallsInHalfOpenState(5).build();CircuitBreaker cb = CircuitBreaker.of("myService", config);// 装饰一个 Supplier 或 Callable
Supplier<String> decorated = CircuitBreaker.decorateSupplier(cb, () -> callRemoteService());try {String result = decorated.get();
} catch (CallNotPermittedException ex) {// 熔断打开,短路到这里,做降级fallback();
}

简单手写熔断器(伪实现):

class SimpleCircuitBreaker {enum State { CLOSED, OPEN, HALF_OPEN }private State state = State.CLOSED;private long openUntil = 0;private int failCount = 0;private int successCount = 0;private final int failThreshold = 5;private final long openMs = 10_000L;public synchronized <T> T call(Callable<T> callable) throws Exception {long now = System.currentTimeMillis();if (state == State.OPEN) {if (now < openUntil) throw new RuntimeException("circuit open");state = State.HALF_OPEN;}try {T r = callable.call();onSuccess();return r;} catch (Exception e) {onFailure();throw e;}}private void onSuccess() {if (state == State.HALF_OPEN) {// 一个成功就关闭,也可以要求连续成功次数state = State.CLOSED; failCount = 0;}}private void onFailure() {failCount++;if (failCount >= failThreshold) {state = State.OPEN;openUntil = System.currentTimeMillis() + openMs;}}
}

注意:真实生产要用滑窗统计、并发安全、冷启动保护等。


2、降级

2.1、介绍

返回降级数据。

        网站处于流量高峰期,服务器压力剧增,根据当前业务情况及流量,对一些服务和页面进行有策略的降级(停止服务,所有的调用直接返回降级数据)。

        以此缓解服务器资源的压力,保证核心业务的正常运行,保持了客户和大部分客户得到正确的响应。

        降级数据可以简单理解为快速返回了一个 false,前端页面告诉用户“服务器当前正忙,请稍后再试。”

  • 作用‌:‌提供柔性方案‌,在熔断或服务不可用时返回预设结果。
  • 实现方式‌:
    • 返回缓存数据(如商品详情页降级展示昨日销量)
    • 返回默认值(如查询失败时显示“服务繁忙”)
    • 流程简化(下单跳过风控校验)
  • 关键点‌:需提前设计降级策略,确保用户体验平滑

2.2、分类

        功能降级(返回默认/缓存数据/静态页面)、流量降级(拒绝非核心请求)、延迟降级(将请求入队异步处理)、降级到降级服务(更便宜或更稳定的实现)。

2.3、实现方式

  • 在代码中实现 fallback(try/catch 或 使用库注入 fallback,例如 Resilience4j 的 fallback 或 Spring Cloud 的 fallback)
  • 使用缓存作为后备(比如返回缓存数据)
  • 业务级降级:降低功能强度(只返回必要字段、减少并行查询、去掉非核心聚合)
  • 降级开关/灰度:通过配置中心(HOT)控制降级

Java 示例(Resilience4j with fallback)

import io.github.resilience4j.circuitbreaker.CallNotPermittedException;try {String res = decorated.get();
} catch (CallNotPermittedException ex) {// 熔断短路 -> 降级处理return fallbackData();
} catch (Exception e) {// 依赖超时/异常 -> 降级return fallbackData();
}
  • 缓存优先(先返回缓存,再异步刷新缓存)
  • 简化响应(只返回 ID 和关键字段)
  • 限功能(把高级功能关闭,保持核心支付/登录业务)

熔断和降级的相同点?

  • 熔断和限流都是为了保证集群大部分服务的可用性和可靠性。防止核心服务崩溃。
  • 给终端用户的感受就是某个功能不可用。

熔断和降级的不同点?

  • 熔断是被调用方出现了故障,主动触发的操作。
  • 降级是基于全局考虑,停止某些正常服务,释放资源。


3、限流

3.1、定义

对请求的流量进行控制, 只放行部分请求,使服务能够承担不超过自己能力的流量压力。

  • 控制突发流量、保护后端资源、避免请求淹没服务、实现 QoS 策略(优先级/计费)

3.2、常见算法

  • 固定窗口计数(Fixed Window):按时间窗口计数(简单但在窗口边界有突发)。
  • 滑动窗口计数(Sliding Window Log/Counter):记录时间戳(更精确,但可能昂贵)。
  • 令牌桶(Token Bucket):以固定速率产生令牌,请求拿到令牌则放行,可实现平滑突发(允许短时突发)。
  • 漏桶(Leaky Bucket):以固定速率处理请求,过载则丢弃/排队(平滑输出)。
  • 令牌桶 + Redis/Lua:分布式限流实现常用。

本地简单实现:令牌桶(Token Bucket)

class TokenBucket {private final long capacity;private final long refillTokens;private final long refillIntervalMillis;private double tokens;private long lastRefillTimestamp;public TokenBucket(long capacity, long refillTokens, long refillIntervalMillis) {this.capacity = capacity;this.refillTokens = refillTokens;this.refillIntervalMillis = refillIntervalMillis;this.tokens = capacity;this.lastRefillTimestamp = System.currentTimeMillis();}public synchronized boolean tryConsume(int numTokens) {refill();if (tokens >= numTokens) {tokens -= numTokens;return true;}return false;}private void refill() {long now = System.currentTimeMillis();long intervals = (now - lastRefillTimestamp) / refillIntervalMillis;if (intervals > 0) {double add = intervals * refillTokens;tokens = Math.min(capacity, tokens + add);lastRefillTimestamp += intervals * refillIntervalMillis;}}
}

分布式限流(Redis + Lua)

  • 用 Lua 脚本在 Redis 原子执行计数或令牌桶操作,避免竞争。
  • 示例:滑动窗口计数(用时间戳链表)或令牌桶(使用 Redis key 存 token 值与 timestamp)。

简单的 Redis 计数(固定窗口)Lua(伪代码):

local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])local count = redis.call('GET', key)
if not count thenredis.call('SET', key, 1, 'PX', window)return 1
end
if tonumber(count) + 1 > limit thenreturn 0
elseredis.call('INCR', key)return 1
end


4、隔离(Bulkhead / 资源隔离)

4.1、概念

        将服务或资源按“隔间”划分(线程池、连接池、限额),使得某一隔间发生故障或耗尽时不致影响其他隔间(业务)。灵感来自船舱隔离(bulkhead)。

4.2、实现方式

  • 线程池隔离:对不同下游或不同类型请求使用不同线程池(或不同拒绝策略),避免单个慢调用耗尽主线程池。
  • 信号量隔离(semaphore):限制并发调用数(轻量,不带线程切换)。
  • 连接池/资源配额:每个依赖一个单独连接池,上游耗尽连接不影响其它依赖。
  • 容器/微服务资源限制:通过 k8s 限制资源、pod 副本隔离。

Java 示例:线程池隔离(Executor)

ExecutorService pool = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<>(50),new ThreadPoolExecutor.AbortPolicy()); // 当队列满时拒绝Future<String> future = pool.submit(() -> callRemoteService());
try {String res = future.get(2, TimeUnit.SECONDS); // 超时控制
} catch (TimeoutException e) {// 超时 -> 降级future.cancel(true);fallback();
}

信号量隔离(Resilience4j 提供 Bulkhead)

import io.github.resilience4j.bulkhead.*;
BulkheadConfig config = BulkheadConfig.custom().maxConcurrentCalls(20).maxWaitDuration(Duration.ZERO) // 不等待,直接拒绝.build();
Bulkhead bulkhead = Bulkhead.of("service", config);
Supplier<String> decorated = Bulkhead.decorateSupplier(bulkhead, () -> callRemoteService());
try { decorated.get(); } catch (BulkheadFullException ex) { fallback(); }

为什么优先选择信号量还是线程池?

  • 信号量:延迟低、轻量(适合在同线程中限流),但当依赖阻塞时会占用调用线程。
  • 线程池:能把阻塞转化为排队,保护调用线程(比如 tomcat 请求处理线程),但可能导致上下文切换和队列积压。


5、四者组合与实际策略

请求进入时做鉴权与快速限流(Token Bucket)—— 保护入口。

常见防御链(建议):

  • 对调用下游前先使用隔离(线程池/信号量)—— 保证主线程不被耗尽。
  • 用熔断器判断下游健康,快速短路失败—— 减少无用等待与重试风暴。
  • 对短路/失败使用降级策略(缓存/默认结果/异步排队)—— 保证核心业务可用。
  • 对内部延迟/超时做度量并上报监控(实时告警)。

配置示例(伪):限流 200 rps;线程池 max 50;熔断:10s 窗口内失败率>50% 且最小20个请求 -> 打开 30s -> 半开 5次试探。


6、监控与指标(必须)

  • 熔断器需上报:成功率、失败率、请求数、state transitions(CLOSED->OPEN->HALF_OPEN)。
  • 限流需上报:命中数、被拒绝数、当前速率。
  • 隔离需上报:线程池活跃数、队列长度、拒绝数、等待时长。
  • 降级需上报:fallback 调用次数、降级率、触发原因(熔断、超时、资源压力)。
  • 告警阈值应基于业务SLA设定,避免噪音。


参考文章:

1、Spring Cloud源码 - Hystrix原理分析_spring hystrix 原理 及底层实现-CSDN博客文章浏览阅读1.1k次,点赞28次,收藏29次。Spring Cloud源码 - Hystrix原理分析_spring hystrix 原理 及底层实现 https://blog.csdn.net/qq_43350524/article/details/145883838?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522275af48ced5566253a61655c3e43474d%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=275af48ced5566253a61655c3e43474d&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-2-145883838-null-null.142^v102^control&utm_term=springcloud%E9%87%8C%E9%9D%A2%E7%9A%84hystrix%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E5%8F%8A%E5%8E%9F%E7%90%86&spm=1018.2226.3001.4187

2、【spring cloud】 Hystrix使用及原理_hystrix: enabled: true-CSDN博客文章浏览阅读211次。文章目录服务降级服务端的服务降级Pom依赖service层主启动类添加@EnableCircuitBreaker消费端服务降级pom依赖yml.application业务类为特定方法指定备选方法:服务降级服务降级就是在一个方法调用失败或超时时,调用一个备用的方法,来给调用方一个友好的提示。服务端的服务降级Pom依赖org.springframework.cloud https://blog.csdn.net/yao09605/article/details/111640736?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_utm_term~default-9-111640736-blog-145883838.235^v43^pc_blog_bottom_relevance_base6&spm=1001.2101.3001.4242.6&utm_relevant_index=11

http://www.dtcms.com/a/332718.html

相关文章:

  • AI优质信息源汇总:含X账号,Newsletter,播客,App
  • 如何在 FastAPI 中玩转 APScheduler,让任务定时自动执行?
  • 上下文块嵌入(contextualized-chunk-embeddings)
  • collections:容器数据类型
  • C语言——深入理解指针(四)
  • 完整技术栈分享:基于Hadoop+Spark的在线教育投融资大数据可视化分析系统
  • 使用XXL-SSO实现登录认证以及权限管控
  • 解决 MySQL 查询速度缓慢的问题
  • Filebeat 轻量级日志采集实践:安装、配置、多行合并、JSON 解析与字段处理
  • Java集合Map与Stream流:Map实现类特点、遍历方式、Stream流操作及Collections工具类方法
  • 【软件设计模式】前置知识类图、七大原则(精简笔记版)
  • C++ 调试报错 常量中有换行符
  • 基于桥梁三维模型的无人机检测路径规划系统设计与实现
  • Cursor 分析 bug 记录
  • 3D视觉与空间智能
  • imx6ull-驱动开发篇25——Linux 中断上半部/下半部
  • 智谱开源了最新多模态模型,GLM-4.5V
  • 关系型数据库从入门到精通:MySQL 核心知识全解析
  • 高并发系统性能优化实战:实现5万并发与毫秒级响应
  • Kafka生产者——提高生产者吞吐量
  • LeetCode 面试经典 150_数组/字符串_最长公共前缀(20_14_C++_简单)(暴力破解)(求交集)
  • 简单使用 TypeScript 或 JavaScript 创建并发布 npm 插件
  • 从零到一:发布你的第一个 npm 开源库(2025 终极指南)
  • IT资讯 | VMware ESXi高危漏洞影响国内服务器
  • Day62--图论--97. 小明逛公园(卡码网),127. 骑士的攻击(卡码网)
  • 嵌入式 C 语言编程规范个人学习笔记,参考华为《C 语言编程规范》
  • 使用CMAKE-GU生成Visual Studio项目
  • ​Visual Studio 2013.5 ULTIMATE 中文版怎么安装?iso镜像详细步骤
  • Pushgateway安装和部署,以及对应Prometheus调整
  • 六维力传感器:工业机器人的“触觉神经”如何突破自动化瓶颈?