Sentinel架构篇 - 熔断降级

news2024/10/2 20:29:15

熔断降级

概念

除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用其它模块,可能是一个远程服务、数据库、或者第三方 API 等。然而,被依赖的服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,导致请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会堆积,最终可能会耗尽业务自身的线程池,甚至服务本身变得不可用。

现在的微服务架构都是分布式的,由非常多的服务组成。不同的服务之间相互调用,形成复杂的调用链路。链路中某一环不稳定,可能会层层级联,最终导致整个链路不可用。因此需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定的调用,避免局部不稳定因素导致整体的雪崩。熔断降级通常在客户端(调用端)进行配置。

熔断可以类比成生活中的保险丝,一旦电流过载,保险丝就会断开。

Sentinel 熔断降级基于熔断器模式 (circuit breaker pattern) 实现。熔断器内部维护了一个熔断器的状态机,状态机的转换关系如下图所示:

在这里插入图片描述

熔断器有三种状态:

  • Closed 状态:也是初始状态,该状态下,熔断器会保持闭合,对资源的访问直接通过熔断器的检查。
  • Open 状态:断开状态,熔断器处于开启状态,对资源的访问会被切断。
  • Half-Open 状态:半开状态,该状态下除了探测流量,其余对资源的访问也会被切断。探测流量指熔断器处于半开状态时,会周期性的允许一定数目的探测请求通过,如果探测请求能够正常的返回,代表探测成功,此时熔断器会重置状态到 Closed 状态,结束熔断;如果探测失败,则回滚到 Open 状态。

熔断策略

