【java】常见限流算法原理及应用

news2024/9/21 14:09:35

目录

前言

限流的作用

4种常见限流算法

固定窗口限流

基本原理

简单实现

优点和缺点

滑动窗口限流

基本原理

简单实现

优点和缺点

漏桶限流

基本原理

简单实现

优点和缺点

令牌桶限流

基本原理

简单实现

优点和缺点

算法比较与选择


前言

在现代分布式系统和高并发场景下,限流已成为保障系统稳定性和可用性的重要手段。随着互联网应用规模的不断扩大,系统经常会面对海量请求和突发流量,如何有效控制和管理这些请求流量成为一项关键挑战。限流算法作为流量控制的重要工具,能够帮助系统平衡资源分配、抵御恶意攻击,并在高峰期维持服务的连续性与可靠性。本文将深入探讨几种常见的限流算法及其应用场景,帮助读者更好地理解限流机制的工作原理与优化策略。

限流的作用

限流的作用在于防止系统过载、保障服务的可用性、优化资源利用、平滑高峰流量,并确保服务质量和用户体验。通过控制请求的频率,限流机制能够在高并发或突发流量的情况下保护系统资源,提升其整体性能和响应能力,从而避免系统崩溃或服务中断。

4种常见限流算法

固定窗口限流

基本原理

计数器算法是在一定的时间间隔里,记录请求次数,当请求次数超过间隔内的最大次数时,触发限流策略。当进入下一个时间窗口时进行访问次数的清零。例如,如果设置了1秒钟内最多允许100个请求的上限,那么系统会统计当前窗口内的请求数量,当请求数量未达到上限时,允许请求通过,一旦达到上限,则拒绝剩余的请求,直到进入下一个时间窗口为止,在新的时间窗口开始时,计数器会被重置,重新统计请求数量。如下图所示:

简单实现
import org.redisson.api.*;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Service
public class RedisLimiterManager {

    @Resource
    RedissonClient redissonClient;


    /**
     * 限流操作
     *
     * @param key        限流键
     * @param limit      请求限制数量
     * @param windowSize 窗口大小
     */
    public void doRateLimit(String key, Long limit, Long windowSize) {
        // 加分布式锁
        RLock rLock = redissonClient.getLock(key);
        try {
            // 加锁
            rLock.lock(100, TimeUnit.MILLISECONDS);
            // 获取原子计数器
            RAtomicLong counter = redissonClient.getAtomicLong(key);
            // 计数
            long count = counter.incrementAndGet();
            // 如果是第一个请求,初始化窗口并设置过期时间
            if (count == 1) {
                // 窗口时长设置为1秒
                counter.expire(windowSize, TimeUnit.SECONDS);
            }

            // 如果请求数量超过限制,触发限流
            if (count > limit) {
                // 触发限流的不记在请求数量中
                counter.decrementAndGet();
                // 执行限流逻辑

            }

            // 请求通过,继续处理业务逻辑

        } finally {
            rLock.unlock();
        }

    }
}
优点和缺点

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

缺点:存在明显的临界问题,比如: 假设限流阀值为5个请求,单位时间窗口是1s,如果我们在单位时间内的前0.8-1s1-1.2s,分别并发5个请求。虽然都没有超过阀值,但是如果算0.8-1.2s,则并发数高达10,已经超过单位时间1s不超过5阀值的定义啦。

滑动窗口限流

基本原理

滑动窗口顾名思义,就是持续的滑动,它的窗口大小是固定的,但是起止时间是动态的,窗口大小被分割成大小相等的若干子窗口,每次滑动,都会滑过一个子窗口,同时每个子窗口单独计数,并且所有子窗口的计数求和不应大于整体窗口的阈值。这样的话,当新请求的时间点大于时间窗口右边界时,窗口右移一个子窗口,最左边的子窗口的计数值舍弃。 例如,如果设定的限流策略是“每秒钟最多允许100个请求”,那么系统会不断滑动地统计过去1秒内的请求次数。如果请求次数未达到上限,允许请求通过;否则拒绝请求。如下图所示:

简单实现
import org.redisson.api.*;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Service
public class RedisLimiterManager {

    @Resource
    RedissonClient redissonClient;


