手写RPC-令牌桶限流算法实现,以及常见限流算法

news2024/11/15 3:32:30

为什么需要服务限流、降级

        分布式架构下,不同服务之间频繁调用,对于某个具体的服务而言,可能会面临高并发场景。在这样的情况下,提供服务的每个服务节点就都可能由于访问量过大而引起一系列问题,比如业务处理耗时过长、CPU飘高、频繁Full GC以及服务进程直接宕机等。但是在生产环境中,要保证服务的稳定性和高可用性,这时就需要业务进行自我保护,从而保证在高访问量、高并发的场 景下,应用系统依然稳定,服务依然高可用。 我们再次借助 RPC 框架来分析,RPC 调用包括服务端和调用端。对于服务端来讲一般实现限流、降级算法;对于调用方来说一般实现熔断算法。

        加入服务调用方调用某个多服务节点的其中一个实现如下:

        此时对于特定的服务节点来说,可能存在多个不同调用者的调用,从而使得节点接收服务量超过其最大承载力。此时如果不采取某种策略进行调整,对应的具体服务可能由于过多调用而崩溃,无法对于提供服务能力。限流算法可以很好解决以上问题:当调用端发送请求过来时,服务端在执行业务逻辑之前先执行限流逻辑,如果发现访问量过大并 且超出了限流的阈值,就让服务端直接抛回给调用端一个限流异常,否则就执行正常的业务逻辑。

常见限流算法实现有哪些

        常见限流算法实现固定窗口计数器法、滑动窗口计数器法、漏桶算法、令牌桶算法。

令牌桶算法

        令牌桶是一个限流容器,容器有最大容量(最大处理能力),每秒或每100ms产生一个令牌(具体取决于业务实现以及机器的最大处理能力),当容器中令牌数量达到最大容量时,令牌数量此时不会增加,只有当请求过来时,才会使得令牌数量减少(只有获取到令牌的请求才会执行业务逻辑),才会不断的以一定的速率生成令牌。

         令牌桶限流范围:

        假设令牌桶最大容量为n ,每秒产生 r 个令牌
        平均速率:则随着时间推延,处理请求的平均速率越来越趋近于每秒处理r 个请求,说明令牌桶算法可以控制平均速率
        瞬时速率:如果在一瞬间有很多请求进来,此时来不及产生令牌,则在一瞬间最多只有n 个请求能获取到令牌执行业务逻辑,所以令牌桶算
法也可以控制瞬时速率
        在这里提下漏桶,漏桶由于出水量固定,所以无法应对突然的流量爆发访问,也就是没有保证瞬时速率的功能,但是可以保证平均速率

漏桶算法

        漏桶算法强调把服务调用比作水滴,当有调用(任务)发生时,将其加入桶中,随后采用某种机制(如:定时器)以一定的速率从桶中取出任务进行处理(相当于桶以一定的速率不断在漏水,也就是【漏桶】算法)

        漏桶算法的实现可以借助于Redis实现的消息队列和定时任务。

优点:

  • 实现简单、易于理解。
  • 可以控制限流速率,避免网络拥塞和系统过载。

缺点:

  • 无法应对突然激增的流量,因为只能以固定的速率处理请求,对系统资源利用不够友好。
  • 桶流入水(发请求)的速率如果一直大于桶流出水(处理请求)的速率的话,那么桶会一直是满的,一部分新的请求会被丢弃,导致服务质量下降。

        在实际业务场景中一般不适用漏桶算法,基于Redis实现的消息队列,其容量非常大,可以使用,其可以理解为容量无线的桶,在一定程度下不会发生消息丢失。

固定窗口计数器法

固定窗口其实就是时间窗口,其原理是将时间划分为固定大小的窗口,在每个窗口内限制请求的数量或速率,即固定窗口计数器算法规定了系统单位时间处理的请求数量。

假如我们规定系统中某个接口 1 分钟只能被访问 33 次的话,使用固定窗口计数器算法的实现思路如下:

  • 将时间划分固定大小窗口,这里是 1 分钟一个窗口。
  • 给定一个变量 counter 来记录当前接口处理的请求数量,初始值为 0(代表接口当前 1 分钟内还未处理请求)。
  • 1 分钟之内每处理一个请求之后就将 counter+1 ,当 counter=33 之后(也就是说在这 1 分钟内接口已经被访问 33 次的话),后续的请求就会被全部拒绝。
  • 等到 1 分钟结束后,将 counter 重置 0,重新开始计数。

