rrpc:实现熔断与限流

news2024/11/28 0:43:38

一、服务端的自我保护(实现限流)

为什么需要限流器?

  我们先看服务端,举个例子,假如我们要发布一个 Rrpc 服务,作为服务端接收调用端发送过来的请求,这时服务端的某个节点负载压力过高了,我们该如何保护这个节点?

  这个问题还是很好解决的,既然负载压力高,那就不让它再接收太多的请求就好了,等接收和处理的请求数量下来后,这个节点的负载压力自然就下来了。

  在 rrpc 调用中服务端的自我保护策略就是限流,

  限流是一个比较通用的功能,我们可以在 rrpc 框架中集成限流的功能,让使用方自己去配置限流阈值;我们还可以在服务端添加限流逻辑,当调用端发送请求过来时,服务端在执行业务逻辑之前先执行限流逻辑,如果发现访问量过大并且超出了限流的阈值,就让服务端直接抛回给调用端一个限流异常,否则就执行正常的业务逻辑。

我们可以假设这样一个场景:我发布了一个服务,提供给多个应用的调用方去调用,这时有一个应用的调用方发送过来的请求流量要比其它的应用大很多,这时我们就应该对这个应用下的调用端发送过来的请求流量进行限流。所以说我们在做限流的时候要考虑应用级别的维度,甚至是 IP 级别的维度,这样做不仅可以让我们对一个应用下的调用端发送过来的请求流量做限流,还可以对一个 IP 发送过来的请求流量做限流。

服务端实现限流,配置的限流阈值是作用在每个服务节点上的。比如说我配置的阈值是每秒 1000 次请求,那么就是指一台机器每秒处理 1000 次请求;如果我的服务集群拥有 10 个服务节点,那么我提供的服务限流阈值在最理想的情况下就是每秒 10000 次。

我们可以提供一个专门的限流服务,让每个节点都依赖一个限流服务,当请求流量打过来时,服务节点触发限流逻辑,调用这个限流服务来判断是否到达了限流阈值。我们甚至可以将限流逻辑放在调用端调用端在发出请求时先触发限流逻辑,调用限流服务,如果请求量已经到达了限流阈值,请求都不需要发出去,直接返回给动态代理一个限流异常即可。

 

实现限流的方式

方式有很多,比如最简单的计数器,还有可以做到平滑限流的滑动窗口、漏斗算法以及令牌桶算法等等。其中令牌桶算法最为常用我们主要实现的是IP维度通过令牌桶算法的限流。

令牌桶算法的实现步骤

令牌算法是以固定速度rate往一个桶内增加令牌tokens,当桶内令牌满了后(总容量capacity),就停止增加令牌。上游请求时,先从桶里拿一个令牌,后端只服务有令牌的请求,所以后端处理速度不一定是匀速的。当有突发请求过来时,如果令牌桶是满的,则会瞬间消耗桶中存量的令牌。如果令牌还不够,那么再等待发放令牌(固定速度),这样就导致处理请求的速度超过发放令牌的速度。

限流代码 

public class TokenBuketRateLimiter implements RateLimiter{

    //代表令牌的数量 , >0 说明有令牌, 能放行,放行就-1,==0 无令牌
    private int tokens;

    //限流的本质就是 令牌数
    //总容量
    private final int capacity;

    //每秒加500个令牌,不能超过总数容量
    //使用定时任务去加 --> 启动定时任务 每秒执行一次 token+500
    //加令牌的速率
    private final int rate;
    //上一次放令牌的时间
    private Long lastTokenTime = System.currentTimeMillis();

    public TokenBuketRateLimiter(int capacity, int rate) {
        this.capacity = capacity;
        this.rate = rate;
        lastTokenTime = System.currentTimeMillis();
        tokens = capacity;

    }