Sentinel 提供了如下三种熔断策略。

  • 慢调用比例(SLOW_REQUEST_RATIO):选择慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),如果请求的时间大于该阈值则被统计为慢调用。当单位统计时长(statIntervalMs)内请求的数量大于设置的最小请求数量,并且慢调用的比例大于阈值,则接下来的熔断时长(timeWindow)内请求自动被熔断。经过熔断时长后,熔断器进入探测恢复状态(Half-Open 状态),如果接下来的一个请求的响应时间小于设置的慢调用 RT 则结束熔断;如果大于则再次被熔断。慢调用比例的阈值范围为 [0.0, 1.0],代表 0% - 100%。
  • 异常比例(ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数量大于设置的最小请求数量,并且异常比例大于阈值,则接下来的熔断时长(timeWindow)内请求自动被熔断。经过熔断时长后,熔断器进入探测恢复状态(Half-Open 状态),如果接下来的一个请求成功完成(没有错误)则结束熔断;否则再次被熔断。异常比例的阈值范围为 [0.0, 1.0] ,代表 0% - 100%。
  • 异常数(ERROR_COUNT):当单位统计时长(statIntervalMs)内的异常数量超过阈值之后,则接下来的熔断时长(timeWindow)内请求自动被熔断。经过熔断时长后,熔断器进入探测恢复状态(Half-Open 状态),如果接下来的一个请求成功完成(没有错误)则结束熔断;否则再次被熔断。

熔断降级规则

字段说明默认值
resource资源名,即规则作用的对象
grade熔断策略,支持慢调用比例/异常比例/异常数慢调用比例
count慢调用比例模式下对应慢调用RT(超过该值即为慢调用);异常比例/异常数模式下为对应的阈值
timeWindow熔断时长,单位为秒
minRequestAmount熔断触发的最小请求数,请求数小于该值时即使异常比例超过阈值也不会熔断(1.7.0 版本引入)5
statIntervalMs统计时长,单位为毫秒(1.8.0 版本引入)1000
slowRationThreshold慢调用比例阈值,仅慢调用比例模式有效(1.8.0 版本引入)

同一个资源可以同时有多个熔断降级规则。

实际操作

在 Nacos 的控制台中的配置管理/配置列表中,在 public 的命名空间中创建一个如下配置:

dataId:spring-cloud-demo-consumer-sentinel-degrade

group:DEFAULT

[
    {
        "resource": "/hello/say",
        "limitApp": "default",
        "grade": 0,
        "count": 200,
        "timeWindow": 10,
        "statIntervalMs": 1000,
        "slowRatioThreshold": 0.6   
    }
]

如果 1 秒内,请求数量至少达到 200,并且(请求的响应时间超过 200 毫秒即为慢调用)慢调用的比例达到 60%,则进行熔断,熔断时长为 10 秒。


对应的客户端的配置文件如下:

spring:
    application:
        name: spring-cloud-demo-consumer
    cloud:
        nacos:
            discovery:
                server-addr: 10.211.55.11:8848,10.211.55.12:8848,10.211.55.13:8848
                enabled: true
        sentinel:
            transport:
                dashboard: 127.0.0.1:9000
            eager: true
            datasource:
                degrade-nacos-datasource:
                    nacos:
                        server-addr: 10.211.55.11:8848,10.211.55.12:8848,10.211.55.13:8848
                        group-id: DEFAULT_GROUP
                        namespace: public
                        data-id: ${spring.application.name}-sentinel-degrade
                        data-type: json
                        rule-type: degrade
                        username: nacos
                        password: nacos

DegradeSlot

负责熔断降级规则的判断。

@Spi(order = Constants.ORDER_DEGRADE_SLOT)
public class DegradeSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                      boolean prioritized, Object... args) throws Throwable {
      	// 校验熔断降级规则
        performChecking(context, resourceWrapper);

        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }

    void performChecking(Context context, ResourceWrapper r) throws BlockException {
      	// 由DegradeRuleManager负责加载所有的断路器
        List<CircuitBreaker> circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName());
      	// 如果断路器列表为空,则直接返回
        if (circuitBreakers == null || circuitBreakers.isEmpty()) {
            return;
        }
      	// 遍历断路器列表,只要有一个断路器判定请求不通过,则抛出DegradeException异常
        for (CircuitBreaker cb : circuitBreakers) {
            if (!cb.tryPass(context)) {
                throw new DegradeException(cb.getRule().getLimitApp(), cb.getRule());
            }
        }
    }

    @Override
    public void exit(Context context, ResourceWrapper r, int count, Object... args) {
        Entry curEntry = context.getCurEntry();
      	// 如果调用过程中存在BlockException异常,则直接返回
        if (curEntry.getBlockError() != null) {
            fireExit(context, r, count, args);
            return;
        }
      	// 由DegradeRuleManager负责加载所有的断路器
        List<CircuitBreaker> circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName());
      	// 如果断路器列表为空,则直接返回
        if (circuitBreakers == null || circuitBreakers.isEmpty()) {
            fireExit(context, r, count, args);
            return;
        }
		// 如果调用过程中不存在BlockException异常
        if (curEntry.getBlockError() == null) {
          	// 遍历断路器列表,触发每个断路器的onRequestComplete方法的回调
            for (CircuitBreaker circuitBreaker : circuitBreakers) {
                circuitBreaker.onRequestComplete(context);
            }
        }

        fireExit(context, r, count, args);
    }
}

接下来看下断路器如何判断请求是否通过的。

AbstractCircuitBreaker

AbstractCircuitBreaker(DegradeRule rule, EventObserverRegistry observerRegistry) {
    AssertUtil.notNull(observerRegistry, "observerRegistry cannot be null");
    if (!DegradeRuleManager.isValidRule(rule)) {
        throw new IllegalArgumentException("Invalid DegradeRule: " + rule);
    }
    this.observerRegistry = observerRegistry;
    this.rule = rule;
    this.recoveryTimeoutMs = rule.getTimeWindow() * 1000;
}

接下来看下 tryPass 方法的处理逻辑。

@Override
public boolean tryPass(Context context) {
    // 如果断路器的状态是CLOSED,则返回true,表示请求通过
    if (currentState.get() == State.CLOSED) {
        return true;
    }
  	// 如果断路器的状态是OPEN
    if (currentState.get() == State.OPEN) {
      	// 判断是否到了熔断结束时间,如果到了则尝试将断路器的状态从OPEN变为HALF-OPEN
        return retryTimeoutArrived() && fromOpenToHalfOpen(context);
    }
  	// 剩余情况,返回false,表示请求不通过
    return false;
}