优点:实现简单,易于理解。

缺点:

  • 限流不够平滑。例如,我们限制某个接口每分钟只能访问 30 次,假设前 30 秒就有 30 个请求到达的话,那后续 30 秒将无法处理请求,这是不可取的,用户体验极差!
  • 无法保证限流速率,因而无法应对突然激增的流量。

        对于固定窗口计数器算法而言,存在非常严重的一个问题就是临界问题: 假设1min内服务器的负载能力为100,因此一个周期的访问量限制在100,然后在第一个周期的最后5s和下一个周期的开始5s时间段内,分别涌入了100的访问量,虽然没有超过每个周期的限制量,但是整体上10s内已经达到了200的访问量,远超服务器的承载能力。

滑动窗口计数器法

滑动窗口计数器算法 算的上是固定窗口计数器算法的升级版,限流的颗粒度更小。

滑动窗口计数器算法相比于固定窗口计数器算法的优化在于:它把时间以一定比例分片

例如我们的接口限流每分钟处理 60 个请求,我们可以把 1 分钟分为 60 个窗口。每隔 1 秒移动一次,每个窗口一秒只能处理不大于 60(请求数)/60(窗口数) 的请求, 如果当前窗口的请求计数总和超过了限制的数量的话就不再处理其他请求。

        如下图,假设时间周期为1min,将1min再分为2个小周期,统计每个小周期的访问数量,则可以看到,第一个时间周期内,访问数量为75,第二个时间周期内,访问数量为100,超过100的访问则被限流掉了 

        很显然, 当滑动窗口的格子划分的越多,滑动窗口的滚动就越平滑,限流的统计就会越精确。

优点:

  • 相比于固定窗口算法,滑动窗口计数器算法可以应对突然激增的流量。
  • 相比于固定窗口算法,滑动窗口计数器算法的颗粒度更小,可以提供更精确的限流控制。

缺点:

  • 与固定窗口计数器算法类似,滑动窗口计数器算法依然存在限流不够平滑的问题。
  • 相比较于固定窗口计数器算法,滑动窗口计数器算法实现和理解起来更复杂一些。

四种限流算法的比较 

令牌桶限流算法在RPC服务中的应用

        为了实现令牌桶算法,我们并不需要严格按照算法的定义多长时间产生一个令牌,我们只需要当桶中无令牌时,根据(当前系统时间 - 上次令牌生成时间之间的差值 )x 令牌生成速率,便可以得到这段时间应该生成的令牌总数,随后取其和容量CAPACITY的最小值即可。

        代码实现:

package main.version4.v1.server.rateLimit.impl;

import lombok.extern.slf4j.Slf4j;
import main.version4.v1.server.rateLimit.RateLimit;

/**
 * 令牌桶限流算法实现
 * CAPACITY为令牌桶的最大容量,curCAPACITY为当前令牌桶中令牌的数量,timeStamp为上一次请求获取令牌的时间,
 * 我们在这里并没有实现计数器每秒产生多少令牌放入容器中,而是记住了上一次请求到来的时间,和这次请求之间的时间差值
 * 进一步根据RATE计算出这段时间能够产生的令牌数量,取min(CAPACITY, CURCAPAITY)。
 */
@Slf4j
public class TokenBucketRateLimitImpl implements RateLimit {
    // 令牌产生速率(单位ms)
    private static int RATE;
    // 桶容量
    private static int CAPACITY;
    // 当前桶容量
    private volatile static int curCapacity;
    // 时间戳
    private volatile long timeStamp = System.currentTimeMillis();
    public TokenBucketRateLimitImpl(int rate, int capacity){
        RATE = rate;
        CAPACITY = capacity;
        curCapacity = CAPACITY;
    }

