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用于存储CircuitBreaker
    Map<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或创建新的CircuitBreaker
        CircuitBreaker 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> {
    @Override
    public void entry(...) throws Throwable {
        // 熔断验证逻辑
        performChecking(...);
        // 放行
        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }

    @Override
    public 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,反之返回 false
    return TimeUtil.currentTimeMillis() >= nextRetryTimestamp;
}

fromOpenToHalfOpen()

/*
尝试将熔断器的状态从打开(OPEN)更改为半开启(HALF_OPEN)。如果状态切换成功,返回 true 表示请求放行;否则返回 false 表示拒绝请求
*/
protected boolean fromOpenToHalfOpen(Context context) {
    if (currentState.compareAndSet(State.OPEN, State.HALF_OPEN)) {
        // 这里使用观察者模式, 通知观察者, 当前熔断器的状态从OPEN变成了HALF_OPEN
        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) {
                // 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 blocked
                    currentState.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 {
    /*
    请求数据统计
    */
    @Override
    public void onRequestComplete(Context context) {
        Entry entry = context.getCurEntry();
        if (entry == null) {
            return;
        }
        Throwable error = entry.getError();
        // 获取当前值
        SimpleErrorCounter counter = stat.currentWindow().value();
        // 如果此次请求报错了,则将 errorCount + 1
        if (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 {
    @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();
        // 判断响应时间是否超过最大允许响应时间,若超过则将慢请求计数加1
        if (rt > maxAllowedRt) {
            counter.slowCount.add(1);
        }
         // 不论请求是否为慢请求,都将总请求计数加1
        counter.totalCount.add(1);
		
        // 根据当前请求的响应时间与设定阈值的关系,调用handleStateChangeWhenThresholdExceeded(rt)方法来执行相应的状态变更操作
        handleStateChangeWhenThresholdExceeded(rt);
    }
}
降级

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

总结

熔断器分类和原理

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

熔断器大体流程

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

状态流转

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

如下图

HALF_OPEN像是一个中间态

在这里插入图片描述

参考资料

通关 Sentinel 流量治理框架 - 编程界的小學生

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机一级word 文字处理理论+实操试题

计算机一级word 文字处理理论实操试题 单选题&#xff1a; 1、在Word编辑状态下&#xff0c;要将另一文档的内容全部添加在当前文档的当前光标处&#xff0c;应选择的操作是依次单击______。 A.“文件”选项卡和“打开”项 B.“文件”选项卡和“新建”项 C.“插入”选项卡…

windows server 下的mysql 8.0.28修改数据库目录

1. 查看当前数据库存储位置 show global variables like %datadir%; 默认是&#xff1a;C:\ProgramData\MySQL\MySQL Server 8.0\Data 2. 修改 C:\ProgramData\MySQL\MySQL Server 8.0\my.ini配置文件。如下&#xff1a; datadirD:/ProgramData/MySQL/MySQL Server 8.0/Dat…

【HyperLips:】数字人——控制嘴唇 项目源码python实现

最近受到商汤“复活”汤晓鸥的视频刺激&#xff0c;大大的amazing&#xff01;没看过的小伙伴可以自行百度&#xff0c;看了不研究一下【数字人】技术&#xff0c;都要跟时代脱轨了&#xff0c;那就以HyperLips为开篇吧。 目录 &#x1f34e;&#x1f34e;1.摘要 &#x1f3…

OgGame——游戏全球发行的全套解决方案

在现今瞬息万变的游戏行业&#xff0c;成功发行一款游戏面临着各方面的难题&#xff0c;例如市场、版号、本土化等等。OgGame以其全球游戏发行的全套解决方案&#xff0c;成为开发者们的首选&#xff0c;为其提供了稳定而全面的支持。 为什么需要游戏全球发行解决方案&#xff…

专访沈劭劼:7千元干出城市NOA,大疆车载如何在「西瓜上雕树林」?

作者 |张祥威 编辑 |德新 在中国乃至全球智驾的供应商中&#xff0c;大疆车载是一家需要被重视的公司&#xff0c;这家公司在「极致性价比」的方向上进展极快。 去年&#xff0c;大疆发布了基于TITDA4 VH的量产方案&#xff0c;在五菱宝骏云朵等车型上进行了量产。一年后&…

C#,数值计算,数据测试用的对称正定矩阵(Symmetric Positive Definite Matrix)的随机生成算法与源代码

C.Hermite 1、对称矩阵 对称矩阵(Symmetric Matrices)是指以主对角线为对称轴,各元素对应相等的矩阵。在线性代数中,对称矩阵是一个方形矩阵,其转置矩阵和自身相等。1855年,埃米特(C.Hermite,1822-1901年)证明了别的数学家发现的一些矩阵类的特征根的特殊性质,如称为埃…

Spark杂谈

文章目录 什么是Spark对比HadoopSpark应用场景Spark数据处理流程什么是RDDSpark架构相关进程入门案例&#xff1a;统计单词数量Spark开启historyServer 什么是Spark Spark是一个用于大规模数据处理的统一计算引擎Spark一个重要的特性就是基于内存计算&#xff0c;从而它的速度…

Jmeter-实战案例(随机上传文件,接口依赖调用)

前置知识 1 两个接口 1-1 readData需要上传文件 参数 // formData类型 sdbh:"" file: "上传一个压缩包"响应 {"code": 1000,"status": "success","message": "操作成功","data":{"n…

es索引操作命令

索引操作 index 创建索引 put 方法创建索引 使用 put 创建索引时必须指明文档id&#xff0c;否则报错 # PUT 创建命令 # test1 索引名称 # type1 类型名称&#xff0c;默认为_doc&#xff0c;已经被废弃 # 1 文档id PUT /test1/type1/1 {"name":"zhangsan&…

第三门课:结构化机器学习项目-机器学习策略

文章目录 1 机器学习策略一1.1 为什么是ML策略&#xff1f;1.2 正交化1.3 单一数字评估指标1.4 满足和优化指标1.5 训练、开发及测试集划分1.6 开发集和测试集的大小1.7 什么时候改变开发、测试集和指标&#xff1f;1.8 为什么是人的表现&#xff1f;1.9 可避免偏差1.10 理解人…

贪心算法(算法竞赛、蓝桥杯)--线段覆盖

1、B站视频链接&#xff1a;A29 贪心算法 P1803 线段覆盖_哔哩哔哩_bilibili 题目链接&#xff1a;凌乱的yyy / 线段覆盖 - 洛谷 #include <bits/stdc.h> using namespace std;struct line{int l,r;bool operator<(line &b){return r<b.r;//重载小于号,按右端…

FreeRTOS的列表和列表项

这个章节的内容是非常重要的&#xff0c;因为 FreeRTOS 的源码实现离不开列表&#xff0c;所以说大家如果想要看懂 FreeRTOS 的源码&#xff0c;看它是如何实现的&#xff0c;那么这个列表你必须要掌握。 1. 列表和列表项 1.1 列表和列表项的简介 列表 是 FreeRTOS 中的一个…

【递归专题】【蓝桥杯备考训练】:有序分数、正则问题、带分数、约数之和、分形之城【已更新完成】

目录 1、有序分数&#xff08;usaco training 2.1&#xff09; 2、正则问题&#xff08;第八届蓝桥杯省赛C A组 & Java A组&#xff09; 3、带分数&#xff08;第四届蓝桥杯省赛Java A组/B组 & C B组/C组&#xff09; 4、约数之和&#xff08;《算法竞赛进阶指南》…

面试笔记——Redis(使用场景、面临问题、缓存穿透)

Redis的使用场景 Redis&#xff08;Remote Dictionary Server&#xff09;是一个内存数据结构存储系统&#xff0c;它以快速、高效的特性闻名&#xff0c;并且它支持多种数据结构&#xff0c;包括字符串、哈希表、列表、集合、有序集合等。它主要用于以下场景&#xff1a; 缓…

虹科技术|PCAN系列网关内部存储空间解析:EEPROM与Flash的集成应用

导读&#xff1a;网关设备是确保数据流畅通信的关键。虹科PCAN系列网关凭借卓越性能和创新技术&#xff0c;为众多应用提供了高效稳定的解决方案。本文将深入探讨虹科PCAN系列网关内部存储空间&#xff0c;特别是EEPROM和SPI Flash的配置与利用&#xff0c;并解析如何通过C编程…

每日一题——LeetCode1694.重新格式化电话号码

方法一 模拟&#xff1a; 首先去除number里面的破折号和空格&#xff0c;取出纯数字组成的字符串str。 对于str每三个数分成一组&#xff0c;加一个破折号&#xff0c;当str的长度小于等于4时再分情况讨论&#xff0c;如果等于4就分为22形式&#xff0c;如果小于4&#xff0c…

flask之ssti [WesternCTF2018]shrine1

打开题目 整理一下&#xff0c;代码: import flask import osapp flask.Flask(__name__) app.config[FLAG] os.environ.pop(FLAG) app.route(/)def index():return open(__file__).read()app.route(/shrine/)def shrine(shrine):def safe_jinja(s):s s.replace((, ).replac…

基于springboot+vue的乡政府管理系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

奇数乘积(C语言)

一、运行结果&#xff1b; 二、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>int main() {//初始化变量值&#xff1b;int i 1;int j 3;//循环运算&#xff1b;while (j < 12){//运算&#xff1b;i i * j;//改变数值&#xff1b;j 2…

多线程服务器适用场合

前提 进程”指的是fork(2)系统调用的产物 线程”指的是pthread_create()的产物,因此是宝贵的那种原生线程。而且Pthreads是NPTL的,每个线程由clone(2)产生,对应一个内核的task_struct。 Pthreads是一组线程操作的标准&#xff0c;NPTL是 Native POSIX Thread Library 的缩写&…