sentinel熔断降级

熔断降级

Slot 责任链上的最后一环:熔断降级 DegradeSlot,熔断降级作为保护系统的一种强大手段,可以根据慢调用、异常比例和异常数进行熔断,并自定义持续时间以实现系统保护

规则配置

规则类中属性解析

与控制面板对应

// 其中资源名称在 AbstractRule 里。
public class DegradeRule extends AbstractRule {/*熔断策略 (0: 平均RT,1: 异常比例,2: 异常计数)*/private int grade = RuleConstant.DEGRADE_GRADE_RT;// 阈值计数, 含义取决于所选择的熔断策略private double count;// 断路器断开后恢复时间(单位秒), 超时后, 断路器转换成半开状态, 允许部分请求通过private int timeWindow;// 触发熔断最低请求数private int minRequestAmount = RuleConstant.DEGRADE_DEFAULT_MIN_REQUEST_AMOUNT;// RT模式下慢请求比例的阈值private double slowRatioThreshold = 1.0d;// 间隔统计持续时间 (毫秒)private int statIntervalMs = 1000;
}
  • grade:熔断降级规则的类型,取值范围为

    • 慢调用比例: RuleConstant.DEGRADE_GRADE_RT=0, 默认值就是慢比例
    • 异常比例: RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO=1
    • 异常数: RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT=2
  • count:熔断降级的阈值,具体含义取决于 grade 字段的值

    • 慢调用比例: count 表示慢调用比例阈值
    • 异常比例: count 表示异常比例阈值
    • 异常数: count 表示异常数阈值
  • timeWindow: 熔断降级发生后的降级持续时间(单位:秒),在这段时间内,对应的资源将被降级, 超时后, 断路器转换成半开状态, 允许部分请求通过, 如果这部分请求还是不通过, 那么断路器转换成开状态, 继续熔断, 如果通过, 那么断路器转换成关状态

  • minRequestAmount: 熔断降级统计周期内的最小请求总数。仅当周期内的请求总数达到此值时,才会根据 gradecount 进行熔断降级。

    • 默认值为 RuleConstant.DEGRADE_DEFAULT_MIN_REQUEST_AMOUNT=5
  • slowRatioThreshold:慢调用比例阈值,仅当 grade 为慢调用比例时生效。取值范围为 0 到 1 之间的小数,表示慢调用请求占总请求的比例, 默认值为1

  • statIntervalMs:熔断降级统计周期(单位:毫秒)。在这个周期内,Sentinel 会对请求进行统计,以判断是否需要进行熔断降级。默认值为 1000 毫秒(1 秒)

断路器

不同的策略底层使用的算法不一样, 我们可以通过if-else进行, 也可以通过switch进行, 但是都不够优雅, 更加优雅的做法是, 使用策略模式, sentinel底层就是采用策略模式实现的

什么是策略模式不在赘述, 见这个链接策略模式 | 菜鸟教程 (runoob.com)

CircuitBreaker 是一个断路器接口, 用于实现 Sentinel 的熔断降级功能。它定义了一些关键方法和一个内部枚举类型 State

public interface CircuitBreaker {DegradeRule getRule();boolean tryPass(Context context);State currentState();void onRequestComplete(Context context);enum State {OPEN, // 开启HALF_OPEN, // 半开CLOSED // 关闭}
}
  1. DegradeRule getRule():获取当前断路器所对应的熔断降级规则。
  2. boolean tryPass(Context context):尝试通过断路器
    • 关闭状态(CLOSED): 则允许请求通过
    • 打开状态(OPEN): 则拒绝请求
    • 半开状态(HALF_OPEN): 则根据规则允许部分请求通过。
  3. State currentState():获取当前断路器的状态(OPEN, HALF_OPEN, CLOSED)
  4. void onRequestComplete(Context context):在请求完成后调用此方法,用于更新断路器的统计数据。

内部枚举类型 State

