限流原理与实践:固定窗口、滑动窗口、漏桶与令牌桶解析

方案一、固定窗口限流算法

这里我们通过一个 demo 来介绍固定窗口限流算法。

  1. 创建一个 FixWindowRateLimiterService 类。
@Service
public class FixWindowRateLimiterService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    private static final DefaultRedisScript<Long> LIMIT_SCRIPT;


    /**
     * 是否运行请求通过
     *
     * @param key     Redis key
     * @param max     允许请求通过的最大数
     * @param timeout 一个窗口的时间
     * @return true:通过 false:限流
     */
    public boolean isAllowed(String key, Long max, Long timeout) {
        Long signal = stringRedisTemplate.execute(
                LIMIT_SCRIPT,
                Collections.singletonList(key),
                String.valueOf(max),
                String.valueOf(timeout)
        );

        if (Objects.isNull(signal)) {
            return false;
        }

        //返回 0,则说明就是限流
        return signal != 0;
    }

    static {
        LIMIT_SCRIPT = new DefaultRedisScript<>();
        LIMIT_SCRIPT.setLocation(new ClassPathResource("RateLimiterLua.lua"));
        LIMIT_SCRIPT.setResultType(Long.class);
    }
}
  1. 写一个 lua 脚本(保证线程安全),lua 脚本要放在 resources 的根目录下
local key = KEYS[1]
local max = tonumber(ARGV[1])
local timeout = tonumber(ARGV[2])
local current = tonumber(redis.call('get', key) or "0")
if current + 1 > max then
    return 0
else
    redis.call("INCRBY", key, 1)
    redis.call("EXPIRE", key, timeout)
    return current + 1
end
  1. 测试
    在这里插入图片描述
    注意,固定窗口算法存在边界问题,下图介绍了窗口问题。

方案二、滑动窗口限流算法

滑动窗口限流算法相对固定窗口限流算法更复杂一些,但更为精确。下面是一个基于 Redis 的 zset 结构 实现的简单滑动窗口限流的例子。

@RestController
public class RateLimitController {
    private static final String KEY = "rate:limit";
    private static final int MAX_VISIT = 1;

    @Resource
    private RedisTemplate redisTemplate;

    @ResponseBody
    @GetMapping("/visit")
    public String visit() {
        //1970-01-01T00:00:00Z 到现在的秒数
        long now = Instant.now().getEpochSecond();
        //移除现在这个时刻往前推 60s 的访问统计数
        redisTemplate.opsForZSet().removeRangeByScore(KEY, 0, now - 60);
        //获取当前的访问统计数
        Long currentVisits = redisTemplate.opsForZSet().zCard(KEY);

        if (currentVisits >= MAX_VISIT) {
            return "请稍后再试~";
        } else {
            redisTemplate.opsForZSet().add(KEY, String.valueOf(now), now);
            return "访问成功~";
        }
    }
}

方案三、漏桶限流算法

这个算法的基本思想就像有一个漏洞的桶一样。漏桶以一定的速度出水,当水流入过大的时候溢出。通过这个思想来进行流量的控制。

在程序实现中,漏桶通常以一个队列的形式存在,在有新的请求先进入队列中。队列以一定的速率处理请求,当队列满了以后,新进入的请求就会被拒绝。

下面是一个漏桶算法的 demo。在这个例子中,我们创建了一个容量为 10 的桶,每秒可以漏水两个(也就是系统每秒可以处理两个请求)。每当有新请求到来时,我们先计算桶里还剩多少水,如果没满则把请求加进去,满了就拒绝请求。

public class LeakyBucketDemo {
    /**
     * 桶的容量
     */
    private final long capacity = 10L;

    /**
     * 水流出的速度
     */
    private final long rate = 2L;

    /**
     * 当前水量(实际上就是请求书)
     */
    private long water = 0L;

    /**
     * 上次漏水时间
     */
    private long lastTime = System.currentTimeMillis();

    public boolean tryConsume() {
        long now = System.currentTimeMillis();
        //计算当前水量
        water = Math.max(0, water - (now - lastTime) * rate);
        lastTime = now;
        //判断剩余空间是否足够
        if ((water + 1) < capacity) {
            water++;
            return true;
        } else {
            return false;
        }
    }

    public static void main(String[] args) {
        LeakyBucketDemo leakyBucketDemo = new LeakyBucketDemo();
        for (int i = 0; i < 11; i++) {
            System.out.println(leakyBucketDemo.tryConsume() ? "请求通过" : "请求被限流");
        }
    }
} 

方案四、令牌桶限流算法

这个算法的思想是在一个常量固定速率下,把令牌放到令牌桶中。当请求来临的时候,令牌桶中有令牌则请求成功,没有令牌则请求失败。每请求成功一次,就会桶令牌丢到一个令牌。