    /**
     * 多线程同时使用该方法,要保证线程安全synchronized
     * 判断请求是否可以放行
     * @return true 放行
     */
    public synchronized boolean allowRequest(){
        //1.给令牌同添加令牌 计算从现在到上一次的时间间隔需要添加的令牌数
        Long currentTime = System.currentTimeMillis();
        long timeInterval = currentTime - lastTokenTime;
        //距离上一次请求 超过1秒 才会加令牌
        if (timeInterval >= 1000/rate){
            int needAddTokens = (int) (timeInterval * rate / 1000);
            System.out.println("needAddTokens="+ needAddTokens);
            //给令牌桶添加令牌 不能超过容量
            tokens = Math.min(needAddTokens + tokens,capacity);
            System.out.println("tokens="+ tokens);
            //保存最后一次时间
            this.lastTokenTime = System.currentTimeMillis();
        }

        //2.自己获取令牌
        if (tokens > 0){
            tokens --;
            System.out.println("请求被放行------------------------");
            return true;
        }else {
            System.out.println("请求被拦截------------------------");
            return false;
        }

    }

    public static void main(String[] args) {
        TokenBuketRateLimiter rateLimiter = new TokenBuketRateLimiter(10,10);
        for (int i = 0; i < 1000; i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            boolean allowRequest = rateLimiter.allowRequest();
            System.out.println("allowRequest = " + allowRequest);
        }
    }

}

当进行方法调用时令牌桶内没令牌时,就会限流

二、调用端的自我保护(简单熔断器的实现)

为什么需要熔断器?

举个例子,假如我要发布一个服务 B,而服务 B 又依赖服务 C,当一个服务 A 来调用服务 B 时,服务 B 的业务逻辑调用服务 C,而这时服务 C 响应超时了,由于服务 B 依赖服务 C,C 超时直接导致 B 的业务逻辑一直等待,而这个时候服务 A 在频繁地调用服务 B,服务 B 就可能会因为堆积大量的请求而导致服务宕机。

熔断器的机制

熔断器的工作机制主要是关闭、打开和半打开这三个状态之间的切换。
    1. 在正常情况下,熔断器是关闭的。
    2. 当调用端调用下游服务出现异常时,熔断器会收集异常指标信息进行计算,当达到熔断条件时熔断器打开
    这时调用端再发起请求是会直接被熔断器拦截,并快速地执行失败逻辑;
    3. 当熔断器打开一段时间后,会转为半打开状态,这时熔断器允许调用端发送一个请求给服务端,如果这次请
    求能够正常地得到服务端的响应,则将状态置为关闭状态,否则设置为打开。

熔断器代码 

理论上:标准的断路器应有3种状态 open close half_open,我们只选取两种,定义三种需要枚举

public class CircuitBreaker {
      
    //开关 需要保证线程安全
    private volatile boolean isOpen = false;

    //需要收集指标    异常的数量   比例
    //总的请求数
    private AtomicInteger requestCount = new AtomicInteger(0);

    //异常的请求数
    private AtomicInteger errorRequest = new AtomicInteger(0);

    //允许异常的阈值
    private int maxErrorRequest;
    //异常的比例
    private float maxErrorRate;

    public CircuitBreaker(int maxErrorRequest, float maxErrorRate) {
        this.maxErrorRequest = maxErrorRequest;
        this.maxErrorRate = maxErrorRate;
    }

    //断路器的核心方法,判断是否开启
    public boolean isBreak() {
        //优先返回,如果已经打开了直接返回true
        if (isOpen) {
            return true;
        }
        //需要判断数据指标,是否满足当前阈值
        if (errorRequest.get() > maxErrorRequest) {
            this.isOpen = true;
            return true;
        }
        //判断异常率
        if (errorRequest.get() > 0 && requestCount.get() > 0 &&
                errorRequest.get() / (float) requestCount.get() > maxErrorRate) {
            this.isOpen = true;
            return true;
        }

        return false;
    }

    //每次发生请求或者异常 应该进行记录
    public void recordRequest() {
        this.requestCount.getAndIncrement();
    }

    public void recordErrorRequest() {
        this.errorRequest.getAndIncrement();
    }

    /**
     * 重置熔断器
     */
    public void reset() {
        this.isOpen = false;
        this.requestCount.set(0);
        this.errorRequest.set(0);
    }

