当前位置: 首页 > news >正文

基于 Redis 实现分布式限流

基于 Redis 实现分布式限流

  • 一、 简介
  • 二、分布式限流
      • 1 数据结构
        • 1.1 Redis List
        • 1.2 Redis Set
        • 1.3 Redis Sorted Set
      • 2 实现分布式限流
      • 3 实现原理分析
  • 三、分布式限流算法
    • 1. 计数器算法
    • 2. 漏斗算法
    • 3. 令牌桶算法
  • 四、分布式限流实战
    • 1. 单机限流实现
    • 2. 基于Redis Clusters的分布式限流实现
  • 五、基于Redis分布式限流的优化
    • 1. 缓存击穿
      • 1.1 问题描述
      • 1.2 解决方案
        • 1.2.1 使用互斥锁
        • 1.2.2 使用预热机制
    • 2. 热点key问题的解决方案
      • 2.1 问题描述
      • 2.2 解决方案
        • 2.2.1 分布式锁
        • 2.2.2 分布式缓存
    • 3. 并发竞争优化
      • 3.1 问题描述
      • 3.2 解决方案
        • 3.2.1 使用限流器
        • 3.2.2 使用异步线程池

一、 简介

分布式限流是指通过将限流策略嵌入到分布式系统中,以控制流量或保护服务,保证系统在高并发访问情况下不被过载。

分布式限流可以防止系统因大量请求同时到达导致压力过大而崩溃,从而提高系统的稳定性和可靠性。同时,它可以使得业务资源能够更好地分配,提高系统的效率。

二、分布式限流

1 数据结构

1.1 Redis List

Redis List 是一个可以输入相同元素和非唯一元素的集合且支持在两端进行快速(O(1))插入和删除元素。

1.2 Redis Set

Redis Set 是一个无序,但不允许重复元素的集合。您可以使用这些命令对Redis集进行常规操作: SADD,SREM,SISMEMBER,SMEMBERS等。

1.3 Redis Sorted Set

Redis Sorted Set 是一个有序的、不重复的元素集合。每个元素都关联着一个浮点数值(称为 Score)。通过 Score 可以从小到大排序得到一个有序集合。

2 实现分布式限流

在Redis中可以使用令牌桶算法来实现分布式限速。具体方法为:

Step 1:创建一个列表作为Redis令牌桶

String key = "rate_limit:" + userId;
// 模拟用户请求访问
List<String> tokens = redis.lrange(key, 0, -1);

Step 2:设定令牌桶的基准参数

int maxTokens = 50;
long timeInterval = 1 * 1000;
long now = System.currentTimeMillis();

Step 3:计算Redis中令牌数量

long expiredTokens = tokens.stream().filter(t -> Long.parseLong(t) < now - timeInterval).count();
tokens = tokens.subList((int) expiredTokens, tokens.size());
long remainTokens = maxTokens - tokens.size();

Step 4:基于令牌数量,判断是否超过限制

if (remainTokens < 1) {throw new RateLimitException("请求太频繁,请稍后再试!");
}

Step 5:如果没有超出限制,则更新Redis中令牌数并设置过期时间

Long expiresIn = now + timeInterval;
redis.multi();
redis.rpush(key, String.valueOf(expiresIn));
redis.pexpire(key, timeInterval);
redis.exec();

3 实现原理分析

以上代码所示首先需要建立Redis List用于存储Token,其次需要设定令牌桶的基准参数(比如最大Token数量和Token过期间隔等)。在用户访问请求时,需要计算Redis中的令牌数量,根据规则对访问量进行限制。如果没有超过限制,则需要更新Redis List中令牌数并设置过期时间;如果超过了限制,则需要返回错误信息并拒绝服务。

整个过程中,需要注意并发访问情况下的线程安全问题,并确保流量控制配置的公共协商,如最大QPS(Queries Per Second),哪些接口需限制流量等。

三、分布式限流算法

在实际的系统设计中,为了防止某一时刻出现大量请求导致系统崩溃,我们通常会采用限流策略来控制流量,而Redis作为分布式NoSQL数据库,在限流中也有着广泛的应用。下面介绍一些Redis分布式限流的经典算法。