    /**
     * 限流操作
     *
     * @param key        限流键
     * @param limit      请求限制数量
     * @param windowSize 窗口大小
     */
    public void doRateLimit(String key, Long limit, Long windowSize) {
        // 获取计数的有序集合
        RScoredSortedSet<Long> counter = redissonClient.getScoredSortedSet(key);
        // 使用分布式锁
        RLock rLock = redissonClient.getLock(key);
        try {
            // 加锁
            rLock.lock(200, TimeUnit.MILLISECONDS);

            // 当前时间戳
            long currentTimestamp = System.currentTimeMillis();
            // 窗口起始时间戳
            long windowStartTimestamp = currentTimestamp - windowSize * 1000;
            // 移除窗口外的请求(即移除时间小于窗口起始时间的请求)
            counter.removeRangeByScore(0, true, windowStartTimestamp, false);

            // 将当前时间戳作为score和member存入有序集合中
            counter.add(currentTimestamp, currentTimestamp);
            // 获取当前窗口内的请求数量
            long count = counter.size();

            // 判断是否超过限流阈值
            if (count > limit) {
                // 执行限流逻辑
            }

            // 请求通过,继续处理业务逻辑
        } finally {
            rLock.unlock();
        }
    }
}
优点和缺点

优点:

  • 简单易懂,精度较高,也解决了固定窗口的临界时间处理问题。

缺点:

  • 突发流量无法处理,一旦到达限流后,请求都会直接暴力被拒绝,影响用户的体验。

漏桶限流

基本原理

漏桶算法可以形象地理解为一个固定容量的水桶,水以不规则的速度流入桶中,但以固定的速率从桶底漏出。假设水桶的容量是固定的,如果水流入的速度超过了漏出的速度,且水桶已满,多余的水(请求)将被丢弃。如下图所示:

简单实现
import org.redisson.api.*;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Service
public class RedisLimiterManager {
    private static final String KEY_PREFIX = "leakyBucketRateLimiter:";

    @Resource
    RedissonClient redissonClient;


    /**
     * 限流操作
     *
     * @param key        限流键
     * @param leakRate   漏出速率
     * @param bucketSize 桶的容量
     */
    public void doRateLimit(String key, Long leakRate, Long bucketSize) {
        // 获取当前请求的水位桶
        RBucket<Long> bucket = redissonClient.getBucket(KEY_PREFIX + key);
        // 获取最后一次漏出请求的时间
        RBucket<Long> lastLeakTimeBucket = redissonClient.getBucket(KEY_PREFIX + key + ":lastLeakTime");
        // 创建分布式锁
        RLock lock = redissonClient.getLock(KEY_PREFIX + "LOCK:" + key);

        try {
            // 尝试获取锁
            lock.lock(100, TimeUnit.MILLISECONDS);

            // 当前时间戳
            long currentTime = System.currentTimeMillis();
            // 当前水位
            Long currentWaterLevel = bucket.get();
            // 上次漏出时间
            Long lastLeakTime = lastLeakTimeBucket.get();

            // 初始化水位和漏出时间
            if (currentWaterLevel == null) {
                currentWaterLevel = 0L;
            }
            if (lastLeakTime == null) {
                lastLeakTime = currentTime;
            }

            // 计算自上次漏出以来经过的时间
            long timeElapsed = currentTime - lastLeakTime;
            // 计算漏出的请求数量
            long leakedAmount = timeElapsed / leakRate;

            // 更新水位
            if (leakedAmount > 0) {
                // 更新水位,确保不为负
                currentWaterLevel = Math.max(0, currentWaterLevel - leakedAmount);
                // 更新最后漏出时间
                lastLeakTimeBucket.set(currentTime);
            }

            // 检查是否可以接受新的请求
            if (currentWaterLevel < bucketSize) {
                // 接受请求,水位加一
                bucket.set(currentWaterLevel + 1);
                // 请求通过,继续处理业务逻辑
            } 
            
            // 触发限流逻辑

        } finally {
            lock.unlock();
        }
    }
}
优点和缺点

优点:

  • 既能够限流,还能够平滑控制处理速度。

缺点:

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

令牌桶限流

基本原理

令牌桶算法以恒定的速率生成令牌并放入桶中,令牌数不会超过桶容量,当有请求到来时,会尝试申请一块令牌,如果没有令牌则会拒绝请求,有足够的令牌则会处理请求,并且减少桶内令牌数。如下图所示:

简单实现
import org.redisson.api.*;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class RedisLimiterManager {

    @Resource
    RedissonClient redissonClient;

    /**
     * 限流操作
     *
     * @param key 限流键
     */
    public void doRateLimit(String key) {
        RRateLimiter rRateLimiter = redissonClient.getRateLimiter(key);
        // 设置令牌桶限流器的限流效果:如限流的类型、每个限流时间窗口内生成的令牌数量、速率的时间间隔和时间间隔的单位。
        rRateLimiter.trySetRate(RateType.OVERALL, 2, 1, RateIntervalUnit.SECONDS);
        // 尝试获取 1 个令牌
        boolean canOp = rRateLimiter.tryAcquire(1);

        if (!canOp) {
            // 获取令牌失败
            // 执行失败后的操作....
        }

        // 请求通过,继续处理业务逻辑

    }
}
优点和缺点