    public static void main(String[] args) {
        CircuitBreaker circuitBreaker = new CircuitBreaker(3, 0.2f);
        new Thread(()->{
            for (int i = 0; i < 1000; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                circuitBreaker.recordRequest();
                int num = new Random().nextInt(100);
                if (num > 70) {
                    circuitBreaker.recordErrorRequest();
                }

                boolean aBreak = circuitBreaker.isBreak();

                String result = aBreak ? "断路器阻塞了请求" : "断路器放行了请求";
                System.out.println(result);

            }
        }).start();

        new Thread(()->{
            for (;;) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("--------------------------------");
                circuitBreaker.reset();
            }
        }).start();

        try {
            Thread.sleep(10000000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

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

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

相关文章

ARM DIY(五)摄像头调试

前言 今天&#xff0c;就着摄像头的调试&#xff0c;从嵌入式工程师的角度&#xff0c;介绍如何从无到有&#xff0c;一步一步地调出一款设备。 摄像头型号&#xff1a;OV2640 开发步骤 分为 2 个阶段 5 个步骤 阶段一&#xff1a; 设备树、驱动、硬件 阶段二&#xff1a; 应…

多线程使用HashMap,HashMap和HashTable和ConcurrentHashMap区别(面试题常考),硬盘IO,顺便回顾volatile

一、回顾&#x1f49b; 谈谈volatile关键字用法 volatile能够保证内存可见性&#xff0c;会强制从主内存中读取数据&#xff0c;此时如果其他线程修改被volatile修饰的变量&#xff0c;可以第一时间读取到最新的值。 二、&#x1f499; HashMap线程不安全没有锁,HashTable线程…

二分搜索树(Java 实例代码)

目录 二分搜索树 一、概念及其介绍 二、适用说明 三、二分查找法过程图示 四、Java 实例代码 src/runoob/binary/BinarySearch.java 文件代码&#xff1a; 二分搜索树 一、概念及其介绍 二分搜索树&#xff08;英语&#xff1a;Binary Search Tree&#xff09;&#xff…

20.液体加载特效

效果 源码 <!doctype html> <html> <head><meta charset="utf-8"><title>Milk | Liquid Loader Animation</title><link rel="stylesheet" href="style.css"> </head> <body><div cl…

【Flutter】Flutter 使用 Equatable 简化对象比较

【Flutter】Flutter 使用 Equatable 简化对象比较 文章目录 一、前言二、Equatable 简介三、为什么需要 Equatable&#xff1f;四、如何使用 Equatable五、Equatable 的其他特性六、完整的业务代码示例七、总结 一、前言 在 Flutter 开发中&#xff0c;我们经常需要比较对象的…

Python基础以及代码

Python基础以及代码 1.第一个代码如下&#xff1a; # 项目&#xff1a;第一个项目 # 作者&#xff1a;Adair # 开放时间&#xff1a; 2023/8/15 21:52print("Hello,world!!")如图所示&#xff1a; 2.数字的代码如下&#xff1a; # 项目&#xff1a;演示第一个项…

什么是Web组件(Web Components)?它们有哪些主要特点?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ Web 组件&#xff08;Web Components&#xff09;⭐ Web 组件的主要特点1. 自定义元素&#xff08;Custom Elements&#xff09;2. Shadow DOM3. HTML 模板4. 封装性和重用性5. 生态系统6. 跨框架兼容性 ⭐ 写在最后 ⭐ 专栏简介 前端入门…

新能源技术是实现碳达峰碳中和的必然路径

在绿色经济发展的时代背景之下&#xff0c;光伏屋顶瓦顺势而生。集发电、环保功能于一体的光伏屋顶瓦&#xff0c;让每一栋建筑都能成为一座绿色发电站&#xff0c;实现建筑用电自给&#xff0c;有效降低建筑能耗&#xff0c;极大的推动生态建筑和生态城市的发展。 太阳能光伏瓦…

win10/11电脑中病毒后programdata文件夹不显示,其他文件夹不显示问题,文件夹存在不显示问题解决

首先出现这个问题确保是打开了显示隐藏文件夹 发现问题还是没有解决继续往下看 就是上边这个文件夹&#xff0c;我解决之前忘记截图了&#xff0c;这是解决后的图 这个文件夹是系统应用程序数据缓存文件夹&#xff0c;很重要 好多病毒就是优先隐藏此文件夹来监视系统数据&…

【洛谷算法题】B2005-字符三角形【入门1顺序结构】

&#x1f468;‍&#x1f4bb;博客主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专栏 【洛谷算法题】 文章目录 【洛谷算法题】B2005-字符三角形【入门1顺序结构】&#x1f30f;题目描述&#x1f30f;输入格式…

ChatGPT Prompting开发实战(三)

一、关于chaining prompts与CoT的比较 前面谈到的CoT的推理过程&#xff0c;可以比作是一次性就烹调好一顿大餐&#xff0c;那么接下来要说的“chaining prompts”&#xff0c;其背后的理念是分多次来完成这样一项复杂任务&#xff0c;每次只完成其中一步或者一个子任务。核心…

猜拳游戏小程序源码 大转盘积分游戏小程序源码 积分游戏小程序源码

简介&#xff1a; 猜拳游戏大转盘积分游戏小程序前端模板源码&#xff0c;一共五个静态页面&#xff0c;首页、任务列表、大转盘和猜拳等五个页面 图片&#xff1a;

计算数组中各元素的方差 忽略数组中所有的nan值 numpy.nanvar()

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 计算数组中各元素的方差 忽略数组中所有的nan值 numpy.nanvar() [太阳]选择题 请问关于以下代码最后输出结果的是&#xff1f; import numpy as np a np.array([1, np.nan, 3]) print("…

QT day1登录界面设计

要设计如下图片&#xff1a; 代码如下&#xff1a; main.cpp widget.h widget.cpp 运行效果&#xff1a; 2&#xff0c;思维导图

Mac下Docker Desktop安装命令行工具、开启本地远程访问

Mac系统下&#xff0c;为了方便在terminal和idea里使用docker&#xff0c;需要安装docker命令行工具&#xff0c;和开启Docker Desktop本地远程访问。 具体方法是在设置-高级下&#xff0c; 1.将勾选的User调整为System&#xff0c;这样不用手动配置PATH即可使用docker命令 …

Windows部署ftp

Windows部署ftp 小白教程&#xff0c;一看就会&#xff0c;一做就成。 1.安装包&#xff0c;需要的可以滴滴我 2.部署 就修改下面标注的&#xff0c;别的可以不动 如果想要多个用户&#xff0c;复制这些&#xff0c;把名字&#xff0c;密码改改就行 端口 启动 cmd窗口不要关闭…

sv断言积累

基本概念&#xff1a; ​​​​​​​​​​​​​​​​​​​​​https://www.cnblogs.com/lyc-seu/p/12718412.htmlhttps://www.cnblogs.com/lyc-seu/p/12718412.html 波形查看&#xff1a; 断言波形显示_波形中看断言_风起云涌66的博客-CSDN博客文章目录一、为何要在波形…

详解 Vue 3 使用了 Proxy 对象来替代 Vue 2 中的 Object.defineProperty

在 Vue 2 中&#xff0c;响应式系统使用了 Object.defineProperty 来实现属性的劫持和监听。这种方式需要在对象上定义 getter 和 setter&#xff0c;以便在属性被访问或修改时触发相应的操作。 然而&#xff0c;Object.defineProperty 有一些限制和性能问题。它只能劫持对象的…

华为云软件精英实战营——感受软件改变世界,享受Coding乐趣

机器人已经在诸多领域显现出巨大的商业价值&#xff0c;华为云计算致力于以云助端的方式为机器人产业带来全新机会 如果您是开发爱好者&#xff0c;想了解华为云&#xff0c;想和其他自由开发者交流经验&#xff1b; 如果您是学生&#xff0c;想和正在从事软件开发行业的大佬…

5G工业网关赋能救护车远程监控,助力高效救援

智慧医疗是传统医疗业发展进步的必要趋势&#xff0c;医疗设备通过物联网技术的应用实现智能化转型。通过5G工业网关将医疗器械等设备的数据采集再经过专网传输到医疗系统中&#xff0c;实现医疗设备间的数据共享和远程监控&#xff0c;能够帮助医疗行业大大提高服务质量和管理…