下面是一个使用 Google 开源的 Guava 库来做限流算法的 demo。

public class TokenBucketDemo {
    private final RateLimiter rateLimiter = RateLimiter.create(10);

    public void doRequest() {
        if (rateLimiter.tryAcquire()) {
            System.out.println("正常处理请求");
        } else {
            System.out.println("限流");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TokenBucketDemo tokenBucketDemo = new TokenBucketDemo();
        for (int i = 0; i < 20; i++) {
            Thread.sleep(1000);
            tokenBucketDemo.doRequest();
        }
    }
}

总结

四种限流算法各有优缺点,需要根据自己的业务场景选择使用。推荐项目比较复杂的时候使用成熟的框架,比如sentinel。

  1. 固定窗口限流算法:适用于对精度要求不高,对性能要求比较高的场景。
  2. 滑动窗口限流算法:适用于对限流精度比较高的场景。
  3. 漏桶限流算法:适用于访问速率绝对稳定的场景。
  4. 令牌桶限流算法:适用于访问速率相对比较稳定的场景,但也可以应对一定的突发流量的场景。(对于使用 Guava 实现的方案,如果自己的项目是分布式的,那么此方案不适用 --Guava 是单机的)

最后

我是 xiucai,一位后端开发工程师。

如果你对我感兴趣,请移步我的个人博客,进一步了解。

