Redis-限流方案

在实际业务中,可能会遇到瞬时流量剧增的情况,大量的请求可能会导致服务器过载和宕机。为了保护系统自身和上下游服务,需要采用限流的方式,拒绝部分请求。
限流就是对请求的频率进行控制,迅速拒绝超过请求阈值的请求。
比如:Redis的QPS为10w,但是此时20w个请求同一时刻请求到Redis,造成Redis性能降低。
[图片]

1. 什么是限流

限流就是在高并发场景下,通过限制系统处理请求的速率,迅速拒绝超过设定上限的请求(最大的QPS),从而保证系统正常运行。
在限流技术中,主要是有两个注意点:当前系统请求的阈值和拒绝策略。
请求的阈值:阈值就是单位时间内允许请求的最大请求数。例如,将QPS(Queries Per Second)设置为1000,表明1s内最多接受1000个请求。通过设置适当的阈值,可以有效控制系统负载,避免系统请求过多而崩溃。(阈值是上线前通过压测或者评估得出的指标)

拒绝策略: 处理超过阈值的请求的方法,常见的拒绝策略包括直接拒绝和等待。

  • 直接拒绝策略:直接拒绝超过阈值的请求,直接返回给客户端。(提示:当前抢购人数太多)
  • 排队等待策略:将请求放入队列中,按照一定规则处理,避免瞬间拒绝大量请求。

2. 常见的限流算法

常见的限流算法分为:固定窗口(计数器限流)、滑动窗口、漏桶、令牌桶 四个方案

2.1. 固定窗口限流算法

在一段时间间隔内,处理请求的最大数量。

固定窗口其实就是时间窗口,原理是将时间划分成固定大小的窗口,并且在每个窗口内限制请求的数量和速率,如果某个窗口内的请求超过了阈值,就直接执行拒绝策略。

即:固定窗口限流算法是规定了单位时间处理的请求数量。

算法步骤:

  1. 将时间划分为固定大小的窗口,例如每秒一个。
  2. 在每一个时间窗口内,记录请求的数量。
  3. 当新的请求到达时,当前窗口的请求计数器的值加一。
  4. 如果窗口内请求计数器超过阈值,那么执行拒绝策略。
  5. 当窗口结束后,重置计数器。
    在这里插入图片描述

例如,每个时间窗口为1s,限流的阈值为3,窗口内超过阈值的请求都会触发拒绝策略。
优点:实现简单,易于理解。
缺点:

  • 请求不够均匀:在固定窗口算法中,请求在窗口内的分布可能会不均匀。假如限制某个接口每分钟只能访问30次,那么前30秒就会有30个请求到达的话,那么后30秒就无法处理请求,导致请求不均匀。
  • 无法保证限流速率,因而无法因对突增的流量。比如一个接口一分钟只能访问1000次,但是接口的QPS为500,前55秒这个接口没有收到请求,但是后一秒突然接收到1000个请求。然后,在当前场景下,这1000个请求在1s内是无法被处理的,系统可能就直接被大量的请求给击垮了。
  • 存在明显的临界问题(请求突刺问题):前一个窗口和后一个窗口之间的可能会存在大量的请求,无法应对无法流量。
    比如:一个窗口内请求的阈值为3,以下存在临界问题:
    在这里插入图片描述