1. 计数器算法

计数器算法比较简单,直接利用Redis存储每个IP或者用户的请求次数,当请求次数超过预设阈值时拒绝服务。代码如下:

public boolean isAllowed(String key, int limit, int timeout) {Jedis jedis = getJedis();long count = jedis.incr(key);if (count == 1) {jedis.expire(key, timeout);}boolean allowed = count <= limit;if (!allowed) {jedis.del(key);}jedis.close();return allowed;
}
  • key:需要限流的用户标识,可根据IP、UserID等进行定义
  • limit:阈值,即允许的最大请求数
  • timeout:过期时间,对于计数器算法,一定要设置过期时间,否则缓存中的请求次数会一直不断累加

2. 漏斗算法

漏斗算法的核心思想是将请求按照恒定的速率转换为水流,有效控制请求超出服务处理能力的情况。漏斗算法实现代码如下:

public boolean isAllowed(String key, int capacity, double leakRate, int reqCount) {Jedis jedis = getJedis();long nowTime = System.currentTimeMillis();String luaScript ="local currentCapacity = tonumber(redis.call('hget', KEYS[1], 'leftCapacity'))\n"+ "if currentCapacity == nil then\n"+ "    redis.call('hset', KEYS[1], 'lastTime', ARGV[2])\n"+ "    redis.call('hset', KEYS[1], 'leftCapacity', ARGV[1] - 1)\n"+ "    return 1\n"+ "end\n"+ "local changeTime = tonumber(redis.call('hget', KEYS[1], 'lastTime'))\n"+ "local delayMillSeconds = nowTime - changeTime\n"+ "local currentDelayCount = tonumber(delayMillSeconds*ARGV[3])\n"+ "local currentCapacity = math.min(currentDelayCount+currentCapacity, ARGV[1])\n"+ "if currentCapacity >= ARGV[4] then\n"+ "    return 0\n"+ "else\n"+ "    redis.call('hset', KEYS[1], 'leftCapacity', currentCapacity-1)\n"+ "    redis.call('hset', KEYS[1], 'lastTime', nowTime)\n"+ "    return 1\n"+ "end";Object result = jedis.eval(luaScript,Collections.singletonList(key),Arrays.asList(String.valueOf(capacity), String.valueOf(nowTime), String.valueOf(leakRate), String.valueOf(reqCount)));boolean allowed = (result instanceof Long ? (Long) result : 0L) == 1L;jedis.close();return allowed;
}
  • key:需要进行限流的用户标识
  • capacity:漏斗容量,即最大允许请求数量
  • leakRate:漏嘴流水速率,保证有序的请求到达
  • reqCount:预计请求量,用于计算漏斗每次流出的数量

3. 令牌桶算法

令牌桶算法的特点是以一个固定的速率不断产生令牌,并将令牌放入到桶中,访问时若桶为空,则表示请求数超限。令牌桶算法实现代码如下:

public boolean isAllowed(String key, int capacity, double rate, int reqCount) {long nowTime = System.currentTimeMillis();Jedis jedis = getJedis();String luaScript ="local currentLimit = tonumber(redis.call('get', KEYS[1]) or '0')\n"+ "if currentLimit + ARGV[1] > tonumber(KEYS[2]) then\n"+ "    return false\n"+ "else\n"+ "    redis.call('incrby', KEYS[1], ARGV[1])\n"+ "    redis.call('expire', KEYS[1], ARGV[2])\n"+ "    return true\n"+ "end";Object result = jedis.eval(luaScript, 2, key, String.valueOf(capacity), String.valueOf(reqCount), String.valueOf(rate * (nowTime / 1000)));boolean allowed = (result instanceof Boolean ? (Boolean) result : false);jedis.close();return allowed;
}
  • key:需要进行限流的用户标识
  • capacity:桶容量
  • rate:令牌发放速率
  • reqCount:请求数量

四、分布式限流实战

1. 单机限流实现