  • OPEN:表示断路器处于打开状态,此时会拒绝所有请求
  • HALF_OPEN:表示断路器处于半开状态,此时允许部分请求通过,以检测系统是否已经恢复正常
  • CLOSED:表示断路器处于关闭状态,此时允许所有请求通过

在这里插入图片描述

断路器接口实现类图如下

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

AbstractCircuitBreaker完成一些功能的基础功能

public abstract class AbstractCircuitBreaker implements CircuitBreaker {}

具体的策略实现类会继承该抽象类完成一些独有的逻辑

public class ExceptionCircuitBreaker extends AbstractCircuitBreaker {}public class ResponseTimeCircuitBreaker extends AbstractCircuitBreaker {}

规程初始化

熔断降级规则的初始化也是通过监听器模式来完成的。监听器就像是一个基础框架,所有的规则都是基于这套框架来实现的

规则的存储和转换

两个Map用户存储熔断策略熔断规则

public final class DegradeRuleManager {// 熔断策略private static volatile Map<String, List<CircuitBreaker>> circuitBreakers = new HashMap<>();// 熔断规则private static volatile Map<String, Set<DegradeRule>> ruleMap = new HashMap<>();
}

将调用者传入的 List<DegradeRule> 转换为上述两个 HashMap

private synchronized void reloadFrom(List<DegradeRule> list) {// List<DegradeRule> 转 List<CircuitBreaker>Map<String, List<CircuitBreaker>> cbs = buildCircuitBreakers(list);// 将断路器策略Map<String, List<DegradeRule>> rules = buildCircuitBreakerRules(cbs);circuitBreakers.updateRules(cbs);ruleMap.updateRules(rules);
}

buildCircuitBreakers

这里使用了策略模式, 并使用swtich进行分发

/*
List<DegradeRule> 转 List<CircuitBreaker>
*/
private Map<String, List<CircuitBreaker>> buildCircuitBreakers(List<DegradeRule> list) {// cbMap用于存储CircuitBreakerMap<String, List<CircuitBreaker>> cbMap = new HashMap<>(8);// 非空判断if (list == null || list.isEmpty()) {return cbMap;}// 遍历列表for (DegradeRule rule : list) {// 非法校验if (!isValidRule(rule)) {RecordLog.warn("[DegradeRuleManager] Ignoring invalid rule when loading new rules: {}", rule);continue;}// 如果规则的limitApp为空,则将其设置为默认值if (StringUtil.isBlank(rule.getLimitApp())) {rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);}// 根据规则获取已存在的CircuitBreaker或创建新的CircuitBreakerCircuitBreaker cb = getExistingSameCbOrNew(rule);if (cb == null) {RecordLog.warn("[DegradeRuleManager] Unknown circuit breaking strategy, ignoring: {}", rule);continue;}// 使用规则的资源名作为键,将CircuitBreaker添加到cbMap对应的列表中,如果cbMap中不存在该键则先创建空列表String resourceName = rule.getResource();List<CircuitBreaker> cbList = cbMap.get(resourceName);if (cbList == null) {cbList = new ArrayList<>();cbMap.put(resourceName, cbList);}cbList.add(cb);}return cbMap;
}/*
获取与给定降级规则相同的现有断路器或创建新的断路器
*/
private static CircuitBreaker getExistingSameCbOrNew(/*@Valid*/ DegradeRule rule) {// 根据给定的降级规则获取所有断路器的列表List<CircuitBreaker> cbs = getCircuitBreakers(rule.getResource());// 非空校验if (cbs == null || cbs.isEmpty()) {return newCircuitBreakerFrom(rule);}// 遍历断路器for (CircuitBreaker cb : cbs) {// 果找到与给定降级规则相同的断路器,则重用该断路器并返回if (rule.equals(cb.getRule())) {// Reuse the circuit breaker if the rule remains unchanged.return cb;}}// 执行到这里说明, 没有找到相同的断路器, 根据给定的规则创建新的断路器, 并返回return newCircuitBreakerFrom(rule);
}/*
根据指定的降级规则创建一个断路器
*/
private static CircuitBreaker newCircuitBreakerFrom(/*@Valid*/ DegradeRule rule) {// 根据断路器策略进行分发switch (rule.getGrade()) {// RT响应时间case RuleConstant.DEGRADE_GRADE_RT:return new ResponseTimeCircuitBreaker(rule);// 异常比例, 异常数case RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO:case RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT:return new ExceptionCircuitBreaker(rule);default:return null;}
}

核心流程

熔断验证
public class DegradeSlot extends AbstractLinkedProcessorSlot<DefaultNode> {@Overridepublic void entry(...) throws Throwable {// 熔断验证逻辑performChecking(...);// 放行fireEntry(context, resourceWrapper, node, count, prioritized, args);}@Overridepublic void exit(...) {}
}

可以看到核心熔断验证逻辑在performChecking(), 那么它做了什么事