public class FixedWindowLimiter {private static final String FORMAT_TIME = "yyyy-MM-dd HH:mm:ss";private final long windowSize; // 窗口大小,默认是毫秒private final int maxRequests; // 最大请求数private int currentRequests; //当前窗口内的请求数private LocalDateTime lastRestTime; //上次窗口重置时间,即窗口开始时间private final Lock resetMutex; //重置锁public FixedWindowLimiter(Duration windowSize, int maxRequests) {this.windowSize = windowSize.toMillis();this.maxRequests = maxRequests;this.currentRequests = 0;this.lastRestTime = LocalDateTime.now();this.resetMutex = new ReentrantLock();}public boolean allow() {resetMutex.lock();try {LocalDateTime now = LocalDateTime.now();if (Duration.between(lastRestTime, now).toMillis() >= windowSize) {this.currentRequests = 0;this.lastRestTime = now;}if (currentRequests >= maxRequests) {return false;}currentRequests++;return true;}finally {resetMutex.unlock();}}
}class FixedWindowLimiterApp {private static final String FORMAT_TIME = "yyyy-MM-dd HH:mm:ss";public static void main(String[] args) {System.out.println(System.currentTimeMillis() / 1000);FixedWindowLimiter limiter = new FixedWindowLimiter(Duration.ofSeconds(1), 3);DateTimeFormatter formatter = DateTimeFormatter.ofPattern(FORMAT_TIME);ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);for (int i = 0; i < 20; i++) {Runnable task = () -> {String now = LocalDateTime.now().format(formatter);if (limiter.allow()) {System.out.println(now + "请求通过");} else {System.out.println(now + "请求被限流");}};pool.schedule(task, i * 100, TimeUnit.MILLISECONDS);}pool.shutdown();try {if (!pool.awaitTermination(5, TimeUnit.SECONDS)) {pool.shutdownNow();}} catch (InterruptedException e) {pool.shutdownNow();}}
}

2.2. 滑动窗口限流算法

滑动窗口限流算法是固定窗口限流算法的升级版本,限流的粒度更小。
滑动窗口和固定窗口相比:滑动窗口是将时间按照一定的比例分片。
假如接口限流每分钟处理60个请求,可以将1分钟分成60个小窗口,然后每隔一秒移动一次,每个窗口一秒只能处理不大于60(请求数) / 60(窗口数),如果当前窗口的请求计数综合超过了限制的数量的话,就触发拒绝策略。
[图片]

这样的话,可以处理[0.5秒 ~ 1.5秒]内的临界问题,红色部分的请求将会触发拒绝策略。
优点:

  • 相比于固定窗口算法,滑动窗口计数器算法可以应对突发流量,解决一些临界问题。
  • 限流粒度更小,可以提供更精确的限流控制。
    缺点:
  • 和固定窗口一样,存在请求不够均匀的情况。

2.3. 漏桶限流算法

漏桶限流的核心思想就是将请求存储在一个漏桶中,无论我们以任意速率存入请求,漏桶始终以固定的速率流出。所以漏桶算法可以将突发流量均匀地处理,确保系统在稳定的负载下运行。
[图片]

优点:

  1. 实现简单,易于理解。
  2. 可以控制限流速率,避免网络拥塞和系统过载。
    缺点:
  3. 在突发情况请求过多时,会丢弃过多的请求。

2.4. 令牌桶限流算法

令牌桶算法也比较简单,和漏桶算法一样,主角还是桶。不过现在桶里面装的是令牌,请求在被处理之前需要拿到一个令牌,请求处理完毕之后直接丢弃令牌即可。我们可以根据限流大小,按照一定的速率往桶里添加令牌,如果桶装满了,就不能继续往桶里添加令牌了。


优点:

  1. 可以应对突发流量:由于令牌可以在桶中堆积,当流量突然增大时,如果桶中有足够的令牌,系统可以快速响应这种突发流量,避免请求被立即拒绝。这使得令牌桶算法特别适合处理具有突发性流量的应用场景。
  2. 灵活性强:通过适当调整流派的生成速率和桶的容量,可以灵活地控制流量。
    算法设计:
