【深入理解SpringCloud微服务】手写实现断路器算法

news2024/11/30 6:42:00

【深入理解SpringCloud微服务】手写实现断路器算法

  • 断路器状态切换
  • 断路器接口
  • 断路器算法实现
    • 相关属性
    • failed()
    • success()
    • canPass()

断路器状态切换

在分析断路器算法前,我们先复习一下断路器的状态转换。

在这里插入图片描述

断路器一般有三个状态:关闭、打开、半开。

  1. 断路器一开始处于关闭状态,此时请求能正常通过;
  2. 断路器会记录调用失败数,当失败数(或失败率)达到一定阈值,断路器就会变成打开状态,此时请求将不会被正常处理;
  3. 当断路器处于打开状态经过一段时间后,会切换为半开状态,此时断路器会允许一个请求通过,如果请求处理成功,则断路器切换为关闭状态,否则还是切换为打开状态。

断路器接口

/**
 * 断路器
 * @author huangjunyi
 * @date 2023/12/27 15:40
 * @desc
 */
public interface Breaker {

    /**
     * 记录成功调用
     */
    void success();

    /**
     * 记录失败调用
     */
    void failed();

    /**
     * 判断是否可通行
     */
    boolean canPass();

}

在这里插入图片描述

Breaker接口有success()、failed()、canPass()三个方法,其中canPass()就是根据断路器状态判断请求能否正常通过。

断路器算法实现

相关属性

我们给Breaker接口提供默认实现类BasicBreaker。

/**
 * @author huangjunyi
 * @date 2023/12/27 19:11
 * @desc
 */
public class BasicBreaker implements Breaker {

    // 时间窗长度(单位秒)
    private int timeSpan;

    // 时间窗内最大允许错误数量
    private int maxFailedNum;

    // 断路器状态,闭合状态为true,表示请求可以通过,开路状态为false,请求不可通过
    private boolean close;

    // 断路器最后一次打开的时间点,闭合状态为-1
    private long lastOpenTime;

    // 断路器开路状态时间(断路器处于开路状态超过该时间,就会转为半开状态)
    private long openTime;

    // 时间跨度内错误数量统计,一个AtomicInteger对应一个时间窗格,一个时间窗格的跨度为1s
    private AtomicInteger[] failedCounter;

	// 时间窗数组
	// 一个数组元素表示一个时间窗格
	// 每个窗格记录的是以秒为单位的时间戳,用于判断时间窗是否过期
    private long[] times;

	...

}

我们的BasicBreaker实现的是滑动时间窗统计失败数的断路器。这里的滑动时间窗算法与上一篇文章《手写实现各种限流算法——固定时间窗、滑动时间窗、令牌桶算法、漏桶算法》中实现的滑动时间窗算法是基本一样的。

但是这里的时间窗是1秒一个时间窗格,而时间窗长度是需要用户指定的timeSpan(单位秒)。AtomicInteger[] failedCounter则是记录失败数的计数器数组,每个时间窗格对应一个计算器。

比如我们设置了timeSpan=5,表示时间窗长度为5,于是时间窗数组times和计数器数组failedCounter长度为5,那么就是下面那样:

在这里插入图片描述

当失败数达到阈值maxFailedNum是,断路器切换为打开,也就是close由true变为false,此时请求将不再正常通过。

failed()

failed()方法的作用是当接口处理失败或超时时记录失败数。

    @Override
    public void failed() {
        long currentTimeMillis = System.currentTimeMillis();
        // 当前这一秒,是从1970年1月1日零点到现在的第几秒
        long currentTimeSeconds = currentTimeMillis / 1000L;
        // 计算时间窗格下标
        int timeIndex = (int) (currentTimeSeconds % times.length);
        // 判断时间窗格对应的计数器是否为空,
        // 如果不为空再判断时间窗格的秒值与currentTimeSeconds是否相等
        // 如果不相等,表示该时间窗格已经过期
        if (failedCounter[timeIndex] == null || times[timeIndex] != currentTimeSeconds) {
        	// 时间窗格对应的计数器为空或已过期,重置计数器和窗格的秒值
            failedCounter[timeIndex] = new AtomicInteger(1);
            times[timeIndex] = currentTimeSeconds;
        } else {
        	// 时间窗格不为空且没过期,增加失败数
            failedCounter[timeIndex].incrementAndGet();
        }

        // 统计失败数,是否要修改为开路
        if (sum(currentTimeSeconds) > maxFailedNum) {
        	lastOpenTime = currentTimeMillis;
            close = false;
        }
    }