  1. 获取断路器
  2. 熔断相关的校验, 失败就抛出降级异常
/*
熔断检查
*/
void performChecking(Context context, ResourceWrapper r) throws BlockException {// 先根据资源name获取断路器List<CircuitBreaker> circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName());if (circuitBreakers == null || circuitBreakers.isEmpty()) {return;}// 调用每个断路器的 tryPass 方法进行验证for (CircuitBreaker cb : circuitBreakers) {// 验证失败则抛出异常进行熔断if (!cb.tryPass(context)) {throw new DegradeException(cb.getRule().getLimitApp(), cb.getRule());}}
}

可以看到真正判断是否触发熔断的是tryPass()

tryPass()做了什么事?

  1. 检查当前断路器状态
    • 关闭: 不需要熔断, 放行
    • 打开: 继续往下执行
  2. 断路器是打开状态, 判断当前系统时间大于等于下一次尝试恢复的时间
    • 是: 将断路器状态更改成半开启
    • 否: 放行
/*
如果此次请求已经达到了断路器恢复时间,并且将断路器的状态从打开变为半开启(HALF_OPEN),则放行,反之拒绝
*/
@Override
public boolean tryPass(Context context) {// 断路器为关闭状态if (currentState.get() == State.CLOSED) {return true;}// 断路器为开启状态if (currentState.get() == State.OPEN) {// 如果此次请求已经达到了断路器恢复时间并且将断路器的状态从打开变为半开启(HALF_OPEN),则放行,反之拒绝return retryTimeoutArrived() && fromOpenToHalfOpen(context);}return false;
}

retryTimeoutArrived()

// nextRetryTimestamp:下一次尝试恢复的时间
protected boolean retryTimeoutArrived() {// 如果当前系统时间大于等于下一次尝试恢复的时间,也就是说已经到达了可以尝试恢复的时间,则返回 true,反之返回 falsereturn TimeUtil.currentTimeMillis() >= nextRetryTimestamp;
}

fromOpenToHalfOpen()

/*
尝试将断路器的状态从打开(OPEN)更改为半开启(HALF_OPEN)。如果状态切换成功,返回 true 表示请求放行;否则返回 false 表示拒绝请求
*/
protected boolean fromOpenToHalfOpen(Context context) {if (currentState.compareAndSet(State.OPEN, State.HALF_OPEN)) {// 这里使用观察者模式, 通知观察者, 当前断路器的状态从OPEN变成了HALF_OPENnotifyObservers(State.OPEN, State.HALF_OPEN, null);Entry entry = context.getCurEntry();entry.whenTerminate(new BiConsumer<Context, Entry>() {@Overridepublic void accept(Context context, Entry entry) {// Note: This works as a temporary workaround for https://github.com/alibaba/Sentinel/issues/1638// Without the hook, the circuit breaker won't recover from half-open state in some circumstances// when the request is actually blocked by upcoming rules (not only degrade rules).if (entry.getBlockError() != null) {// Fallback to OPEN due to detecting request is blockedcurrentState.compareAndSet(State.HALF_OPEN, State.OPEN);notifyObservers(State.HALF_OPEN, State.OPEN, 1.0d);}}});return true;}return false;
}

流程如下
在这里插入图片描述

断路器开关时机

熔断开关时期应该是触发配置阈值时, 但是数据何时采集?

entry()为请求入口, 此时还没结束, 无法获取到异常数, RT相关信息, 而exit()请求出口, 此时请求已经结束, 可以获取到RT, 异常数相关信息, 所以数据再exit()中采集

代码如下

@Override
public void exit(Context context, ResourceWrapper r, int count, Object... args) {Entry curEntry = context.getCurEntry();if (curEntry.getBlockError() != null) {fireExit(context, r, count, args);return;}List<CircuitBreaker> circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName());if (circuitBreakers == null || circuitBreakers.isEmpty()) {fireExit(context, r, count, args);return;}// 如果没报错,那就调用 onRequestComplete 方法来计数if (curEntry.getBlockError() == null) {// 放行该请求// 这里使用for循环的原因: 因为一个资源的断路器有多个, 完全可以对某个资源既按照慢调用比例进行熔断又按照异常数进行熔断for (CircuitBreaker circuitBreaker : circuitBreakers) {circuitBreaker.onRequestComplete(context);}}fireExit(context, r, count, args);
}

onRequestComplete的作用计数, 当请求结束时,会根据配置的熔断策略(异常比例或异常数)来更新计数器。如果达到阈值,断路器状态将从 CLOSED 变为 OPEN ,

具体实现看ExceptionCircuitBreakerResponseTimeCircuitBreaker, 下边分析

ExceptionCircuitBreaker

异常数: errorCount异常数

异常比例: 额外使用totalCount记录请求总数, 异常比例 = errorCount / totalCount

public class ExceptionCircuitBreaker extends AbstractCircuitBreaker {/*请求数据统计*/@Overridepublic void onRequestComplete(Context context) {Entry entry = context.getCurEntry();if (entry == null) {return;}Throwable error = entry.getError();// 获取当前值SimpleErrorCounter counter = stat.currentWindow().value();// 如果此次请求报错了,则将 errorCount + 1if (error != null) {counter.getErrorCount().add(1);}// 将 totalCount 总数 + 1,用于计算异常比例counter.getTotalCount().add(1);// 根据当前请求的异常数/异常比例与设定阈值的关系,调用handleStateChangeWhenThresholdExceeded(error)方法来执行相应的状态变更操作handleStateChangeWhenThresholdExceeded(error);}/*断路器开关变化逻辑处理*/private void handleStateChangeWhenThresholdExceeded(Throwable error) {// 如果当前断路器已经打开了,则直接返回。if (currentState.get() == State.OPEN) {return;}// 如果是半开启状态if (currentState.get() == State.HALF_OPEN) {// 如果本次请求没出现异常,则代表可以关闭断路器了,因此 fromHalfOpenToClose 关闭断路器if (error == null) {// 这里面会通知各个观察者fromHalfOpenToClose();} else {// 如果本次请求还是异常,那就继续熔断,打开断路器// 这里面会通知各个观察者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();}// 如果请求总数没超过最小请求数,那直接放行if (totalCount < minRequestAmount) {return;}// curCount表示为当前配置熔断触发阈值, 配置熔断策略不同, 含义也不同double curCount = errCount;// 熔断策略为异常比例if (strategy == DEGRADE_GRADE_EXCEPTION_RATIO) {// 计算异常比例, 公式: 异常比例 = 异常数 / 总请求数curCount = errCount * 1.0d / totalCount;}// 当错误率或者错误数大于阈值,则打开断路器if (curCount > threshold) {// 这里面会通知各个观察者transformToOpen(curCount);}}
}

异常数/异常比例的熔断降级了流程如下
在这里插入图片描述

ResponseTimeCircuitBreaker

这里主要统计的是慢比例调用数据,

慢比例计算公式如下

响应时间RT = 请求结束时间 - 请求开始时间

public class ResponseTimeCircuitBreaker extends AbstractCircuitBreaker {@Overridepublic 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();// 判断响应时间是否超过最大允许响应时间,若超过则将慢请求计数加1if (rt > maxAllowedRt) {counter.slowCount.add(1);}// 不论请求是否为慢请求,都将总请求计数加1counter.totalCount.add(1);// 根据当前请求的响应时间与设定阈值的关系,调用handleStateChangeWhenThresholdExceeded(rt)方法来执行相应的状态变更操作handleStateChangeWhenThresholdExceeded(rt);}
}
降级

降级就是抛出异常, 抛出异常也是降级的一种手段,Slot 相当于过滤器链,过滤器阶段就给拦截了,就不会进入到主业务流程当中,也就不会去查询数据库等一系列业务逻辑。当然,你可以捕获这个异常做一些你想做的事情,这就是降级

总结

断路器分类和原理