    @Override
    public synchronized boolean getToken() {
        // 如果当前桶有剩余,直接返回
        if(curCapacity > 0){
            curCapacity--;
            return true;
        }
        // 如果桶无剩余
        long currentTime = System.currentTimeMillis();
        // 如果距离上一次的请求的时间大于RATE的时间
        if(currentTime - timeStamp >= RATE){
            //计算这段时间间隔中生成的令牌,如果>2,桶容量加上(计算的令牌-1)
            //如果==1,就不做操作(因为这一次操作要消耗一个令牌)
            if((currentTime - timeStamp) / RATE >= 2){
                curCapacity += (int) (currentTime - timeStamp) / RATE - 1;
            }
            //保持桶内令牌容量<=CAPACITY
            if(curCapacity > CAPACITY){
                curCapacity = CAPACITY;
            }
            //刷新时间戳为本次请求
            timeStamp = currentTime;
            return true;
        }
        return false;
    }
}

参考资料

 服务限流详解 | JavaGuide

常用4种限流算法介绍及比较-CSDN博客

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

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

相关文章

数据结构中的八大金刚--------八大排序算法

目录 引言 一&#xff1a;InsertSort(直接插入排序) 二&#xff1a;ShellSort(希尔排序) 三&#xff1a;BubbleSort(冒泡排序) 四&#xff1a; HeapSort(堆排序) 五&#xff1a;SelectSort(直接选择排序) 六&#xff1a;QuickSort(快速排序) 1.Hoare版本 2.前后指针版本 …

数组Arrays,排序算法,String类,Stringbulider,正则表达式

## 数组 排序 经典的三大排序&#xff1a;冒泡&#xff0c;选择&#xff0c;插入 &#xff08;一&#xff09;冒泡排序核心&#xff1a;数组中的 相邻 两项比较&#xff0c;交换&#xff08;正序or倒序&#xff09; 正序原理图&#xff1a; 代码实现&#xff1a; public s…

jmeter 重试机制

一、功能实现 我们在测试过程中&#xff0c;请求接口可能是因为请求超时&#xff0c;或者接口异常失败&#xff0c;导致整个测试链路验证失败&#xff0c;jmeter重试机制&#xff0c;这个时候就可以避免上述问题发生 二、配置 1、添加线程组 首先&#xff0c;确保你已经在测…

Python | Leetcode Python题解之第278题第一个错误的版本

题目&#xff1a; 题解&#xff1a; # The isBadVersion API is already defined for you. # def isBadVersion(version: int) -> bool:class Solution:def firstBadVersion(self, n: int) -> int:left, right 1, nwhile left < right:mid left (right - left) //…

power bi条件判断函数

power bi条件判断函数 1. iferror函数2. if 函数3. switch函数4. hasonefilter函数5. hasonevalue函数6. selectedvalue函数 1. iferror函数 遇到错误时使用指定数值替换注意&#xff1a;替换的必须是数值例子列 [销售数量]*[单价] 列 iferror([销售数量]*[单价],0) 列 ife…

昇思25天学习打卡营第17天|计算机视觉

昇思25天学习打卡营第17天 文章目录 昇思25天学习打卡营第17天ShuffleNet图像分类ShuffleNet网络介绍模型架构Pointwise Group ConvolutionChannel ShuffleShuffleNet模块构建ShuffleNet网络 模型训练和评估训练集准备与加载模型训练模型评估模型预测 打卡记录 ShuffleNet图像分…

自学Java第11Day

学习目标&#xff1a;面向对象进阶 学习内容&#xff1a;包、final、权限修饰符、代码块、抽象类、接口 学习时间&#xff1a; 下午 3 点-下午 6 点 学习产出&#xff1a; 什么的包&#xff1f; 包就是文件夹。用来管理各种不同功能的Java类&#xff0c;方便后期代码维护。 包…

【Go系列】Go的UI框架Fyne

前言 总有人说Go语言是一门后端编程语言。 Go虽然能够很好地处理后端开发&#xff0c;但是者不代表它没有UI库&#xff0c;不能做GUI&#xff0c;我们一起来看看Go怎么来画UI吧。 正文 Go语言由于其简洁的语法、高效的性能和跨平台的编译能力&#xff0c;非常适合用于开发GUI…

爬虫学习1:初学者简单了解爬虫的基本认识和操作(详细参考图片)

爬虫 定义&#xff1a;爬虫&#xff08;Web Crawler 或 Spider&#xff09;是一种自动访问互联网上网页的程序&#xff0c;其主要目的是索引网页内容&#xff0c;以便搜索引擎能够快速检索到相关信息。以下是爬虫的一些关键特性和功能&#xff1a; 自动化访问&#xff1a;爬虫能…