public class TokenBucketLimiter {private final int rate; // 令牌产生的速度,即每秒生成多少个令牌private final int capacity; // 令牌桶容量,最多可存储令牌数private int currentTokenNum; // 当前令牌数private LocalDateTime lastTime; // 上次请求时间private final Lock lock; // 请求锁public TokenBucketLimiter(int rate, int capacity) {this.rate = rate;this.capacity = capacity;this.currentTokenNum = 0;this.lastTime = LocalDateTime.now();this.lock = new ReentrantLock();}public int getCurrentTokenNum() {return currentTokenNum;}public int getRate() {return rate;}public int getCapacity() {return capacity;}public boolean allow() {lock.lock();try {// 计算当前时间和上一次更新时间的时间间隔long timeDiff = Duration.between(lastTime, LocalDateTime.now()).toMillis();// 计算时间间隔内生成的令牌数量int tokenCount = (int) (timeDiff / 1000.0 * rate);if (tokenCount > 0) {currentTokenNum += tokenCount;lastTime = LocalDateTime.now();}// 令牌的数量不能超过桶的大小if (currentTokenNum > capacity) {currentTokenNum = capacity;}// 获取令牌if (currentTokenNum > 0) {currentTokenNum--;return true;}return false;} finally {lock.unlock();}}}class TokenBucketLimiterApp {public static void mockRequest(int n, Duration d, TokenBucketLimiter limiter) {ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);for (int i = 0; i < n; i++) {int requestId = i + 1;executor.schedule(() -> {if (limiter.allow()) {System.out.printf("第%d个请求通过\n", requestId);} else {System.out.printf("第%d个请求被限流\n", requestId);}}, i * d.toMillis(), TimeUnit.MILLISECONDS);}executor.shutdown();try {if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {executor.shutdownNow();}} catch (InterruptedException e) {executor.shutdownNow();}}public static void main(String[] args) {System.out.println("=================令牌桶算法=================");// 创建一个令牌桶,令牌的生成速度为每秒4个,桶容量为5个请求TokenBucketLimiter limiter = new TokenBucketLimiter(4, 5);// 发送10个请求,每50ms发送一个mockRequest(10, Duration.ofMillis(50), limiter);System.out.println("------------------------------------------");}
}

3. 针对什么进行限流?

实际项目中,针对的先流对象是什么?也就是说针对什么来进行限流?常见的限流对象如下:

  1. 针对IP限流:比较常见,比如通过X-forwarded-For或TCP options字段中真实源IP信息。
  2. 业务ID:挑选唯一的业务ID实现限流。比如,根据用户的ID进行限流。(写leetcode的时候多次提交,提醒提交太快)。
  3. 个性化:根据用户的属性或者行为。进行不同的限流策略。例如,VIP用户不限流,普通用户限流。

4. 单机限流

单机限流是指在单台服务器上,通过限制其在单位时间内处理的请求数量来防止过载。
单机限流可以使用Google Guava自带的限流工具类 RateLimiter。RateLimiter是基于令牌桶算法实现的,可以应对突发流量。

5. 分布式限流

单机限流只能保证一个节点限流,但是无法保证分布式限流。
分布式限流和分布式锁的思想是一样的,就是将原本设计实现在本地服务的限流操作抽离出来,做成一个中心化的限流器,实现全局共享。


分布式限流常见方案:可以借助Sentinel或者使用Redis来自己实现对应的限流逻辑。
基于Redis的实现步骤如下:(Redis + Lua)

  1. 选取一个Redis中心化组件
  2. 设定限流规则,比如每秒允许的最大请求数(QPS),并且将该值存储在Redis中。
  3. 每当有请求到达时,服务器首先会向Redis请求令牌。
  4. 如果获得令牌,请求可以继续处理;如果未获得令牌,则表示请求被限流,执行拒绝策略。
local key = KEYS[1] -- hashmap的key
local rate = tonumber(ARGV[1]) -- 令牌生成速率
local capacity = tonumber(ARGV[2]) -- 桶的容量
local now = tonumber(ARGV[3]) -- 当前时间(毫秒)
local requested = tonumber(ARGV[4]) -- 请求的令牌数-- 获取上次更新时间和当前令牌数
local last_time = redis.call('HGET', key, 'last_time')
local current_tokens = redis.call('HGET', key, 'tokens')-- 如果是首次访问,那么就将last_time初始化为当前时间,当前令牌数为桶容量
if last_time == false thenlast_time = nowcurrent_tokens = capacity
else-- 不是第一次访问,那么从redis获取上次访问时间和当前令牌数last_time = tonumber(last_time)current_tokens = tonumber(current_tokens)
end-- 计算时间差
local time_diff = now - last_time
-- 计算新的令牌数
local new_tokens = math.min(capacity, current_tokens + (time_diff * rate / 1000))local allowed = new_tokens >= requestedif allowed thennew_tokens = new_tokens - requested
endredis.call('HSET', key, 'last_time', now)
redis.call('HSET', key, 'tokens', new_tokens)return allowed

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

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