  1. 异常断路器
    • 负责异常数/异常比例
    • 请求结束时统计异常数和请求总数, 判断是否达到阈值, 达到阈值更改断路器状态
  2. RT断路器:
    • 负责的是响应时间
    • 计算请求结束和请求开始的差值, 和阈值比较, 判断是否达到阈值, 达到阈值更改断路器状态

断路器大体流程

  1. 计数
  2. 对比阈值
  3. 断路器验证

状态流转

  1. OPEN: 断路器打开, 系统进入熔断状态
  2. HALF_OPEN: 断路器半开, 系统放行部分请求, 如果请求通过, 断路器切回关闭状态, 如果请求出现异常, 断路器切回打开, 继续熔断
  3. CLAOSE: 断路器关闭, 系统正常

如下图

HALF_OPEN像是一个中间态
在这里插入图片描述

参考资料

通关 Sentinel 流量治理框架 - 编程界的小學生
服务熔断是指什么

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

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

相关文章

LiveGBS流媒体平台GB/T28181功能-大屏播放上大屏支持轮巡播放分屏轮巡值守播放监控视频轮播大屏轮询播放轮播

LiveGBS支持-大屏播放上大屏支持轮巡播放分屏轮巡值守播放监控视频轮播大屏轮询播放轮播 1、轮播功能2、分屏展示3、选择轮播通道4、配置轮播间隔(秒)5、点击开始轮播6、轮播停止及全屏7、搭建GB28181视频直播平台 1、轮播功能 视频监控项目使用过程中&#xff0c;有时需要大屏…

聊聊Go程序是如何运行的

写在文章开头 Go语言是一门编译语言&#xff0c;其工作过程即直接通过编译生成各大操作系统的机器码即可直接执行&#xff0c;所以这篇文章笔者就从底层汇编码的角度聊一聊Go语言是如何运行的。 Hi&#xff0c;我是 sharkChili &#xff0c;是个不断在硬核技术上作死的 java c…

IntelliJ IDEA 设置运行时环境变量

背景 博主要测试langchain4j&#xff0c;运行时需要OPENAI_BASE_URL和OPENAI_API_KEY这两个环境变量的值。 临时设置 Run -> Edit Configurations -> Edit Environmental Variables 永久设置 在系统环境变量中设置&#xff0c;教程无数。 注意&#xff1a;windows在…

MATLAB教程

目录 前言一、MATLAB基本操作1.1 界面简介1.2 搜索路径1.3 交互式命令操作1.4 帮助系统 二、MATLAB语言基础2.1 数据类型2.2 MATLAB运算2.2.1 算数运算2.2.2 关系运算2.2.3 逻辑运算 2.3 常用内部函数2.4 结构数据与单元数据 三、MATLAB程序设计3.1 M文件3.2 函数文件3.3 程序控…

c#接口 axios的get请求url过长时该怎么做

今天又碰到了&#xff0c;大托参数拼在url里用get传 1、改服务器最大字数限制&#xff08;还是会错&#xff09; 2、改post&#xff08;有些还要跟着把[FromUri]改成[FromBody]&#xff09;

windows查看局域网内所有已使用的IP IP扫描工具 扫描网段下所有的IP Windows环境下

推荐使用&#xff1a; Advanced IP Scanner 官网下载&#xff1a; https://www.advanced-ip-scanner.com/

sqllab第35-45关通关笔记

35关知识点&#xff1a; 宽字节注入数值型注入错误注入 payload:id1andextractvalue(1,concat(0x7e,database(),0x7e))0--联合注入 payload:id0unionselect1,database(),version()-- 36关知识点&#xff1a; 字符型注入宽字节注入错误注入 payload:id1%df%27andextractvalue(…

广州大彩科技新品发布:大彩科技COF系列2.4寸串口屏发布!

一、产品介绍 此次发布的是S系列平台2.4寸COF超薄结构串口屏&#xff0c;分辨率为240*320&#xff0c;该平台采用了Cortex-M3内核的处理器&#xff0c;内置了2Mbyte PSRAM和64Mbit FLASH&#xff0c;是专为小尺寸串口屏设计的MCU&#xff0c;精简了外围电路。 该平台默认支持大…

青海200MW光伏项目 35kV开关站图像监控及安全警示系统

一、背景 随着我国新能源产业的快速发展&#xff0c;光伏发电作为清洁能源的重要组成部分&#xff0c;得到了国家政策的大力扶持。青海作为我国光伏资源丰富地区&#xff0c;吸引了众多光伏项目的投资建设。在此背景下&#xff0c;为提高光伏发电项目的运行效率和安全性能&…

【C++】堆区空间的申请和释放--- 2024.3.19

目录 C和C的区别&#xff08;申请堆区空间&#xff09;C中的new和delete结束语 C和C的区别&#xff08;申请堆区空间&#xff09; 在c语言中&#xff0c;在遇到需要申请一块堆区空间时&#xff0c;我们往往会使用malloc申请&#xff0c;使用free进行释放&#xff0c;但是为什么…

数据可信流通:从运维信任到技术信任

1.数据可信流通概念 "数据可信流通"通常指的是确保数据在不同系统、应用程序或者组织之间的传输和交换过程中的可信性、完整性和安全性。在数据流通的过程中&#xff0c;确保数据的真实性、完整性和保密性是非常重要的&#xff0c;尤其是涉及到敏感信息或者重要数据…

GateWay路由规则

Spring Cloud GateWay 帮我们内置了很多 Predicates功能&#xff0c;实现了各种路由匹配规 则&#xff08;通过 Header、请求参数等作为条件&#xff09;匹配到对应的路由 1 时间点后匹配 server:port: 8888 spring:application:name: gateway-servicecloud:nacos:discovery:…

mysql虚拟列Generated Column

目录​​​​​​​ 1、Generated Column简介 生成的列定义具有以下语法&#xff1a; 2、实践 2.1 存储格式为json字段增加索引 2.2 手机号后四位 3、虚拟列索引介绍 3.1 虚拟列索引的限制 3.1.1 Virtal Generated Column 4、阿里云数据库环境是否支持 下期扩展&…

大话适航(一)民航产业

0. 前言 eVTOL、飞行汽车和低空经济已成为热门话题&#xff0c;政府引导资本投入新赛道&#xff0c;也势必会吸引跨界厂商前来淘金。只说民用航空器整机制造&#xff0c;技术最接近的行业是军工&#xff0c;然后是无人机&#xff0c;还有汽车、农业机械等。“互联网”曾经掀起…

数据库应用:Linux 部署 GaussDB

目录 一、实验 1.环境 2.Linux 部署 GaussDB 3.Linux 使用 GaussDB 4.使用 GaussDB 进行表与索引操作 5.使用 GaussDB 进行视图操作 6.使用 GaussDB 进行联表查询 7.使用 GaussDB 进行外键关联 二、问题 1.运行python脚本报错 2. 安装GaussDB 报错 3. install 安装…

语音识别:whisper部署服务器,可远程访问,实时语音转文字(全部代码和详细部署步骤)

Whisper是OpenAI于2022年发布的一个开源深度学习模型&#xff0c;专门用于语音识别任务。它能够将音频转换成文字&#xff0c;支持多种语言的识别&#xff0c;包括但不限于英语、中文、西班牙语等。Whisper模型的特点是它在多种不同的音频条件下&#xff08;如不同的背景噪声水…

[C语言]指针详解一、数组指针、二维数组传参、函数指针

一、数组指针 对一个数组&#xff0c;如果我们想要让一个指针指向这个数组&#xff0c;我们应该如何定义呢?我们知道一个数组定义本来就是一个指针&#xff0c;那为何要多定义一个数组指针呢?我们来看看下面这个代码就理解了 #include <stdio.h> int main() {int arr…

【基础】哪个厂家的零件更标准?

时间限制 : 1 秒 内存限制 : 128 MB 在统计描述中,方差用来计算每一个变量(观察值)与总体均数之间的差异。比如:甲乙 2 个厂商生产某零件,一批零件要求在尺寸合格的情况下,大小越一致越好,由于生产工艺的问题,零件生产厂商生产的零件不可能一模一样。 为了检测甲乙两…

AIX系统下挂载ISO镜像

我们需要将AIX的iso文件作为软件包的安装源挂载的系统目录中 首先我们查看系统下有哪些挂载文件 如何挂载一个系统iso镜像文件 loopmount -i /ftp/iso/LK4T_1807_11.iso -o "-V cdrfs -o ro " -m /mnt/iso 需要安装软件直接执行smit就可以了&#xff0c;在smit中…

phpStudy安装thinkCMF8时,如何解决服务器rewrite和APIrewrite不支持的问题

解决步骤&#xff1a; 一&#xff1a;服务器rewrite 点击后面的问号跳转到官方文档链接&#xff1a; 复制红框内的代码 打开phpstudy&#xff0c;找到配置的站点&#xff0c;点击管理&#xff0c;找到伪静态 点击确认保存即可。 phpstudy会自动重启站点。 此时&#xff0c;…