retryTimeoutArrived 方法

判断是否到了熔断结束时间

protected boolean retryTimeoutArrived() {
  	// 判断当前时间 >= 下一次的熔断结束时间
    return TimeUtil.currentTimeMillis() >= nextRetryTimestamp;
}

fromOpenToHalfOpen 方法

尝试将断路器的状态从OPEN变为HALF-OPEN

protected boolean fromOpenToHalfOpen(Context context) {
  	// 尝试将断路器的状态从OPEN更新为HALF_OPEN
    if (currentState.compareAndSet(State.OPEN, State.HALF_OPEN)) {
      	// 触发所有CircuitBreakerStateChangeObserver的onStateChange方法回调
        notifyObservers(State.OPEN, State.HALF_OPEN, null);
        Entry entry = context.getCurEntry();
        entry.whenTerminate(new BiConsumer<Context, Entry>() {
            @Override
            public void accept(Context context, Entry entry) {
				// 如果调用过程中存在BlockException异常
                if (entry.getBlockError() != null) {
					// 将断路器的状态从HALF_OPEN更新为OPEN
                    currentState.compareAndSet(State.HALF_OPEN, State.OPEN);
                  	// 触发所有CircuitBreakerStateChangeObserver的onStateChange方法回调
                    notifyObservers(State.HALF_OPEN, State.OPEN, 1.0d);
                }
            }
        });
        return true;
    }
    return false;
}

接下来重点看下 AbstractCircuitBreaker 的子类对于 onRequestComplete 方法的具体实现。

ResponseTimeCircuitBreaker

关注响应时间的断路器实现

public ResponseTimeCircuitBreaker(DegradeRule rule) {
  	// 统计时长由熔断降级规则的statIntervalMs参数指定,默认1000,即1秒
    this(rule, new SlowRequestLeapArray(1, rule.getStatIntervalMs()));
}

ResponseTimeCircuitBreaker(DegradeRule rule, LeapArray<SlowRequestCounter> stat) {
    super(rule);
    AssertUtil.isTrue(rule.getGrade() == RuleConstant.DEGRADE_GRADE_RT, "rule metric type should be RT");
    AssertUtil.notNull(stat, "stat cannot be null");
    this.maxAllowedRt = Math.round(rule.getCount());
    this.maxSlowRequestRatio = rule.getSlowRatioThreshold();
    this.minRequestAmount = rule.getMinRequestAmount();
    this.slidingCounter = stat;
}

看下 ResponseTimeCircuitBreaker 对于 onRequestComplete 方法的具体实现。

@Override
public void onRequestComplete(Context context) {
    SlowRequestCounter counter = slidingCounter.currentWindow().value();
    Entry entry = context.getCurEntry();
    if (entry == null) {
        return;
    }
    long completeTime = entry.getCompleteTimestamp();
    if (completeTime <= 0) {
        completeTime = TimeUtil.currentTimeMillis();
    }
    long rt = completeTime - entry.getCreateTimestamp();
  	// 如果响应时间超过了阈值(对应熔断降级规则中的count参数)
    if (rt > maxAllowedRt) {
      	// 慢请求数指标加一
        counter.slowCount.add(1);
    }
  	// 总请求数指标加一
    counter.totalCount.add(1);

    handleStateChangeWhenThresholdExceeded(rt);
}

接下来看下 handleStateChangeWhenThresholdExceeded 方法的处理逻辑。

