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
: 熔断降级统计周期内的最小请求总数。仅当周期内的请求总数达到此值时,才会根据grade
和count
进行熔断降级。- 默认值为
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 // 关闭}
}
DegradeRule getRule()
:获取当前断路器所对应的熔断降级规则。boolean tryPass(Context context)
:尝试通过断路器关闭状态(CLOSED)
: 则允许请求通过打开状态(OPEN)
: 则拒绝请求半开状态(HALF_OPEN)
: 则根据规则允许部分请求通过。
State currentState()
:获取当前断路器的状态(OPEN, HALF_OPEN, CLOSED)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(), 那么它做了什么事
- 获取断路器
- 熔断相关的校验, 失败就抛出降级异常
/*
熔断检查
*/
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()做了什么事?
- 检查当前断路器状态
- 关闭: 不需要熔断, 放行
- 打开: 继续往下执行
- 断路器是打开状态, 判断当前系统时间大于等于下一次尝试恢复的时间
- 是: 将断路器状态更改成半开启
- 否: 放行
/*
如果此次请求已经达到了断路器恢复时间,并且将断路器的状态从打开变为半开启(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
,
具体实现看ExceptionCircuitBreaker和ResponseTimeCircuitBreaker, 下边分析
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 相当于过滤器链,过滤器阶段就给拦截了,就不会进入到主业务流程当中,也就不会去查询数据库等一系列业务逻辑。当然,你可以捕获这个异常做一些你想做的事情,这就是降级
总结
断路器分类和原理
- 异常断路器
- 负责异常数/异常比例
- 请求结束时统计异常数和请求总数, 判断是否达到阈值, 达到阈值更改断路器状态
- RT断路器:
- 负责的是响应时间
- 计算请求结束和请求开始的差值, 和阈值比较, 判断是否达到阈值, 达到阈值更改断路器状态
断路器大体流程
- 计数
- 对比阈值
- 断路器验证
状态流转
OPEN
: 断路器打开, 系统进入熔断状态HALF_OPEN
: 断路器半开, 系统放行部分请求, 如果请求通过, 断路器切回关闭状态, 如果请求出现异常, 断路器切回打开, 继续熔断CLAOSE
: 断路器关闭, 系统正常
如下图
HALF_OPEN
像是一个中间态
参考资料
通关 Sentinel 流量治理框架 - 编程界的小學生
服务熔断是指什么
相关文章:
sentinel熔断降级
熔断降级 Slot 责任链上的最后一环:熔断降级 DegradeSlot,熔断降级作为保护系统的一种强大手段,可以根据慢调用、异常比例和异常数进行熔断,并自定义持续时间以实现系统保护 规则配置 规则类中属性解析 与控制面板对应 // 其中资源名称在 AbstractRule 里。 pu…...
Redis的安装和部署教程(Windows环境)
一、安装Redis服务 1、下载Redis压缩包 以下这个是我网盘里面的(这个是v8.0版本的,支持导入.rdb数据文件) 链接:百度网盘 请输入提取码 提取码:x0f1 --来自百度网盘超级会员V5的分享 2、解压到文件夹 将下载的压缩…...
MNN Session::resize 之流水线编码(五)
系列文章目录 MNN createFromBuffer(一) MNN createRuntime(二) MNN createSession 之 Schedule(三) MNN createSession 之创建流水线后端(四) 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 一个不完整的示例: 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…...
缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级的理解
一:缓存雪崩 我们可以简单的理解为:由于原有缓存失效,新缓存未到期间 (例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了ÿ…...
springcloud gateway
一、 predicate : 就是你定义一些规则,如果满足了这些规则,就去找到对应的路由。 对于strip 二、自定义过略器和全局过滤器 约定大于配置,后缀不变,只改前缀 sentinel持久化 三、sentinel quick-start | Sentinel 信号量虽然简…...
JAVA八股day1
遇到的问题 相比于包装类型(对象类型), 基本数据类型占用的空间往往非常小为什么说是几乎所有对象实例都存在于堆中呢?静态变量和成员变量、成员变量和局部变量的区别为什么浮点数运算的时候会有精度丢失的风险?如何解…...
探索拓展坞的奥秘:提升电脑接口的无限可能
在数字化时代的浪潮中,电脑已成为我们日常生活和工作中不可或缺的一部分。然而,随着外接设备的日益增多,电脑接口的数量和类型往往无法满足我们的需求。这时,拓展坞便应运而生,以其强大的扩展能力和便捷的使用方式&…...
Linux中执行脚本报错(脚本乱码问题)
主要原因是在windows中编译文件格式导致 linux下解决: 方案一: Linux下打开shell文件,用vi/vim命令打开脚本文件,输入“:set fileformatunix”,回车,保存退出。 方案二: 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数据库中的数据导入是一个常见操作,它涉及将数据从外部源转移到MySQL数据库表中。在本教程中,我们将探讨几种常见的数据导入方式,包括它们的特点、使用场景以及简单的示例。 1. 命令行导入 使用MySQL命令行工具mysql是导入数据的…...
构建部署_Docker常用命令
构建部署_Docker常见命令 启动命令镜像命令容器命令 启动命令 启动docker:systemctl start docker 停止docker:systemctl stop docker 重启docker:systemctl restart docker 查看docker状态:systemctl status docker 开机启动&…...
Spring Boot Actuator介绍
大家在yaml中经常见到的这个配置 management: endpoints: web: exposure: #该配置线上需要去掉,会有未授权访问漏洞 include: "*" 他就是Actuator! 一、什么是 Actuator Spring Boot Actuator 模块提供了生产级别…...
数据库中DQL、DML、DDL、DCL的概念与区别
目录 DQL (Data Query Language) DML (Data Manipulation Language) DDL (Data Definition Language) DCL (Data Control Language) 数据库语言可以根据其功能被分为几个不同的类别:DQL(数据查询语言)、DML(数据操纵语言&…...
MacOS---设置Java环境变量
介绍 在MacOS系统配置Java环境变量。 操作步骤 第一步:打开.bash_profile文件 vim ~/.bash_profile第二步:添加或修改配置 如果是第一次配置需要添加配置如果是已经配置过想更换其他版本需要修改配置 在文件末尾添加或修改下面的配置 export JAVA…...
使用 Boot Camp 助理查明您的 Mac 需不需要 Windows 安装介质
使用 Boot Camp 助理查明您的 Mac 需不需要 Windows 安装介质 当前的 Mac 机型无需介质即可安装 Windows,也就是说,您不需要用到外置驱动器。较早的 Mac 机型需要用到 USB 驱动器或光盘驱动器。使用 Boot Camp 助理可查明您需要用到什么。 Boot Camp 助…...
KY105 整除问题(用Java实现)
描述 给定n,a求最大的k,使n!可以被a^k整除但不能被a^(k1)整除。 输入描述: 两个整数n(2<n<1000),a(2<a<1000) 输出描述: 一个整数. 示例1 输入: 6 10输出: 1代…...
C++ 接口的实现,及作用通俗理解方式
接口 C中的接口,一般就是指抽象类,是一种用来描述类对外提供的操作、方法或功能的集合——注意,一般只是描述(声明),而不对这些方法或功能进行定义实现,通常在类的继承或多态中作为基类使用&am…...
TypeScript:typescript的安装与运行
TypeScript:typescript的安装与运行 1 安装方式 -g全局安装TypeScript: npm install -g typescript2 运行方式 (1)ts编译成js,使用node命令运行js文件 打开vscode,进入ts文件所在目录下并打开终端term…...
【代码随想录Day27】
Day 27 回溯算法03 今日任务 组合总和 40.组合总和II131.分割回文串 代码实现 组合总和,直接套模板可解 public List<List<Integer>> combinationSum(int[] candidates, int target) {backtracking(candidates, target, 0);return result;}void back…...
【一】【单片机】有关LED的实验
点亮一个LED灯 根据LED模块原理图,我们可以知道,通过控制P20、P21...P27这八个位置的高低电平,可以实现D1~D8八个LED灯的亮灭。VCC接的是高电平,如果P20接的是低电平,那么D1就可以亮。如果P20接的是高电平,…...
面试算法-49-缺失的第一个正数
题目 给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。 请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。 示例 1: 输入:nums [1,2,0] 输出:3 解释:范围 [1,2] 中的数字都…...
论文笔记:液体管道泄漏综合检测与定位模型
0 简介 An integrated detection and location model for leakages in liquid pipelines 1 摘要 许多液体,如水和油,都是通过管道运输的,在管道中可能发生泄漏,造成能源浪费、环境污染和对人类健康的威胁。本文描述了一种集成的…...
抖音视频批量提取软件|无水印视频下载
抖音视频批量提取软件,让您高效下载精彩内容! 您是否经常需要下载抖音视频,但传统的下载方式繁琐且低效?别担心,我们为您提供了一款强大而智能的抖音视频批量提取软件,让您轻松实现下载无水印的精彩内容&am…...
Linux docker1--环境及docker安装
一、基础环境要求 Docker分为ce版本(免费,试用7个月)和ee版本(收费)。 最低配置要求:64位操作系统,centOS 7及以上,内核版本不低于3.10 二、部署docker 1、查看服务的基础环境是否满…...
uniapp使用uview - DatetimePicker 时间选择器 /时间戳转化
uniapp使用uview - DatetimePicker 时间选择器 /时间戳转化时转换日期格式后页面仍显示时间戳 单元格内显示时间,点击可出现时间选择器切换时间 <u-cell :isLinktrue click"selectTime" title"开始时间" :value"startTime">…...
python实现websocket
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它允许客户端和服务器之间进行实时数据传输,而不是像 HTTP 协议那样,每次请求都需要建立新的连接。WebSocket 协议最初是由 HTML5 定义的,旨在提供一种更有效的替代方案,…...
ElasticSearch简介及常见用法
简介 Elasticsearch 是 Elastic Stack 核心的分布式搜索和分析引擎。 Logstash 和 Beats 有助于收集、聚合和丰富您的数据并将其存储在 Elasticsearch 中。 Kibana 使您能够以交互方式探索、可视化和分享对数据的见解,并管理和监控堆栈。 Elasticsearch 可以快速索…...
js iframe获取documen中的对象为空问题
原因其实是iframe加载是需要时间的,它还没加载完我就在js中直接获取对象了,所以获取为空 var idocument.getElementById("iframe"); i.onloadfunction(){console.log(i.contentDocument)console.log(i.contentWindow.document.getElementById…...
企业做网站的凭证怎么做/cms快速建站
基本用法 可以用v-model指令在表单控件元素上创建双向数据绑定;它会根据控件类型自动选取正确的方法来更新元素;但v-model本质上是语法糖,它负责监听用户的输入事件以更新数据,并特别处理一些极端的例子 v-model会忽略所有表单元素…...
设计电子商务网站主页/整合营销活动策划方案
⛵ ⛵ ⛵ ⛵ ⛵ 🚀 🚀 🚀 🚀 🚀 相 见 就 是 【 猿 分 】 大家好我是 👉 老孙 👈 淦淦淦 🤝 🔥 🔥 🔥 🔥 🔥 ⭐ ⭐ …...
火星时代ui设计培训怎么样/武汉seo首页优化报价
上一篇:《修炼SpringMVC-注解开发-处理器的请求参数配置》如果文中有纰漏,请多多指正!!!使用Controller注解,注解的处理器,其内部方法常用返回值大致总结为以下4种类型:返回ModelAndView类型返回…...
微信公众号做微网站吗/app推广软件有哪些
今天在Windows上配置了下nginx,看了不少其他大牛们记录的博客,自己也操作了一番,记录一下备忘。 nginx download: http://nginx.org/en/download.htmlphp download: http://php.net/windows下nginxphp的安装配置如下:1、安装php(D…...
wordpress 邮件防骚扰/品牌推广活动策划方案
开篇 AgileEAS.NET5.0平台,预计这个月的中旬就会发布,这次发布里面相比上次的AgileEAS.NET4.0的版本主要的变化是以下几块内容: 本文,主要是针对其中的工作流这块,进行讲述基本的说明,这个月的中旬,大家就可…...