由于我们设计的时间窗是1秒一个窗格,那么只要用System.currentTimeMillis()除以1000,再模上时间窗格数组的长度times.length,就能得出目标时间窗格下标timeIndex。

然后判断时间窗格对应的计数器是否为空,或者时间窗格是否已过期。如果是,那么重置计数器和时间窗格;否则增加时间窗格对应的计数器的计数。

这里怎么判断一个时间窗格是否已过期呢?由于我们已经得到了了从1970年1月1日零点到当前的秒值currentTimeSeconds,而时间窗格times[timeIndex]记录的是从1970年1月1日零点到它当时被重置时的秒值,因此两者比较一下是否相等,就能得知时间窗格是否过期。

比如当前时间是2024-04-05 20:09:20,时间戳是1712318960000,假如时间窗数组是这样:

在这里插入图片描述
那么currentTimeSeconds = 1712318960000 / 1000 = 1712318960,然后timeIndex = 1712318960 % 5 = 0,那么得到的时间窗格数组下标为0,然后判断times[timeIndex]又等于1712318960,那么得知该时间窗格没有过期。

在这里插入图片描述

假如时间往后走了5秒,此时时间是2024-04-05 20:09:25,时间戳是1712318965000,那么currentTimeSeconds = 1712318965000 / 1000 = 1712318965,然后timeIndex = 1712318965 % 5 = 0,得到的时间窗格数组下标又是0,然后times[timeIndex]是1712318960,不等于1712318965,那么得知时间窗格已过期。

在这里插入图片描述

最后通过sum(currentTimeSeconds)得出当前时间窗失败数的统计值,如果超过断路器阈值,则切换断路器状态为打开状态。

在这里插入图片描述

再看下sum()方法如何做统计。

    private int sum(long currentTimeSeconds) {
        int sum = 0;
        for (int i = 0; i < times.length; i++) {
        	// timeSpan是用户指定的时间窗长度
        	// 如果currentTimeSeconds - times[i]大于等于timeSpan,表示该时间窗格已过期
            if (currentTimeSeconds - times[i] >= timeSpan) {
                // 已不在当前时间窗内的窗格,忽略
                continue;
            }
            if (failedCounter[i] != null) {
            	// 计数器的值累加到sum
                sum += failedCounter[i].get();
            }
        }
        return sum;
    }

在这里插入图片描述

success()

    @Override
    public void success() {
        // 请求成功,清空所有的错误记录
        failedCounter = new AtomicInteger[timeSpan];
        // 修改断路器为闭合状态
        close = true;
    }

我们设计的断路器,一旦请求处理成功,那么清空所有的错误记录,然后修改断路器为闭合状态。

在这里插入图片描述

canPass()

    @Override
    public boolean canPass() {
    	// 闭合状态,放行
        if (close) {
            return true;
        }
        long currentTimeMillis = System.currentTimeMillis();
        // 计算断路器打开时间是否已超过指定打开时间openTime
        if (lastOpenTime != -1 && currentTimeMillis - lastOpenTime > openTime) {
        	// 断路器打开时间已超过指定时间,此时处于半开状态,放行一个请求
        	// 更新lastOpenTime断路器最后一次打开时间
            lastOpenTime = currentTimeMillis;
            return true;
        }
        // 断路器处于打开状态,拒绝处理请求
        return false;
    }

我们的断路器用一个boolean类型的close变量记录状态,true是关闭状态,false是打开状态。半开状态是根据时间计算的,当前时间currentTimeMillis减去上一次打开的时间lastOpenTime如果大于等于openTime,表示断路器转为半开状态。

在这里插入图片描述

代码已经提交到gitee,可以自行下载阅读。
https://gitee.com/huang_junyi/simple-microservice/tree/master/simple-microservice-protector/src/main/java/com/huangjunyi1993/simple/microservice/protector/breaker
在这里插入图片描述

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

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