假设我们有一个需求,需要限制每个IP一分钟内最多只能发送100个请求。可以通过Redis的INCR、EXPIRE等API操作来简单实现单机限流。

public boolean isAllowed(String ip, int limit, int interval) {Jedis jedis = getJedis();String key = "ip:" + ip;long count = jedis.incr(key);if (count == 1) {jedis.expire(key, interval);}boolean allowed = count <= limit;if (!allowed) {jedis.del(key);}jedis.close();return allowed;
}

2. 基于Redis Clusters的分布式限流实现

当业务规模扩大时,单机的限流已经无法满足需求,这时候需要考虑使用Redis Clusters实现分布式限流。Clusers扩展了原先Redis的功能,不仅支持横向扩展,而且提高了整个集群的可用性。限流算法同上,只是需要使用把数据分配到Cluser内不同的节点上。

public boolean isAllowed(String ip, int limit, int interval) {JedisCluster jedis = getJedisCluster();String key = "ip:" + ip;long count = jedis.incr(key);if (count == 1) {jedis.expire(key, interval);}boolean allowed = count <= limit;if (!allowed) {jedis.del(key);}return allowed;
}

五、基于Redis分布式限流的优化

1. 缓存击穿

1.1 问题描述

在高并发场景下,如果存在大量的缓存未命中请求,将会导致访问底层数据存储系统,这种情况被称为缓存击穿

1.2 解决方案

1.2.1 使用互斥锁