private void handleStateChangeWhenThresholdExceeded(long rt) {
  	// 如果断路器的状态是OPEN,则直接返回
    if (currentState.get() == State.OPEN) {
        return;
    }
    
  	// 如果断路器的状态是HALF_OPEN
    if (currentState.get() == State.HALF_OPEN) {
      	// 如果请求的响应时间超过了阈值
        if (rt > maxAllowedRt) {
          	// 将断路器的状态更新为OPEN,然后更新下一次的熔断结束时间
            fromHalfOpenToOpen(1.0d);
        } else {
          	// 将断路器的状态更新为CLOSED,然后重置慢请求数、总请求数指标
            fromHalfOpenToClose();
        }
        return;
    }

    List<SlowRequestCounter> counters = slidingCounter.values();
    long slowCount = 0;
    long totalCount = 0;
  	// 累加慢请求数、总请求数
    for (SlowRequestCounter counter : counters) {
        slowCount += counter.slowCount.sum();
        totalCount += counter.totalCount.sum();
    }
  	// 如果总请求数 < 熔断降级规则中的minRequestAmount参数,则直接返回
    if (totalCount < minRequestAmount) {
        return;
    }
  	// 计算慢调用比例
    double currentRatio = slowCount * 1.0d / totalCount;
  	// 如果慢调用比例 > 熔断降级队则中的slowRatioThreshold参数值(默认1)
    if (currentRatio > maxSlowRequestRatio) {
      	// 将断路器的状态更新为OPEN,然后更新下一次的熔断结束时间
        transformToOpen(currentRatio);
    }
  	// 如果当前的慢调用比例达到了100%
    if (Double.compare(currentRatio, maxSlowRequestRatio) == 0 &&
            Double.compare(maxSlowRequestRatio, SLOW_REQUEST_RATIO_MAX_VALUE) == 0) {
      	// 将断路器的状态更新为OPEN,然后更新下一次的熔断结束时间
        transformToOpen(currentRatio);
    }
}

ExceptionCircuitBreaker

关注异常比例、异常数的断路器实现

public ExceptionCircuitBreaker(DegradeRule rule) {
  	// 统计时长由熔断降级规则的statIntervalMs参数指定,默认1000,即1秒
    this(rule, new SimpleErrorCounterLeapArray(1, rule.getStatIntervalMs()));
}

ExceptionCircuitBreaker(DegradeRule rule, LeapArray<SimpleErrorCounter> stat) {
    super(rule);
    this.strategy = rule.getGrade();
    boolean modeOk = strategy == DEGRADE_GRADE_EXCEPTION_RATIO || strategy == DEGRADE_GRADE_EXCEPTION_COUNT;
    AssertUtil.isTrue(modeOk, "rule strategy should be error-ratio or error-count");
    AssertUtil.notNull(stat, "stat cannot be null");
    this.minRequestAmount = rule.getMinRequestAmount();
    this.threshold = rule.getCount();
    this.stat = stat;
}

看下 ExceptionCircuitBreaker 对于 onRequestComplete 方法的具体实现。

@Override
public void onRequestComplete(Context context) {
    Entry entry = context.getCurEntry();
    if (entry == null) {
        return;
    }
    Throwable error = entry.getError();
    SimpleErrorCounter counter = stat.currentWindow().value();
    if (error != null) {
      	// 对异常请求数指标加一
        counter.getErrorCount().add(1);
    }
	// 对总请求数指标加一
    counter.getTotalCount().add(1);

    handleStateChangeWhenThresholdExceeded(error);
}

接下来看下 handleStateChangeWhenThresholdExceeded 方法的处理逻辑。