相关文章

【瑞昱RTL8763E】歌曲传输

1 概要 Watch 端 SD 卡中的歌曲除了可以通过 USB 传输&#xff0c;还可以通过 SPP/BLE 传输来完成歌曲的添加与删 除操作。其中&#xff0c;Android 手机可以安装 LocalPlayback.apk 使用 SPP 协议与 watch 交互&#xff1b;iOS 手机可以安装 LocalPlayback.ipa 通过 BLE 与 wa…

Python 工具库每日推荐 【Matplotlib】

文章目录 引言Python数据可视化库的重要性今日推荐:Matplotlib工具库主要功能:使用场景:安装与配置快速上手示例代码代码解释实际应用案例案例:数据分析可视化案例分析高级特性自定义样式动画效果3D绘图性能优化技巧扩展阅读与资源优缺点分析优点:缺点:总结【 已更新完 T…

【可答疑】基于51单片机的无线病床呼叫系统(含仿真、代码、报告、演示视频等)

✨哈喽大家好&#xff0c;这里是每天一杯冰美式oh&#xff0c;985电子本硕&#xff0c;大厂嵌入式在职0.3年&#xff0c;业余时间做做单片机小项目&#xff0c;有需要也可以提供就业指导&#xff08;免费&#xff09;~ &#x1f431;‍&#x1f409;这是51单片机毕业设计100篇…

什么是虚拟化?| 裸机 vs 虚拟机 vs 容器

“云计算&#xff01;DevOps&#xff01;Docker&#xff01;Kubernetes&#xff01;……” 如果您是一名软件工程师&#xff0c;还没有遇到过以上这些流行词&#xff0c;那么您可能一直生活在与世隔绝的地方。 所有这些技术都与同一样东西有关&#xff0c;对&#xff0c;就是…

openEuler 24.03 (LTS) 部署 K8s(v1.31.1) 高可用集群(Kubespray Ansible 方式)

写在前面 实验需要一个 CNI 为 flannel 的 K8s 集群之前有一个 calico 的版本有些旧了,所以国庆部署了一个v1.31.1 版本 3 * master 5 * work时间关系直接用的工具 kubespray博文内容为部署过程以及一些躺坑分享需要科学上网理解不足小伙伴帮忙指正 &#x1f603;,生活加油 99…

IEC104规约的秘密之七----配置参数t1,t2,t3

104通讯前需要配置通讯参数&#xff0c;一般有如下参数&#xff1a; IP地址&#xff0c;端口号&#xff0c;k&#xff0c;w&#xff0c;t1&#xff0c;t2&#xff0c;t3&#xff0c;公共地址&#xff0c;遥控超时参数&#xff0c;104主规约还有一个t0参数。 本次只讲解t1&#…

2-113 基于matlab的图像的配准融合

基于matlab的图像的配准融合&#xff0c;采用互信息配准&#xff0c;PV差值&#xff0c;powell算法&#xff0c;小波变换的图像融合算法。在GUI界面输入两幅图像&#xff0c;完成图像的配准融合。融合图像要求像素 一样。程序代码已经有详细的注释。程序已调通&#xff0c;可直…

对操作系统中的用户态和内核态的理解

目录 引言 为什么要有用户态和内核态&#xff1f;只有一个内核态不行么&#xff1f; 一、用户态&#xff08;User Mode&#xff09; 定义 特点 应用 二、内核态&#xff08;Kernel Mode&#xff09; 定义 特点 应用 三、用户态与内核态的联系和区别 四、用户态和内…

通过dem2terrain生成MapboxGL地形服务

概述 MapboxGL在2的版本之后通过地形服务开始支持三维的展示了&#xff0c;之前也有文章“mapboxGL2中Terrain的离线化应用”对该服务进行过说明与分析。前些天在翻公众号的时候翻到了dem2terrain可以生成地形服务&#xff0c;同时做了一些优化&#xff0c;今天就给大家分享一…

2024全面升级!从零开始的大模型开发学习路线图——精通之路