相关文章

无感方波开环强拖总结

一、强拖阶段的核心原理与设计要点 开环换相逻辑 固定频率斜坡&#xff1a;以预设斜率逐步提升换相频率&#xff08;如0.5-5Hz/ms&#xff09;&#xff0c;强制电机跟随磁场旋转。电压-频率协调控制&#xff1a;初始阶段施加高电压&#xff08;80%-100%额定&#xff09;克服静摩…

Java虚拟机之垃圾收集(一)

目录 一、如何判定对象“生死”&#xff1f; 1. 引用计数算法&#xff08;理论参考&#xff09; 2. 可达性分析算法&#xff08;JVM 实际使用&#xff09; 3. 对象的“缓刑”机制 二、引用类型与回收策略 三、何时触发垃圾回收&#xff1f; 1. 分代回收策略 2. 手动触发…

代码随想录算法训练营第22天 | 组合 组合总和 电话号码的字母组合

77. 组合 77. 组合 - 力扣&#xff08;LeetCode&#xff09; class Solution {List<Integer> path new ArrayList<>();List<List<Integer>> result new ArrayList<>();public void backTracking(int n,int k,int startIndex){if(path.size() …

#UVM# 关于field automation机制中的标志位及if的使用

通过前面文章的复习,我们知道了 uvm_field 机制带来的好处,确实方便了我们很多代码的coding 时间,但是会不会有一种情况呢? 比如,我们不想将实例中的某一些成员进行打包、复制、比较操作,怎么办呢? 如果只执行 比较但不进行打包操作呢?是不是很复杂呢 ? 一 标志位…

RK3588 安装ffmpeg6.1.2

在安装 ffmpeg 在 RK3588 开发板上时,你需要确保你的开发环境(例如 Ubuntu、Debian 或其他 Linux 发行版)已经设置好了交叉编译工具链,以便能够针对 RK3588 架构编译软件。以下是一些步骤和指导,帮助你安装 FFmpeg: 1. 安装依赖项 首先,确保你的系统上安装了所有必要的…

leetcode day25 28 KMP算法

28找出字符串中第一个匹配项的下标 给你两个字符串 haystack 和 needle &#xff0c;请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标&#xff08;下标从 0 开始&#xff09;。如果 needle 不是 haystack 的一部分&#xff0c;则返回 -1 。 示例 1&#xff…

编程语言介绍:Rust

什么是Rust Rust是由Mozilla研究院开发的一种系统级编程语言&#xff0c;旨在提供更好的内存安全保证&#xff0c;同时保持高性能&#xff0c;自2010年首次发布以来&#xff0c;Rust以其安全性、并发性和实用性迅速获得了广泛的关注。Rust最独特的特性之一是其所有权模型&#…

Java Spring MVC (2)

常见的Request Controller 和 Response Controller 的区别 用餐厅点餐来理解 想象你去一家餐厅吃饭&#xff1a; Request Controller&#xff08;接单员&#xff09;&#xff1a;负责处理你的点餐请求&#xff0c;记录你的口味、桌号等信息。Response Controller&#xff08…

Oracle 字符类型对比

本文以 Oracle12c 为例 1.主要区别对比 类型存储方式最大长度字符集支持适用场景备注​CHAR(M)固定长度空格填充2000 字节&#xff0c;M 代表字节长度默认字符集固定长度编码实际存储长度固定为定义长度&#xff08;如 CHAR(10) 始终占 10 字节&#xff09;​VARCHAR2(M)可变长…

Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露

一&#xff1a;背景 1. 讲故事 前面跟大家分享过一篇 C# 调用 C代码引发非托管内存泄露 的文章&#xff0c;这是一个故意引发的正向泄露&#xff0c;这一篇我们从逆向的角度去洞察引发泄露的祸根代码&#xff0c;这东西如果在 windows 上还是很好处理的&#xff0c;很多人知道开…

