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

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 流量治理框架 - 编程界的小學生
服务熔断是指什么

相关文章:

sentinel熔断降级

熔断降级 Slot 责任链上的最后一环&#xff1a;熔断降级 DegradeSlot,熔断降级作为保护系统的一种强大手段,可以根据慢调用、异常比例和异常数进行熔断,并自定义持续时间以实现系统保护 规则配置 规则类中属性解析 与控制面板对应 // 其中资源名称在 AbstractRule 里。 pu…...

Redis的安装和部署教程(Windows环境)

一、安装Redis服务 1、下载Redis压缩包 以下这个是我网盘里面的&#xff08;这个是v8.0版本的&#xff0c;支持导入.rdb数据文件&#xff09; 链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;x0f1 --来自百度网盘超级会员V5的分享 2、解压到文件夹 将下载的压缩…...

MNN Session::resize 之流水线编码(五)

系列文章目录 MNN createFromBuffer&#xff08;一&#xff09; MNN createRuntime&#xff08;二&#xff09; MNN createSession 之 Schedule&#xff08;三&#xff09; MNN createSession 之创建流水线后端&#xff08;四&#xff09; MNN Session::resize 之流水线编码&am…...

2. IS-IS 基础实验

2.1 IS-IS 配置实验 2.1.1 实验介绍 2.1.1.1 学习目标 1. 实现 IS-IS 协议基本配置 2. 实现 IS-IS 协议 DIS 优先级修改 3. 实现 IS-IS 协议网络类型修改 4. 实现 IS-IS 协议外部路由引入 5. 实现 IS-IS 接口 cost 修改 6. 实现 IS-IS 路由渗透配置 2.1.1.2 实验组网介…...

Rust 并行库 crossbeam 的 Channel 示例

示例1 一个不完整的示例&#xff1a; let (tx, rx) channel::unbounded::<Task>(); let mut handlers vec![];for _ in 0..number {let rx rx.clone();let handle thread::spawn(move || {while let Some(task) rx.recv() {task.call_box();}});handlers.push(han…...

缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级的理解

一&#xff1a;缓存雪崩 我们可以简单的理解为&#xff1a;由于原有缓存失效&#xff0c;新缓存未到期间 (例如&#xff1a;我们设置缓存时采用了相同的过期时间&#xff0c;在同一时刻出现大面积的缓存过期)&#xff0c;所有原本应该访问缓存的请求都去查询数据库了&#xff…...

springcloud gateway

一、 predicate : 就是你定义一些规则&#xff0c;如果满足了这些规则&#xff0c;就去找到对应的路由。 对于strip 二、自定义过略器和全局过滤器 约定大于配置&#xff0c;后缀不变&#xff0c;只改前缀 sentinel持久化 三、sentinel quick-start | Sentinel 信号量虽然简…...

JAVA八股day1

遇到的问题 相比于包装类型&#xff08;对象类型&#xff09;&#xff0c; 基本数据类型占用的空间往往非常小为什么说是几乎所有对象实例都存在于堆中呢&#xff1f;静态变量和成员变量、成员变量和局部变量的区别为什么浮点数运算的时候会有精度丢失的风险&#xff1f;如何解…...

探索拓展坞的奥秘:提升电脑接口的无限可能

在数字化时代的浪潮中&#xff0c;电脑已成为我们日常生活和工作中不可或缺的一部分。然而&#xff0c;随着外接设备的日益增多&#xff0c;电脑接口的数量和类型往往无法满足我们的需求。这时&#xff0c;拓展坞便应运而生&#xff0c;以其强大的扩展能力和便捷的使用方式&…...

Linux中执行脚本报错(脚本乱码问题)

主要原因是在windows中编译文件格式导致 linux下解决&#xff1a; 方案一&#xff1a; Linux下打开shell文件&#xff0c;用vi/vim命令打开脚本文件&#xff0c;输入“:set fileformatunix”&#xff0c;回车&#xff0c;保存退出。 方案二&#xff1a; yum install -y dos2uni…...

el-table按钮获取当前行元素

el-table按钮获取当前行元素 vue2 <el-table-column label"操作" width"240px"><template slot-scope"scope"><el-button size"mini" click"toItem(scope.row)">用户详情</el-button><el-butto…...

MySQL数据导入的方式介绍

MySQL数据库中的数据导入是一个常见操作&#xff0c;它涉及将数据从外部源转移到MySQL数据库表中。在本教程中&#xff0c;我们将探讨几种常见的数据导入方式&#xff0c;包括它们的特点、使用场景以及简单的示例。 1. 命令行导入 使用MySQL命令行工具mysql是导入数据的…...

构建部署_Docker常用命令

构建部署_Docker常见命令 启动命令镜像命令容器命令 启动命令 启动docker&#xff1a;systemctl start docker 停止docker&#xff1a;systemctl stop docker 重启docker&#xff1a;systemctl restart docker 查看docker状态&#xff1a;systemctl status docker 开机启动&…...

Spring Boot Actuator介绍