/*** 获取缓存值方法* @param key 缓存键值* @return 缓存值*/
public String getCacheValue(String key) {String value = cache.get(key);if (value == null) { //缓存未命中//使用互斥锁Lock lock = redisson.getLock(key);if (lock.tryLock()) { //尝试获取锁try {value = cache.get(key); //再次尝试获取缓存if (value == null) { //如果仍然未命中,从数据库中获取value = db.get(key);cache.put(key, value); //将查询结果放入缓存}} finally {lock.unlock(); //释放锁}} else {Thread.sleep(100); //自旋一段时间后重试return getCacheValue(key);}}return value;
}

1.2.2 使用预热机制

预热机制是指在系统启动的时候,提前加载热点数据到缓存中,以减少缓存未命中请求。预热的方式可以使用定时任务或者其他方式,在系统低峰期进行加载。

2. 热点key问题的解决方案

2.1 问题描述

在高并发场景下,如果某个key的请求量过大,将会导致这个key成为热点key,从而导致缓存雪崩问题。

2.2 解决方案

2.2.1 分布式锁

/*** 获取缓存值方法* @param key 缓存键值* @return 缓存值*/
public String getCacheValue(String key) {String value = cache.get(key);if (value == null) { //缓存未命中//使用分布式锁RLock lock = redisson.getFairLock(key);if (lock.tryLock()) { //尝试获取锁try {value = cache.get(key); //再次尝试获取缓存if (value == null) { //如果仍然未命中,从数据库中获取value = db.get(key);cache.put(key, value); //将查询结果放入缓存}} finally {lock.unlock(); //释放锁}} else {Thread.sleep(100); //自旋一段时间后重试return getCacheValue(key);}}return value;
}

2.2.2 分布式缓存

使用分布式缓存将数据均匀地分散到多个节点上,从而避免单点瓶颈问题。

3. 并发竞争优化

3.1 问题描述

在高并发场景下,对于某些资源的并发访问将会导致性能瓶颈,需要进行并发竞争优化。

3.2 解决方案

3.2.1 使用限流器

限流器类似于信号灯,用于控制并发请求的数量,在高峰期可以采用漏桶算法或令牌桶算法进行限流。

3.2.2 使用异步线程池

对于一些耗时的操作,可以使用异步线程池进行处理,从而避免阻塞主线程,提升系统的并发能力。

相关文章:

基于 Redis 实现分布式限流

基于 Redis 实现分布式限流 一、 简介二、分布式限流1 数据结构1.1 Redis List1.2 Redis Set1.3 Redis Sorted Set 2 实现分布式限流3 实现原理分析 三、分布式限流算法1. 计数器算法2. 漏斗算法3. 令牌桶算法 四、分布式限流实战1. 单机限流实现2. 基于Redis Clusters的分布式…...

前端下载文件方式(Blob)

以下以下载图标svg文件为例&#xff0c;实现点击按钮下载文件&#xff0c;其中icon结构如下&#xff1a; const DownloadSvg (props) > {function download(downfile) {const tmpLink document.createElement("a");const objectUrl URL.createObjectURL(downfi…...

【STM32】FreeRTOS软件定时器学习

软件定时器 FreeRTOS提供了现成的软件定时器功能&#xff0c;可以一定程度上替代硬件定时器&#xff0c;但精度不高。 实验&#xff1a;创建一个任务&#xff0c;两个定时器&#xff0c;按键开启定时器&#xff0c;一个500ms打印一次&#xff0c;一个1000ms打印一次。 实现&…...

【LeetCode】复写零

复写零 题目描述算法描述编程代码 链接: 复写零 题目描述 算法描述 编程代码 class Solution { public:void duplicateZeros(vector<int>& arr) {int n arr.size();int dest -1,cur 0;while(cur < n){if(arr[cur]){dest;}else{dest2;}cur;if(dest > n-1){…...

使用docker-maven-plugin插件构建镜像并推送至私服Harbor

前言 如下所示&#xff0c;建议使用 Dockerfile Maven 插件&#xff0c;但该插件也停止维护更新了。因此先暂时使用docker-maven-plugin插件。 一、开启Docker服务器的远程访问 1.1 开启2375远程访问 默认的dokcer是不支持远程访问的&#xff0c;需要加点配置&#xff0c;开…...

YOLO目标检测——动漫头像数据集下载分享

动漫头像数据集是用于研究和分析动漫头像相关问题的数据集&#xff0c;它包含了大量的动漫风格的头像图像。动漫头像是指以动漫风格绘制的虚构人物的头像图像&#xff0c;常见于动画、漫画、游戏等媒体。 数据集点击下载&#xff1a;YOLO动漫头像数据集50800图片.rar...

学习Vue:Vue3 VS Vue2

Vue 3作为Vue.js的最新版本&#xff0c;带来了一系列令人激动的新特性和改进&#xff0c;让开发者们在构建现代Web应用时体验更加顺畅和高效。本文将全面介绍Vue 3相对于Vue 2的改进&#xff0c;重点解释Composition API的使用&#xff0c;以及新引入的Teleport和Suspense等特性…...

1.2亿成都市城市安全风险综合监测预警平台建设项目

导读&#xff1a;原文《1.2亿&#xff01;成都市城市安全风险综合监测预警平台建设项目WORD》&#xff08;获取来源见文尾&#xff09;&#xff0c;本文精选其中精华及架构部分&#xff0c;逻辑清晰、内容完整&#xff0c;为快速形成售前方案提供参考。 部分页面&#xff1a; …...

《树莓派4B家庭服务器搭建指南》第二十期:在树莓派运行rsnapshot, 实现对服务器数据低成本增量本地备份

title: 020《树莓派4B家庭服务器搭建指南》第二十期&#xff1a;在树莓派运行rsnapshot, 实现对服务器数据低成本增量本地备份 我的天翼云服务器有/opt 和 /usr/share/nginx两个目录, 用来存储网站的内容, 数据无价, 为了避免珍贵的数据丢失&#xff0c;我决定使用树莓派运行 …...

大数据 算法

什么是大数据 大数据是指数据量巨大、类型繁多、处理速度快的数据集合。这些数据集合通常包括结构化数据&#xff08;如数据库中的表格数据&#xff09;、半结构化数据&#xff08;如XML文件&#xff09;和非结构化数据&#xff08;如文本、音频和视频文件&#xff09;。大数据…...

html | 基于iframe的简易富文本编辑器

效果图 支持: 选中后 ctrlI 斜体 代码 思路就是在iframe种嵌套html和css。 <pre> - 支持: 选中后 ctrlI 斜体 - todo: 鼠标实现单击斜体 </pre> <iframe name"richedit" style"height:30%; width:100%;"></iframe><script…...

HJ108 求最小公倍数

描述 正整数A和正整数B 的最小公倍数是指 能被A和B整除的最小的正整数值&#xff0c;设计一个算法&#xff0c;求输入A和B的最小公倍数。 数据范围&#xff1a;1≤a,b≤100000 1≤a,b≤100000 输入描述&#xff1a; 输入两个正整数A和B。 输出描述&#xff1a; 输出A和B…...

JVM - 垃圾收集器

目录 垃圾收集器 串行垃圾收集器 并行垃圾收集器 什么是 吞吐量优先 什么是 响应时间优先 &#xff1f; CMS&#xff08;并发&#xff09;垃圾收集器 G1 垃圾收集器 垃圾收集器 垃圾收集器大概可以分为&#xff1a; 串行垃圾收集器并行垃圾收集器CMS&#xff08;并发&a…...

华为数通方向HCIP-DataCom H12-821题库(单选题:21-40)

第21题 在广播类型网络中,DIS默认发送Hello时间间隔为多少? A、10s B、3.3s C、5S D、40s 答案&#xff1a;B 解析&#xff1a; 在广播环境中,DIS 发送 hello 报文的周期更加的短,是普通ISIS路由器的1/3,普通ISIS路由器发送hello的时间为10s,所以DIS发送hello的周期是3.3s …...

Springboot+mybaits-plus+h2集成产生的一些问题(not found tables)

一、问题描述 org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "EP_MAPPING" not found (this database is empty);大概就是说在引入mybatis-plus的依赖后&#xff0c;找不到数据库找不到表的问题。 排查方向&#xff1a;在引入mybatish2时&#xff0c;是可以正常…...

【C#学习笔记】C#特性的继承,封装,多态

文章目录 封装访问修饰符静态类和静态方法静态构造函数 继承继承原则sealed修饰符里氏替换原则继承中的构造函数 多态接口接口的实例化 抽象类和抽象方法抽象类和接口的异同 虚方法同名方法new覆盖的父类方法继承的同名方法 运行时的多态性编译时的多态性 照理继承封装多态应该…...

常用的电参数

电参数根据电流的特点可以分为直流电参数和交流电参数&#xff0c;在电参数中有些是可以通过电参数表测得&#xff0c;有些参数则为通过测得的参数计算而来。 一、电参数 1.1 直接可测电参数 ——瞬时电压值 ——瞬时电流值 n——采样点数 f——频率 time——时间 其中&…...

Rabbitmq的应用场景

Rabbitmq的应用场景 一、异步处理 场景说明&#xff1a;用户注册后&#xff0c;需要发注册邮件和注册短信,传统的做法有两种 1.串行的方式 2.并行的方式 ​​串行方式​​: 将注册信息写入数据库后,发送注册邮件,再发送注册短信,以上三个任务全部完成后才返回给客户端。 这有…...

【CSS动画08--流光按钮】

CSS动画08--流光按钮 介绍HTMLCSS 介绍 流光button HTML <!DOCTYPE html> <html><head><meta http-equiv"content-type" content"text/html; charsetutf-8"><meta name"viewport" content"widthdevice-width,i…...

计算机视觉:比SAM快50倍的分割一切视觉模型FastSAM

目录 引言 1 FastSAM介绍 1.1 FastSAM诞生 1.2 模型算法 1.3 实验结果 2 FastSAM运行环境构建 2.1 conda环境构建 2.2 运行环境安装 2.3 模型下载 3 FastSAM运行 3.1 命令行运行 3.1.1 Everything mode 3.1.2 Text prompt 3.1.3 Box prompt (xywh) 3.1.4 Points p…...

Web AR开发全指南:从技术原理到实战应用

Web AR开发全指南&#xff1a;从技术原理到实战应用 【免费下载链接】AR.js Image tracking, Location Based AR, Marker tracking. All on the Web. 项目地址: https://gitcode.com/gh_mirrors/arj/AR.js 随着增强现实技术的发展&#xff0c;Web AR开发已成为前端领域的…...

Step3-VL-10B-Base与C语言基础教程:嵌入式开发入门

Step3-VL-10B-Base与C语言基础教程&#xff1a;嵌入式开发入门 1. 引言 想学嵌入式开发但不知道从哪开始&#xff1f;很多新手卡在第一步&#xff1a;既要学C语言&#xff0c;又要懂硬件&#xff0c;感觉门槛很高。其实没那么复杂&#xff0c;用对方法就能快速上手。 这个教…...

2026考公全攻略:在校生如何选岗、如何备考、如何上岸一次讲清

很多在校生最近都在问&#xff1a;现在开始准备公务员考试&#xff0c;还来得及吗先说结论&#xff1a;来得及&#xff0c;但前提是你要走对路径这几年考公环境已经发生明显变化报考人数持续增加岗位信息越来越透明单纯刷题已经不再有效考公正在从拼努力&#xff0c;变成拼策略…...

RAG深度解析一:从参数化知识到检索增强的范式重构

【内容定位】深度技术原理【文章日期】2026-03-27【场景引入】进入2026年3月&#xff0c;一场围绕大语言模型“可信性”的讨论在技术社区再度升温。开发者们早已不再争论模型参数量&#xff0c;而是转向一个更实际的问题&#xff1a;如何让动辄千亿参数的大模型&#xff0c;在回…...

三三复制系统模式介绍

三三复制系统模式介绍&#xff1a;从底层逻辑到合规落地在社交电商与团队裂变领域&#xff0c;三三复制系统凭借其低门槛、高稳定性的特点&#xff0c;成为企业实现用户快速增长与业绩倍增的重要工具。不同于传统多级分销的复杂层级&#xff0c;三三复制系统以“三”为核心基数…...

告别加班!3个Word神技巧,文档处理快人一步

如影随形地跟着那堆积如山的文档&#xff0c;像学生名单&#xff0c;课程表&#xff0c;教学计划&#xff0c;家长通知等等&#xff0c;这些重复性工作着实耗费了大量精力。事实上&#xff0c;Word当中蕴含着好些能够让你达成事半功倍效果的技巧&#xff0c;一旦将它们掌握住&a…...

Python并发革命进行时:GIL移除后你必须掌握的5种内存序模型(x86/ARM/RISC-V实测对比)

第一章&#xff1a;Python无锁GIL环境下的并发模型架构总览传统CPython解释器受全局解释器锁&#xff08;GIL&#xff09;制约&#xff0c;无法真正实现多线程CPU并行。而“无锁GIL环境”并非指移除GIL本身&#xff0c;而是指在GIL被主动释放、绕过或由替代运行时&#xff08;如…...

3大核心步骤打造专属翻译引擎:Zotero PDF Translate高级扩展指南

3大核心步骤打造专属翻译引擎&#xff1a;Zotero PDF Translate高级扩展指南 【免费下载链接】zotero-pdf-translate 支持将PDF、EPub、网页内容、元数据、注释和笔记翻译为目标语言&#xff0c;并且兼容20多种翻译服务。 项目地址: https://gitcode.com/gh_mirrors/zo/zoter…...

【大模型工程实践③】RAG 基础架构与完整实现

【大模型工程实践③】RAG 基础架构与完整实现:从0到1跑通 作者:AI学习者 | 来源:大模型工程实践学习系列 | 更新:2026年3月 【理论要点速览】 学习本篇前,建议先掌握以下核心理论(点击跳转): ① 为什么需要RAG? ② RAG vs Fine-tuning vs Long Context的决策框架 ③ …...

别再让电费偷偷溜走!用智能时间开关改造家里的热水器和空调(附保姆级选购指南)

别再让电费偷偷溜走&#xff01;用智能时间开关改造家里的热水器和空调&#xff08;附保姆级选购指南&#xff09; 每到月底收到电费账单时&#xff0c;那种"钱不知不觉就溜走"的感觉总是让人心疼。特别是热水器和空调这两大"电老虎"&#xff0c;它们往往…...