vite.config.js 是Vite 项目的配置文件,分析具体用法

vite.config.js 是 Vite 项目的配置文件&#xff0c;用于定义项目的构建、开发服务器、插件等配置选项。以下是示例代码中各部分的作用分析&#xff1a; 1. 导入模块 import { fileURLToPath, URL } from node:url import { defineConfig } from vite import vue from vitejs…

行为模式---责任链模式

概念 责任链模式是一种行为设置模式&#xff0c;它的核心思想就是将请求的发送者和接收者进行解耦&#xff0c;每个接收者都可以处理请求。 在责任链模式中将每个接收者连成一个链条&#xff0c;当有请求发送上来的时候会经过每一个接收者。直到消息被处理。 适用场景 1、当…

pytest结合allure

Allure 一、文档二、指令三、装饰器3.1 allure.step装饰器3.2 allure.description装饰器3.3 allure.title装饰器3.4 allure.link、allure.issue 和 allure.testcase装饰器3.5 allure.epic、allure.feature 和 allure.story装饰器3.6 allure.severity装饰器 一、文档 allure文档…

前端知识点---http.createHttp()的理解(arkts)

通俗易懂的例子&#xff1a;点外卖 &#x1f354;&#x1f964; 想象一下&#xff0c;你在家里点外卖&#xff0c;HTTP 请求就像是你和餐厅之间的沟通方式。 1️⃣ 没有 http.createHttp()&#xff1a;每次点餐都重新拨电话 &#x1f4de; 如果你每次点餐都重新拨打餐厅的电话…

大模型开发(五):P-Tuning项目——新零售决策评价系统(下)

P-Tuning项目——新零售决策评价系统&#xff08;下&#xff09; 0 前言1 P-Tuning原理2 数据处理 0 前言 上篇文章我们介绍了使用PET方式微调BERT模型&#xff0c;PET属于提示词微调的一种&#xff0c;另一种比较常见的提示词微调是P-Tuning&#xff0c;我们今天在相同的项目…

分布式中间件:Redis介绍

目录 Redis 概述 Redis 的特点 高性能 丰富的数据结构 持久化 分布式特性 简单易用 Redis 的数据结构 字符串&#xff08;String&#xff09; 哈希&#xff08;Hash&#xff09; 列表&#xff08;List&#xff09; 集合&#xff08;Set&#xff09; 有序集合&…

在昇腾GPU上部署DeepSeek大模型与OpenWebUI:从零到生产的完整指南

引言 随着国产AI芯片的快速发展&#xff0c;昇腾&#xff08;Ascend&#xff09;系列GPU凭借其高性能和兼容性&#xff0c;逐渐成为大模型部署的重要选择。本文将以昇腾300i为例&#xff0c;手把手教你如何部署DeepSeek大模型&#xff0c;并搭配OpenWebUI构建交互式界面。无论…

系统思考—组织诊断

“未经过诊断的行动是盲目的。” — 托马斯爱迪生 最近和一家教育培训机构沟通时&#xff0c;发现他们面临一个有意思的问题&#xff1a;每年招生都挺不错&#xff0c;但教师的整体绩效一直提升缓慢&#xff0c;导致师生之间存在长期的不匹配。管理层试了很多办法&#xff0c;…

AI大模型学习(五): LangChain(四)

Langchian读取数据库 案例&#xff1a;在数据库中表格数据上的问题系统的基本方法,将涵盖使用链和代理的视线,通过查询数据库中的数据并得到自然语言的答案,两者之间的主要区别在于,我们代理可以根据多次循环查询数据库以回答问题 实现思路: 1.将问题转换成DSL查询,模型将用…

人工智能与深度学习的应用案例:从技术原理到实践创新

第一章 引言 人工智能(AI)作为21世纪最具变革性的技术之一,正通过深度学习(Deep Learning)等核心技术推动各行业的智能化进程。从计算机视觉到自然语言处理,从医疗诊断到工业制造,深度学习通过模拟人脑神经网络的层次化学习机制,实现了对复杂数据的高效分析与决策。本…