优点:

  • 面对突发流量,可以在短时间内提供更多的处理能力,以处理突发流量。
  • 与漏桶算法相比,令牌桶算法提供了更大的灵活性,可以动态调整生成令牌的速率。

缺点:

  • 如果令牌产生速率和桶的容量设置不合理,可能会出现问题比如大量的请求被丢弃、系统过载。

算法比较与选择

固定窗口算法:业务简单,对突发流量要求不高的场景。

滑动窗口算法:需要精确控制请求速率,平滑限流时使用。

漏桶算法:适合对流量有严格平稳要求的场景,尤其是在处理突发请求能力有限、系统必须稳定输出流量的情况下。

令牌桶算法:对突发流量有要求,对稳定性和精度要求较高的场景。

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

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

相关文章

102.SAPUI5 sap.ndc.BarcodeScannerButton调用摄像头时,localhost访问正常,使用IP访问失败

目录 原因 解决办法 1.修改谷歌浏览器的setting 2.在tomcat中配置https访问 参考 使用SAPUI5的sap.ndc.BarcodeScannerButton调用摄像头时&#xff0c;localhost访问正常&#xff0c;使用IP访问时&#xff0c;一直打不开摄像头&#xff0c;提示getUserMedia()问题。 原因…

【React】(推荐项目)一个用 React 构建的 CRUD 应用程序

推荐项目&#xff1a;CRUD 应用示例 在本篇文章中&#xff0c;我想向大家推荐一个非常实用的项目&#xff1a;CRUD 应用示例。这个项目展示了如何使用现代技术栈创建一个基础的增删改查&#xff08;CRUD&#xff09;应用&#xff0c;非常适合用于学习和实践后端开发技能。 适…

【工具变量】科技金融试点城市DID数据集(2000-2023年)

时间跨度&#xff1a;2000-2023年数据范围&#xff1a;286个地级市包含指标&#xff1a; year city treat post DID&#xff08;treat*post&#xff09; 样例数据&#xff1a; 包含内容&#xff1a; 全部内容下载链接&#xff1a; 参考文献-pdf格式&#xff1a;https://…

Navicat连接SQLServer报错

一、Navicat连接SQLServer报错 1.1、问题描述 由于项目原因&#xff0c;需要使用SQLServer作为数据源。Navicat中新建SQLServer的连接&#xff0c;填写完必要信息后连接失败&#xff0c;报错原因如下&#xff1a; 1.2、Navicat的版本 1.3、解决方法 第一步&#xff1a;下载【s…

工厂模式,策略模式,代理模式,单例模式在项目中的应用

项目背景&#xff1a; 首先这篇文章是总结了OJ项目和AI答题平台项目&#xff08;和一点点的聚合搜索项目&#xff09;中设计模式的文章 在项目中也用了很多次的设计模式&#xff0c;我感觉起来&#xff0c;这些设计模式的作用就是提高项目的扩展性和降低耦合性 工厂模式&…

Gitlab学习(007 gitlab项目操作)

尚硅谷2024最新Git企业实战教程&#xff0c;全方位学习git与gitlab 总时长 5:42:00 共40P 此文章包含第25p-第p26的内容 文章目录 推送项目到gitlabidea安装gitlab插件配置免密登录推送项目到远程库 在gitlab上创建项目额外功能的使用推送分支到远程库标记功能创建合并请求 推…

leetcode刷题(71-75)

算法是码农的基本功&#xff0c;也是各个大厂必考察的重点&#xff0c;让我们一起坚持写题吧。 遇事不决&#xff0c;可问春风&#xff0c;春风不语&#xff0c;即是本心。 我们在我们能力范围内&#xff0c;做好我们该做的事&#xff0c;然后相信一切都事最好的安排就可以啦…

IM项目组件学习-----spdlog二次封装

这里写自定义目录标题 封装的原因封装的思想初始化接口的封装对日志输出接口进行宏的封装 封装的原因 1.避免单例的锁冲突&#xff0c;因此直接创建全局的线程安全的日志器进行使用 2.因为日志输出没有文件名行号&#xff0c;因此使用宏进行二次封装输出日志的文件名和行号 3.…

【shell脚本1】Shell脚本学习--入门

目录 简介 Hello World 注释 打印输出 简介 Shell是一种脚本语言&#xff0c;那么&#xff0c;就必须有解释器来执行这些脚本。 Unix/Linux上常见的Shell脚本解释器有bash、sh、csh、ksh等&#xff0c;习惯上把它们称作一种Shell。我们常说有多少种Shell&#xff0c;其实说的…

昇腾大模型推理解决方案MindIE部署