  • 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注😊
  • 本文首发于个人博客,未经许可禁止转载💌

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

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

相关文章

HCIP —— BGP 基础实验

实验拓扑&#xff1a; 实验要求&#xff1a; 1.所有设备上均有环回接口 2.R1属于AS 100 &#xff0c;R2-R4 属于AS 200 &#xff0c;R5 属于AS 300 3.R2 - R4 属于同一个area &#xff0c;运行OSPF。 4.全网通过运行BGP实现网络互通。 实验步骤&#xff1a; 1.配置 IP地址…

Node.js使用Express框架写服务端接口时,如何将接口拆分到不同文件中

项目目录结构说明&#xff1a; node.js连接mysql数据库步骤可参考&#xff1a;Node.js 连接 MySQL | 菜鸟教程 1、拆分之前的写法&#xff0c;未区分模块&#xff0c;所有接口api都写在了入口文件app.js中&#xff1b; 需求&#xff1a;想要将接口api拆分成根据不同的业务模块…

window10下载与安装zookeeper,图文说明

1&#xff0c;下载 打开连接 &#xff1b;https://downloads.apache.org/zookeeper/ 选择版本下载 2&#xff0c;解压 cmd黑窗口解压命令 tar -zxvf apache-zookeeper-3.8.3-bin3&#xff0c;修改配置 复制zoo_sample.cfg&#xff0c;重命名为zoo.cfg zoo.cfg配置 # The …

操作系统系列:Unix进程系统调用fork,wait,exec

操作系统系列&#xff1a;Unix进程系统调用 fork系统调用fork()运用的小练习 wait系统调用Zombiesexec 系列系统调用 开发者可以查看创建新进程的系统调用&#xff0c;这个模块会讨论与进程相关的Unix系统调用&#xff0c;下一个模块会讨论Win32 APIs相关的进程。 fork系统调用…

uniapp-安卓APP开发时使用手机调试

调试 1. 手机打开开发者模式: 华为手机举列-->设置-->关于手机-->版本号&#xff0c;多次连续点击“版本号”&#xff0c;就会提示已打开开发者模式 2. 华为手机举列-->设置-->系统和更新-->开发人员选项-->打开 USB调试&#xff0c;进入调试模式 3. 先…

硬件基础--实施运维工程师介绍

1.实施运维工程师介绍 1.1 什么是实施工程师 1.2 是实施工程师的职责 1.3 什么是运维工程师&#xff1f; 1.4 运维工程师的职责 1.5 实施运维的主要技能 2.计算机组件 3.BIOS介绍 ​编辑 4.总结 1.实施运维工程师介绍 1.1 什么是实施工程师 常用操作系统、应用软件及公…

scrapy快加构造并发送请求

scrapy数据建模与请求 学习目标&#xff1a; 应用 在scrapy项目中进行建模应用 构造Request对象&#xff0c;并发送请求应用 利用meta参数在不同的解析函数中传递数据 1. 数据建模 通常在做项目的过程中&#xff0c;在items.py中进行数据建模 1.1 为什么建模 定义item即提前…

使用bs4 分析html文件

首先需要 pip install beautifulsoup4安装 然后为了方便学习此插件&#xff0c;随便打开一个网页&#xff0c;然后鼠标右键&#xff0c;打开源网页&#xff0c;如下图片 这样就可以获得一个网页源码&#xff0c;全选复制粘贴到本地&#xff0c;存储为 .html 文件&#xff0c;…

SpringBoot日志

目录 日志 日志使用 观察日志结构 打印日志 日志框架 门面模式&#xff08;外观模式&#xff09; SLF4J框架 日志格式 日志级别 日志配置 配置日志级别 配置控制台颜色 配置日志格式 日志持久化 日志文件分割 更简单的方式---Lombok框架支持 日志 日志作用&…

Tomcat多实例、负载均衡、动静分离

Tomcat多实例部署 安装jdk [rootlocalhost ~]#systemctl stop firewalld.service [rootlocalhost ~]#setenforce 0 [rootlocalhost ~]#cd /opt [rootlocalhost opt]#ls apache-tomcat-8.5.16.tar.gz jdk-8u91-linux-x64.tar.gz rh [rootlocalhost opt]#tar xf jdk-8u91-linu…

C# WPF上位机开发(crc校验)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 为了验证数据传输的过程中有没有发生翻转&#xff0c;我们在传输报文的同时一般还会添加一个crc校验。对于modbus协议也是一样&#xff0c;它在数据…

Jmeter中使用py插件

-安装插件 1、下载插件jython-standalone-2.7.0.jar到jmeter的lib\ext目录下 链接&#xff1a; https://pan.baidu.com/s/13ZXtUwoQEV62M98GaIR26w 提取码&#xff1a;ioyk 2、重启jmeter&#xff0c;查看是否生效&#xff0c;如果这个语言有python的选项说明可以了&#xf…

Excel怎样统计一列中不同的数据分别有多少个?

文章目录 1.打开Excel数据表2.选择“插入”&#xff0c;“数据透视表”3.选择数据透视表放置位置4.将统计列分别拖到“行”和“数值”区间5.统计出一列中不同的数据分别有多少个 1.打开Excel数据表 2.选择“插入”&#xff0c;“数据透视表” 3.选择数据透视表放置位置 4.将统计…

【Ehcache技术专题】「入门到精通」带你一起从零基础进行分析和开发Ehcache框架的实战指南(3-储存方式)

这里写目录标题 Ehcache的存储方式堆内存&#xff08;MemoryStore&#xff09;指定可用内存Xml代码Xml代码Xml代码 驱除策略元素过期Xml代码 非堆内存&#xff08;BigMemory&#xff09;磁盘&#xff08;DiskStore&#xff09;指定可用容量Xml代码 元素过期 Ehcache的存储方式 …

PyCharm关闭项目很慢

我的版本&#xff1a; PyCharm 2023.2.5 (Professional Edition) 问题&#xff1a; 关闭项目的时候显示一直在关闭项目 &#xff08;单次解决&#xff1a;任务管理器里面杀掉PyCharm&#xff09; 解决方案&#xff1a; 在PyCharm中按下快捷键 CtrlShiftA。 输入Registry或…

python画图【01】

前提&#xff1a;使用anaconda环境&#xff0c;且安装好&#xff0c;使用的是jupyter pandas 和 matplotlib 安装教程可以参考&#xff1a;miniconda安装与使用 import pandas as pd读取xlsx表格数据 data pd.read_excel("data1.xlsx",sheet_nameSheet1) #data p…

vue内容渲染

内容渲染指令用来辅助开发者渲染DOM元素的文本内容。常用的内容渲染指令有3个 1.v-text 缺点&#xff1a;会覆盖元素内部原有的内容 2.{{}}&#xff1a;插值表达式在实际开发中用的最多&#xff0c;只是内容的占位符&#xff0c;不会覆盖内容 3.v-html&#xff1a;可以把带有标…

详细教程 - 进阶版 鸿蒙harmonyOS应用 第十二节——鸿蒙操作系统中的动画效果封装:Java和TypeScript版

简介 动画效果是开发鸿蒙应用时的一个重要功能。在这篇文章中&#xff0c;我们将详细探讨如何在鸿蒙系统中使用Java和TypeScript实现动画效果的封装&#xff0c;并提供一些代码示例。 Java版动画效果的实现 在鸿蒙操作系统中&#xff0c;我们可以使用ohos.agp.animation.Anima…

基于SSM的马病管理系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

MyBatis首次使用并查询数据库中表的数据

文章目录 MyBatis首次使用并查询数据库中表的数据1、传统Jdbc代码问题分析传统JDBC存在的问题 2、MyBatis框架概述3、MyBatis入门案例3.1、使用idea创建一个maven项目后&#xff0c;修改pom.xml文件中内容为&#xff1a;3.2、在src-main-resources下创建log4j.properties和myba…