private void handleStateChangeWhenThresholdExceeded(Throwable error) {
  	// 如果断路器的状态是OPEN,则直接返回
    if (currentState.get() == State.OPEN) {
        return;
    }
    // 如果断路器的状态是HALF_OPEN
    if (currentState.get() == State.HALF_OPEN) {
        if (error == null) {
          	// 将断路器的状态更新为CLOSED,然后重置慢请求数、总请求数指标
            fromHalfOpenToClose();
        } else {
          	// 将断路器的状态更新为OPEN,然后更新下一次的熔断结束时间
            fromHalfOpenToOpen(1.0d);
        }
      	// 直接返回
        return;
    }
    
    List<SimpleErrorCounter> counters = stat.values();
    long errCount = 0;
    long totalCount = 0;
  	// 累加错误请求数、总请求数
    for (SimpleErrorCounter counter : counters) {
        errCount += counter.errorCount.sum();
        totalCount += counter.totalCount.sum();
    }
  	// 如果总请求数 < 熔断降级规则中的minRequestAmount参数,则直接返回
    if (totalCount < minRequestAmount) {
        return;
    }
    double curCount = errCount;
  	// 如果策略是统计异常数比例,则将异常数比例转化成错误请求数
    if (strategy == DEGRADE_GRADE_EXCEPTION_RATIO) {
        curCount = errCount * 1.0d / totalCount;
    }
  	// 如果错误请求数 > 阈值
    if (curCount > threshold) {
      	// 将断路器的状态更新为OPEN,然后更新下一次的熔断结束时间
        transformToOpen(curCount);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/397513.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

原生HTML放大镜

该放大区域用背景图片放大 <!DOCTYPE html> <html lang"zh"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta http-equiv"X-UA-Compat…

Linux - buff和cache的区别

free -h命令可以查看内存的使用情况 [rootzabbix-server ~]# free -htotal used free shared buff/cache available Mem: 1.8G 432M 894M 10M 492M 1.2G Swap: 2.0G 0B 2.0G为什…

抖音小程序实践四:实现小程序分享

有时候我们要把一个小程序分享给别人&#xff0c;去看套餐、买东西之类的&#xff0c;是一个很常见的功能&#xff0c;但是在接入抖音小程序的时候&#xff0c;初始化右上角三个点并没有分享的入口&#xff0c;那看来不是要申请&#xff0c;就是有别的开发的口子了。下面我们一…

《SQL基础》17. InnoDB引擎

InnoDB引擎InnoDB引擎逻辑存储结构架构内存结构磁盘结构后台线程事务原理事务基础redo logundo logMVCC基本概念隐式字段undo log版本链readView原理分析InnoDB引擎 逻辑存储结构 InnoDB的逻辑存储结构如下图所示&#xff1a; 表空间 表空间是InnoDB存储引擎逻辑结构的最高层…

React18 setState是同步还是异步?

相信大家对于react的setState肯定是不陌生了, 这是一个用于更新状态的函数. 但是在之前有一道非常经典的面试题就是关于setState是同步还是异步的问题, 具体可以参考我之前写的一篇文章: 一篇文章彻底理解setState是同步还是异步&#xff01;. 对于react 18之前的版本, 上文说的…

2019年MathorCup数学建模A题数据驱动的城市轨道交通网络优化策略解题全过程文档及程序

2019年第九届MathorCup高校数学建模挑战赛 A题 数据驱动的城市轨道交通网络优化策略 原题再现&#xff1a; 截至 2018 年 12 月 31 日&#xff0c;中国内地累计共有 35 座城市建成并投运城市轨道交通&#xff0c;里程共计 5766.6 公里。进入“十三五”以来&#xff0c;三年累…

Spring Bean实例创建装载过程分析-spring源码学习(2)

随着Spring框架的应用越来越广泛&#xff0c;对Spring Bean的实例创建装载过程的了解就显得尤为重要。本文将围绕这一主题&#xff0c;为大家详细介绍Spring Bean实例创建装载的整个过程&#xff0c;并透彻解析其细节。 时序图 一、Spring Bean实例的创建过程 Spring Bean实例…

Web前端学习:章三 -- JavaScript预热(二)

六五&#xff1a;作用域与function function&#xff1a;函数&#xff0c;不是数学上的函数&#xff0c;与写代码有关 JS中的函数&#xff1a;运用它&#xff0c;起个名字&#xff0c;然后对函数进行调用&#xff0c;即可将函数中的内容执行一遍 1、function 最基本的作用域…

CNCF x Alibaba云原生技术公开课 第五章 应用编排与管理

1、元数据的组成 用来识别资源的具有标识型的标签&#xff1a;Labels key valueselector(筛选/组合资源):多个相等条件&#xff0c;逻辑与的关系; 集合型,in notin 用来描述资源的非标识型的注解&#xff1a;Annotations 扩展资源的spec/status可以包含特殊字符可以结构化也可…

企业管理经典书籍推荐

几乎每一位成功的商业人士都有着良好的阅读习惯。并且他们阅读涉猎的范围也大多与企业管理和领导力有关。而关于企业管理经典书籍&#xff0c;我推荐你看以下这两本。一本是《经理人参阅&#xff1a;企业管理实务》&#xff0c;另一本是《经理人参阅&#xff1a;领导力提升》。…

无刷高速风筒方案介绍--【PCBA方案】

疫情三年过去&#xff0c;春节后&#xff0c;一个新的开始&#xff0c;大家满怀希望畅谈今年好气象。 三年来一波一波的封城、隔离、核酸&#xff0c;经济压抑到了无以复加的地步&#xff0c;也导致了诸多社会问题的出现。消费力被磨平&#xff0c;人们小心翼翼的生活。 常跟…

【第六课】Arcgis中基本操作

一、前言 前面课程已经对Arcgis主页面&#xff0c;相关板块进行介绍&#xff0c;相信大家也有了一定的了解&#xff0c;当然这部分内容其实不需要大家死记硬背&#xff0c;有一个初步印象即可&#xff0c;这一节课程可能更需要掌握&#xff0c;之后会慢慢有实例给大家展现&…

数据结构刷题(二十):17电话号码的字母组合、39组合总和、40组合总和II

一、电话号码的字母组合题目链接思路&#xff1a;回溯三部曲。确定回溯函数参数&#xff1a;题目中给的 digits&#xff0c;还要有一个参数就是int型的index&#xff08;记录遍历第几个数字&#xff0c;就是用来遍历digits的&#xff0c;同时也代表了递归的深度&#xff09;&am…

【牛客刷题专栏】0x10:JZ8 二叉树的下一个结点(C语言编程题)

前言 个人推荐在牛客网刷题(点击可以跳转)&#xff0c;它登陆后会保存刷题记录进度&#xff0c;重新登录时写过的题目代码不会丢失。个人刷题练习系列专栏&#xff1a;个人CSDN牛客刷题专栏。 题目来自&#xff1a;牛客/题库 / 在线编程 / 剑指offer&#xff1a; 目录前言问题…

@Component实现原理

直接从关键代码开始&#xff1a; 直接找到org.springframework.context.support.AbstractApplicationContext#refresh方法&#xff0c;找到invokeBeanFactoryPostProcessors(beanFactory)方法&#xff0c;最终找org.springframework.context.support.PostProcessorRegistratio…

各种各样的锁

1.悲观锁和乐观锁 一个共享数据加了悲观锁&#xff0c;那线程每次想操作这个数据前都会假设其他线程也可能会操作这个数据&#xff0c;所以每次操作前都会上锁&#xff0c;这样其他线程想操作这个数据拿不到锁只能阻塞了。 synchronized 和 ReentrantLock是典型的悲观锁 共享…

Linux学习记录——십사 进程控制(1)

文章目录1、进程创建1、fork函数2、进程终止1、情况分类2、如何理解进程终止3、进程终止的方式3、进程等待1、进程创建 1、fork函数 fork函数从已存在进程中创建一个新进程&#xff0c;新进程为子进程&#xff0c;原进程为父进程。 #include <unistd.h> pid_t fork(vo…

论文阅读:Syntax-Aware Network for Handwritten Mathematical Expression Recognition

论文阅读&#xff1a;Syntax-Aware Network for Handwritten Mathematical Expression Recognition1 主要观点&#xff1a; 1、提出将语法信息纳入编码器-解码器网络的方法。使用一组语法规则&#xff0c;用于将每个表达式的LaTeX标记序列转换为解析树&#xff1b;用深度神经…

【vue create】一.使用vue creat搭建项目

场景&#xff1a;使用vue create脚手架快速搭建vue的项目 前提&#xff1a;需要安装node.js和cnpm以及yarn 并且cnpm需要设置为淘宝镜像&#xff0c;cnpm和yarn安装教程网上很多可以自行搜索 1.使用dos命令安装vue-cli脚手架 //这个是从镜像源下载 cnpm install -g vue/cli 查…

Google三大论文之GFS

Google三大论文之GFS Google GFS&#xff08;Google File System&#xff09; 文件系统&#xff0c;一个面向大规模数据密集型应用的、可伸缩的分布式文件系统。GFS 虽然运行在廉价的普遍硬件设备上&#xff0c;但是它依然了提供灾难冗余的能力&#xff0c;为大量客户机提供了…