58、主从复制数据库+读写分离

mysql的主从复制和读写分离&#xff08;面试问原理&#xff09; mysql的主从复制和读写分离&#xff1a; 主从复制 面试必问 主从复制的原理。 读写分离&#xff0c;MHA 一、主从复制 1.1、主从复制的模式&#xff1a; 1、mysql的默认模式&#xff1a; 异步模式&#xf…

centos系统mysql数据库差异备份与恢复

文章目录 差异备份mysql数据一、 安装 Percona XtraBackup数据库中创建一些数据三、创建全备份四、创建差异备份1. 在数据库中添加数据&#xff0c;让数据发生一些改变2. 创建第一个差异备份3. 数据库中再次添加一些数据4. 创建第二个差异备份 五、模拟数据丢失&#xff0c;删库…

MongoDB 学习笔记

一、简介 1、MongoDB 是什么 MongoDB 是一个基于分布式文件存储的数据库&#xff0c;官方地址 https://www.mongodb.com/ 2、数据看是什么 数据库&#xff08;DataBase&#xff09;是按照数据结构来组织、存储和管理数据的应用程序。 3、数据库的作用 主要作用是 管理数据…

Data Analytics for Business BISM7233

SSIS Task: Company_data.csv contains information for each of the companies, some of the state code information is missing in this table. You would need to use “state_code.csv” to fill in these blank cells under state code before creating the company dime…

利用OSMnx求路网最短路径并可视化(二)

书接上回&#xff0c;为了增加多路径的可视化效果和坐标匹配最近点来实现最短路可视化&#xff0c;我们使用图形化工具matplotlib结合OSMnx的绘图功能来展示整个路网图&#xff0c;并特别高亮显示计算出的最短路径。 多起终点最短路路径并计算距离和时间 完整代码#运行环境 P…

vite环境下使用bootstrap

环境 nodejs 18 pnpm 初始化 pnpm init pnpm add -D vite --registry http://registry.npm.taobao.org pnpm add bootstrap popperjs/core --registry http://registry.npm.taobao.org pnpm add -D sass --registry http://registry.npm.taobao.org新建vite.config.js cons…

【微服务】Spring Cloud Bus的注意事项和常用案例

文章目录 强烈推荐引言关键方面注意事项1. 消息代理选择2. 消息队列配置3. 消息持久化4. 幂等性5. 安全性6. 消息大小7. 性能监控8. 错误处理9. 版本兼容性10. 测试11. 配置同步12. 日志记录 常用示例示例 1: 配置同步配置服务器 (config-server)客户端服务 (client-service)触…

数据库的事务隔离级别有哪些?

并行事务会引发什么问题&#xff1f; 同时处理多个事务的时候&#xff0c;就可能出现脏读&#xff08;dirty read&#xff09;、不可重复读&#xff08;non-repeatable read&#xff09;、幻读&#xff08;phantom read&#xff09;的问题。脏读: 如果一个事务「读到」了另一个…

MQ消息队列+Lua 脚本实现异步处理下单流程,将同步下单改为异步下单

回顾一下下单流程&#xff1a; 用户发起请求 会先请求Nginx,Nginx反向代理到Tomcat&#xff0c;而Tomcat中的程序&#xff0c;会进行串行工作&#xff0c; 分为以下几个操作&#xff1a; 1 查询优惠券 2 判断秒杀库存是否足够 3 查询订单 4 校验是否是一人一单 5 扣减库…

Unity Shader - 2024 工具篇

目录 IDE 工具建议 IDE工具 Sublime 3 大势所趋&#xff0c;但是Sublime 使用插件还是相当的不习惯 代码跳转 Go to definite IDE 工具建议 () what is the best ide for coding shaderlab - #4 by DaveAstator - Unity Engine - Unity Discussions​​​​​​​I IDE工…

用Swagger进行后端接口测试的实战操作

目录 一.什么是Swagger&#xff1f; 二.Swagger的使用操作流程&#xff1a; 1.在pom.xml配置文件导入 Knife4j 的依赖&#xff1a; 2.在config配置类中加入 Knife4j 的相关配置并设置静态资源映射&#xff08;否则接口文档无法访问&#xff09;&#xff1a; 三.Swagger的四个…