MindIE大模型推理套件 MindIE&#xff08;Mind Inference Engine&#xff0c;昇腾推理引擎&#xff09;是华为公司针对AI全场景推出的整体解决方案&#xff0c;包含丰富的推理加速套件。通过开放各层次AI能力&#xff0c;支撑客户多样化的AI业务需求&#xff0c;使能百模千态&a…

Windows快捷切换Java jdk版本

使用方法 新建txt文本文件&#xff0c;将下方代码粘贴进去编辑对应的jdk路径 如&#xff1a;set JAVA_HOMED:\Java\jdk-17.0.11修改文件后缀为bat&#xff0c;双击运行选择对应版本 echo off rem 切换Java jdk版本 echo 请以管理员来进行切换 java -version:menu echo echo 请…

2024华为杯C题详细完整思路和视频讲解

文章目录 一、背景问题描述数据描述问题问题一&#xff1a; 励磁波形分类问题二&#xff1a; 斯坦麦茨方程&#xff08;Steinmetz-equation&#xff09;修正问题三&#xff1a; 磁芯损耗因素分析问题四问题五 参考文献补充磁芯损耗分离模型磁芯损耗经验计算模型 特别注意事项问…

【Android Studio】2024.1.1最新版本AS调试老项目(老版AS项目文件、旧gradle)导入其他人的项目

文章目录 实验环境开始修改项目文件1. 删除.gradle及.idea两个文件夹2.修改SDK路径&#xff08;本地SDK存放路径&#xff09;3.修改gradle版本4.修改gradle插件版本&#xff08;AGP&#xff09;5.修改JDK版本 实验环境 Android Studio 版本 项目版本 开始修改项目文件 1. 删…

我的AI工具箱Tauri版-MicrosoftTTS文本转语音

本教程基于自研的AI工具箱Tauri版进行MicrosoftTTS文本转语音服务。 MicrosoftTTS文本转语音服务 是自研的AI工具箱Tauri版中的一款功能模块&#xff0c;专为实现高效的文本转语音操作而设计。通过集成微软TTS服务&#xff0c;用户可以将大量文本自动转换为自然流畅的语音文件…

创新驱动,技术引领:2025年广州见证汽车电子技术新高度

汽车行业的创新浪潮正汹涌澎湃&#xff0c;一场引领未来出行的科技盛宴即将拉开帷幕&#xff01; AUTO TECH 2025 第十二届广州国际汽车电子技术展览会将于 2025 年 11 月 20日至 22 日在广州保利世贸博览馆&#xff08;PWTC Expo&#xff09;隆重举行。 作为亚洲地区领先的汽…

树莓派智能语音助手实现音乐播放

树莓派语音助手从诞生的第一天开始&#xff0c;我就想着让它能像小爱音箱一样&#xff0c;可以语音控制播放音乐。经过这些日子的倒腾&#xff0c;今天终于实现了。 接下里&#xff0c;和大家分享下我的实现方法&#xff1a;首先音乐播放模块用的是我在上一篇博文写的《用sound…

netty编程之基于websocket发送二进制数据

写在前面 本文看下基于websocket发送二进制数据。 1&#xff1a;正文 直接看源码吧&#xff0c;主要如下几个类&#xff1a; WebSocketServerProtocolHandler (内置)&#xff1a;负责websocket握手消息处理 BinaryWebSocketFrameHandler(自定义)&#xff1a;负责处理二进制…

【深入理解SpringCloud微服务】了解微服务的熔断、限流、降级,手写实现一个微服务熔断限流器

【深入理解SpringCloud微服务】了解微服务的熔断、限流、降级&#xff0c;手写实现一个微服务熔断限流器 服务雪崩熔断、限流、降级熔断降级限流 手写实现一个微服务熔断限流器架构设计代码实现整体逻辑ProtectorAspect#aroundMethod(ProceedingJoinPoint)具体实现1、获取接口对…

在 Windows 上运行 Vue 项目时解决 ‘NODE_OPTIONS‘ 错误

在 Windows 上运行 Vue 项目时解决 ‘NODE_OPTIONS’ 错误 在 Windows 系统上启动 Vue 项目时&#xff0c;遭遇报错。具体报错信息如下&#xff1a; ‘NODE_OPTIONS‘ 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。这个错误通常意味着 Windows 系统无法识…

速通LLaMA2:《Llama 2: Open Foundation and Fine-Tuned Chat Models》全文解读

文章目录 概览LLaMA和LLaMA2的区别AbstractIntroductionPretrainingFine-tuning1. 概括2、Supervised Fine-Tuning&#xff08;SFT&#xff09;3、⭐Reinforcement Learning with Human Feedback&#xff08;RLHF&#xff09;&#x1f53a;总览Training Objectives&#xff1a;…