第一阶段&#xff1a;基础理论入门 目标&#xff1a;了解大模型的基本概念和背景。 内容&#xff1a; 人工智能演进与大模型兴起。 大模型定义及通用人工智能定义。 GPT模型的发展历程。 第二阶段&#xff1a;核心技术解析 目标&#xff1a;深入学习大模型的关键技术和工…

多文件并发多线程MD5工具(相对快速的MD5一批文件),适配自定义MD5 Hash I/O缓存。

自己写的多文件 MD5校验工具&#xff0c;一个文件开一个线程&#xff0c;有最大I/O 缓存设置&#xff0c;兼容读写MD5后缀文件。 共计91个文件&#xff0c;合计180G左右 12分钟左右&#xff0c;UI基本卡废&#xff0c;但程序没蹦&#xff0c;属于正常。 卡的原因是基本是用 I/O…

每日OJ题_牛客_牛牛冲钻五_模拟_C++_Java

目录 牛客_牛牛冲钻五_模拟 题目解析 C代码 Java代码 牛客_牛牛冲钻五_模拟 牛牛冲钻五 (nowcoder.com) 描述&#xff1a; 牛牛最近在玩炉石传说&#xff0c;这是一款一对一对战的卡牌游戏&#xff0c;牛牛打算努力冲上钻五分段&#xff0c;获得丰厚的天梯奖励。…

力扣 中等 78.子集

文章目录 题目介绍解法解法一&#xff1a;解法二&#xff1a; 题目介绍 解法 有两种解法&#xff0c;对于计算[1,2]的子集问题&#xff1a; 解法一&#xff1a; 站在输入的角度思考&#xff1a;每个元素都可以选/不选 代码如下&#xff1a; class Solution {List<List&…

ReGCL Rethinking Message Passingin Graph Contrastive Learning

AAAI24 推荐指数&#xff1a; #paper/⭐ 总体说&#xff1a;利用梯度对对比正负样本加权的。个人觉得和与正负样本加权没有区别&#xff0c;读完之后不想做笔记了。

成都睿明智科技有限公司真实可靠吗?

在这个日新月异的电商时代&#xff0c;抖音作为短视频与直播电商的佼佼者&#xff0c;正以前所未有的速度重塑着消费者的购物习惯。而在这片充满机遇与挑战的蓝海中&#xff0c;成都睿明智科技有限公司以其独到的眼光和专业的服务&#xff0c;成为了众多商家信赖的合作伙伴。今…

RAG再总结之如何使大模型更好使用外部数据:四个不同层级及查询-文档对齐策略

我们来看看RAG进展。《Retrieval Augmented Generation (RAG) and Beyond: A Comprehensive Survey on How to Make your LLMs use External Data More Wisely》(https://arxiv.org/abs/2409.14924)&#xff0c;主要讨论了如何使大型语言模型&#xff08;LLMs&#xff09;更明智…

【Canvas与标志】白座红芯辐射标志

【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>白座红芯辐射标志</title><style type"text/css"…

23.1 k8s监控中标签relabel的应用和原理

本节重点介绍 : relabel的源码在 7.7节做过详细的解读强大的relabel能力 在k8s中的应用 应用1&#xff1a; labelmap 在采集cadvisor指标时 对服务发现标签key名字截取应用2&#xff1a; 采集pod自定义指标中replace 和 keep的应用应用3&#xff1a; k8s服务组件采集时的endpo…

资产管理系统建设方案,资产盘点,rfid,出入库,消耗品管理,系统方案,系统源码(word原件)

固定资产管理系统需求要点&#xff1a; 1. 实现公司内部固定资产管理全生命周期管理&#xff0c;包括资产采购、资产入库、资产领用、资产借用、资产归还、资产报废、资产维修、资产调拨等全过程管理。 2. 可实现集团内部固定资产盘点管理&#xff0c;包括盘点计划、盘点查询等…

【深度解析】从电视广播到互联网接入:通信卫星如何改变我们的世界?

1.通信卫星的发展历程和现状 1.1 早期发展 通信卫星的发展历程可以追溯到20世纪50年代末期和60年代初期。 1957年10月4日&#xff0c;苏联成功发射了第一颗人造卫星“斯普特尼克1号”&#xff0c;标志着人类进入了太空时代&#xff0c;也推动了通信卫星的发展。 1958年12月18…