大家在yaml中经常见到的这个配置 management: endpoints: web: exposure: #该配置线上需要去掉&#xff0c;会有未授权访问漏洞 include: "*" 他就是Actuator&#xff01; 一、什么是 Actuator Spring Boot Actuator 模块提供了生产级别…...

数据库中DQL、DML、DDL、DCL的概念与区别

目录 DQL (Data Query Language) DML (Data Manipulation Language) DDL (Data Definition Language) DCL (Data Control Language) 数据库语言可以根据其功能被分为几个不同的类别&#xff1a;DQL&#xff08;数据查询语言&#xff09;、DML&#xff08;数据操纵语言&…...

MacOS---设置Java环境变量

介绍 在MacOS系统配置Java环境变量。 操作步骤 第一步&#xff1a;打开.bash_profile文件 vim ~/.bash_profile第二步&#xff1a;添加或修改配置 如果是第一次配置需要添加配置如果是已经配置过想更换其他版本需要修改配置 在文件末尾添加或修改下面的配置 export JAVA…...

使用 Boot Camp 助理查明您的 Mac 需不需要 Windows 安装介质

使用 Boot Camp 助理查明您的 Mac 需不需要 Windows 安装介质 当前的 Mac 机型无需介质即可安装 Windows&#xff0c;也就是说&#xff0c;您不需要用到外置驱动器。较早的 Mac 机型需要用到 USB 驱动器或光盘驱动器。使用 Boot Camp 助理可查明您需要用到什么。 Boot Camp 助…...

KY105 整除问题(用Java实现)

描述 给定n&#xff0c;a求最大的k&#xff0c;使n&#xff01;可以被a^k整除但不能被a^(k1)整除。 输入描述&#xff1a; 两个整数n(2<n<1000)&#xff0c;a(2<a<1000) 输出描述&#xff1a; 一个整数. 示例1 输入&#xff1a; 6 10输出&#xff1a; 1代…...

C++ 接口的实现,及作用通俗理解方式

接口 C中的接口&#xff0c;一般就是指抽象类&#xff0c;是一种用来描述类对外提供的操作、方法或功能的集合——注意&#xff0c;一般只是描述&#xff08;声明&#xff09;&#xff0c;而不对这些方法或功能进行定义实现&#xff0c;通常在类的继承或多态中作为基类使用&am…...

TypeScript:typescript的安装与运行

TypeScript&#xff1a;typescript的安装与运行 1 安装方式 -g全局安装TypeScript&#xff1a; npm install -g typescript2 运行方式 &#xff08;1&#xff09;ts编译成js&#xff0c;使用node命令运行js文件 打开vscode&#xff0c;进入ts文件所在目录下并打开终端term…...

[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解

突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 ​安全措施依赖问题​ GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

pam_env.so模块配置解析

在PAM&#xff08;Pluggable Authentication Modules&#xff09;配置中&#xff0c; /etc/pam.d/su 文件相关配置含义如下&#xff1a; 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块&#xff0c;负责验证用户身份&am…...

1688商品列表API与其他数据源的对接思路

将1688商品列表API与其他数据源对接时&#xff0c;需结合业务场景设计数据流转链路&#xff0c;重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点&#xff1a; 一、核心对接场景与目标 商品数据同步 场景&#xff1a;将1688商品信息…...

Java多线程实现之Callable接口深度解析

Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...

在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?

uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件&#xff0c;用于在原生应用中加载 HTML 页面&#xff1a; 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

Netty从入门到进阶(二)

二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架&#xff0c;用于…...

tauri项目,如何在rust端读取电脑环境变量

如果想在前端通过调用来获取环境变量的值&#xff0c;可以通过标准的依赖&#xff1a; std::env::var(name).ok() 想在前端通过调用来获取&#xff0c;可以写一个command函数&#xff1a; #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...

Ubuntu系统多网卡多相机IP设置方法

目录 1、硬件情况 2、如何设置网卡和相机IP 2.1 万兆网卡连接交换机&#xff0c;交换机再连相机 2.1.1 网卡设置 2.1.2 相机设置 2.3 万兆网卡直连相机 1、硬件情况 2个网卡n个相机 电脑系统信息&#xff0c;系统版本&#xff1a;Ubuntu22.04.5 LTS&#xff1b;内核版本…...

消防一体化安全管控平台:构建消防“一张图”和APP统一管理

在城市的某个角落&#xff0c;一场突如其来的火灾打破了平静。熊熊烈火迅速蔓延&#xff0c;滚滚浓烟弥漫开来&#xff0c;周围群众的生命财产安全受到严重威胁。就在这千钧一发之际&#xff0c;消防救援队伍迅速行动&#xff0c;而豪越科技消防一体化安全管控平台构建的消防“…...

SQL进阶之旅 Day 22:批处理与游标优化

【SQL进阶之旅 Day 22】批处理与游标优化 文章简述&#xff08;300字左右&#xff09; 在数据库开发中&#xff0c;面对大量数据的处理任务时&#xff0c;单条SQL语句往往无法满足性能需求。本篇文章聚焦“批处理与游标优化”&#xff0c;深入探讨如何通过批量操作和游标技术提…...