Sentinel架构篇 - 熔断降级
熔断降级
概念
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用其它模块,可能是一个远程服务、数据库、或者第三方 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-consumercloud:nacos:discovery:server-addr: 10.211.55.11:8848,10.211.55.12:8848,10.211.55.13:8848enabled: truesentinel:transport:dashboard: 127.0.0.1:9000eager: truedatasource:degrade-nacos-datasource:nacos:server-addr: 10.211.55.11:8848,10.211.55.12:8848,10.211.55.13:8848group-id: DEFAULT_GROUPnamespace: publicdata-id: ${spring.application.name}-sentinel-degradedata-type: jsonrule-type: degradeusername: nacospassword: nacos
DegradeSlot
负责熔断降级规则的判断。
@Spi(order = Constants.ORDER_DEGRADE_SLOT)
public class DegradeSlot extends AbstractLinkedProcessorSlot<DefaultNode> {@Overridepublic 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());}}}@Overridepublic 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;}// 如果断路器的状态是OPENif (currentState.get() == State.OPEN) {// 判断是否到了熔断结束时间,如果到了则尝试将断路器的状态从OPEN变为HALF-OPENreturn 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_OPENif (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>() {@Overridepublic void accept(Context context, Entry entry) {// 如果调用过程中存在BlockException异常if (entry.getBlockError() != null) {// 将断路器的状态从HALF_OPEN更新为OPENcurrentState.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_OPENif (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_OPENif (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);}
}
相关文章:
Sentinel架构篇 - 熔断降级
熔断降级 概念 除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用其它模块,可能是一个远程服务、数据库、或者第三方 API 等。然而,被依赖的服务的稳定性是不能保证的。如果依赖的服…...
shell脚本的一些记录 与jenkins的介绍
shell 脚本的执行 sh ***.sh shell脚本里面的命令 其实就是终端执行一些命令 shell 连接服务器 可以直接ssh连接 但是这样最好是无密码的 不然后面的命令就不好写了 换而言之有密码得 不好写脚本 需要下载一些expect的插件之类的才可以 判断语句 的示例 需要注意的是…...
JVM的了解与学习
一:jvm是什么 jvm是java虚拟机java Virtual Machine的缩写 jdk包含jre和java DevelopmentTools 二:什么是java虚拟机 虚拟机是一种抽象化的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的。java虚拟机有自己完善的硬体结构,如处理器、堆栈、寄存器等,还有…...
提升数字品牌的5个技巧
“品牌”或“品牌推广”的概念通常用于营销。因为建立您的企业品牌对于产品来说极其重要,品牌代表了您与客户互动的身份和声音。今天,让我们来看看在数字领域提升品牌的一些有用的技巧。如何在数字领域提升您的品牌?在了解这些技巧之前&#…...
java通过反射获取加了某个注解的所有的类
有时候我们会碰到这样的情况:有n个场景,每个场景都有自己的逻辑,即n个处理逻辑,这时候我们就需要通过某个参数的值代表这n个场景,然后去加载每个场景不同的bean对象,即不同的类,这些类中都有一个…...
Warshall算法
🚀write in front🚀 📜所属专栏:> 算法 🛰️博客主页:睿睿的博客主页 🛰️代码仓库:🎉VS2022_C语言仓库 🎡您的点赞、关注、收藏、评论,是对我…...
vector中迭代器失效的问题及解决办法
目录 vector常用接口 vector 迭代器失效问题 vector中深浅拷贝问题 vector的数据安排以及操作方式,与array非常相似。两者的唯一差别在于空间的运用的灵活性。array 是静态空间,一旦配置了就不能改变;要换个大(或小) 一点的房子&#x…...
【蓝桥杯刷题训练营】day05
1 数的分解 拆分成3个数相加得到该数 然后采用了一种巨愚蠢的办法: int main() {int count 0;int a 2;int b 0;int c 1;int d 9;int a1, a2, a3;int c1, c2, c3;int d1, d2, d3;for (a1 0; a1 < 2; a1){for (a2 0; a2 < 2; a2){for (a3 0; a3 <…...
线程中断interrupt导致sleep产生的InterruptedException异常
强制当前正在执行的线程休眠(暂停执行),以“减慢线程”。 Thread.sleep(long millis)和Thread.sleep(long millis, int nanos)静态方法当线程睡眠时,它睡在某个地方,在苏醒之前不会返回到可运行状态。 当睡眠时间到期…...
ubuntu的快速安装与配置
文章目录前言一、快速安装二 、基础配置1 Sudo免密码2 ubuntu20.04 pip更新源3 安装和配置oneapi(infort/mpi/mkl) apt下载第一次下载的要建立apt源apt下载(infort/mpi/mkl)4 安装一些依赖库等5 卸载WSLpython总结前言 win11系统 ubuntu20.04 提示:以下…...
人工智能AI工具汇总(AIGC ChatGPT时代个体崛起)
NameCategoryWebsiteDescription描述《AIGC时代:超级个体的崛起》小报童https://xiaobot.net/p/SuperIndividual 介绍AIGC,ChatGPT,使用技巧与搞钱方式。Masterpiece Studio3Dhttps://masterpiecestudio.comSimplifying 3D Creation with AI…...
【rust-grpc-proxy】在k8s中,自动注入代理到pod中,再不必为grpc调试而烦恼
目录前言原理sidecarwebhook实现安装k8s设置webhook使用尾语前言 rust-grpc-proxy 目前功能基本完善。是时候上环境开始应用了。 之前考虑是gateway模式或者sidecar模式。 思考良久之后,觉得两种模式都有使用场景,那就都支持。本次就带来sidecar模式的食…...
VisualStudio2022制作多项目模板及Vsix插件
一、安装工作负载 在vs2022上安装“visual studio扩展开发 ”工作负载 二、制作多项目模板 导出项目模板这个我就不再多说了(项目→导出模板→选择项目模板,选择要导出的项目→填写模板信息→完成)。 1.准备模板文件 将解决方案中的多个…...
仿写简单IOC
目录 TestController类: UserService类: 核心代码SpringIOC: Autowired和Component注解 SpringIOCTest 类 编辑 总结: TestController类: Component public class TestController {Autowiredprivate UserService userService;public void test…...
liunx下安装node exporter
1 建立文件夹 cd /opt mkdir software 下载最新的包,并解压 https://prometheus.io/download/ 下载 curl -LO https://github.com/prometheus/node_exporter/releases/download/v0.18.1/node_exporter-0.18.1.linux-amd64.tar.gz 3.解压 tar -xvf node_exporter-0.…...
lambda函数
Lambda(函数指针)lambda 是c11非常重要也是最常用的特性之一,他有以下优点:可以就地匿名定义目标函数或函数对象,不需要额外写一个函数lambda表达式是一个匿名的内联函数lambda表达式定义了一个匿名函数,语法如下:[cap…...
【Python入门第二十七天】Python 日期
Python 日期 Python 中的日期不是其自身的数据类型,但是我们可以导入名为 datetime 的模块,把日期视作日期对象进行处理。 实例 导入 datetime 模块并显示当前日期: import datetimex datetime.datetime.now() print(x)运行实例 2023-0…...
C++基础知识【5】数组和指针
目录 一、概述 数组 指针 二、数组 2.1、数组的声明 2.2、数组的初始化 2.3、数组的访问 2.4、多维数组 2.5、数组作为函数参数 三、指针 3.1、指针的声明 3.2、指针的赋值 3.3、指针的访问 3.4、指针运算 3.5、指针数组和数组指针 3.6、二级指针 四、数组和指…...
Vim使用操作命令笔记
Vim使用操作命令笔记在普通模式下,输入 : help tutor 就可以进入vim的教学 在 terminal 中输入 vim 文件名 就可以打开文件 vim有两种模式 normal mode (普通模式)→ 指令操作 insert mode (输入模式&…...
【论文阅读】Robust Multi-Instance Learning with Stable Instances
1、摘要与引言 以往的MIL算法遵循i.i.d假设:训练样本与测试样本都分别来自于同一分布中,而这一假设往往与现实应用中有所出入。研究人员通过计算训练样本与测试样本之间的密度比对训练样本进行加权,以解决分布变化带来的问题。 分布的变化发…...
以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...
IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用
在工业制造领域,无损检测(NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统,以非接触式光学麦克风技术为核心,打破传统检测瓶颈,为半导体、航空航天、汽车制造等行业提供了高灵敏…...
MFE(微前端) Module Federation:Webpack.config.js文件中每个属性的含义解释
以Module Federation 插件详为例,Webpack.config.js它可能的配置和含义如下: 前言 Module Federation 的Webpack.config.js核心配置包括: name filename(定义应用标识) remotes(引用远程模块࿰…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现指南针功能
指南针功能是许多位置服务应用的基础功能之一。下面我将详细介绍如何在HarmonyOS 5中使用DevEco Studio实现指南针功能。 1. 开发环境准备 确保已安装DevEco Studio 3.1或更高版本确保项目使用的是HarmonyOS 5.0 SDK在项目的module.json5中配置必要的权限 2. 权限配置 在mo…...
