基于 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文件为例,实现点击按钮下载文件,其中icon结构如下: const DownloadSvg (props) > {function download(downfile) {const tmpLink document.createElement("a");const objectUrl URL.createObjectURL(downfi…...
【STM32】FreeRTOS软件定时器学习
软件定时器 FreeRTOS提供了现成的软件定时器功能,可以一定程度上替代硬件定时器,但精度不高。 实验:创建一个任务,两个定时器,按键开启定时器,一个500ms打印一次,一个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
前言 如下所示,建议使用 Dockerfile Maven 插件,但该插件也停止维护更新了。因此先暂时使用docker-maven-plugin插件。 一、开启Docker服务器的远程访问 1.1 开启2375远程访问 默认的dokcer是不支持远程访问的,需要加点配置,开…...
YOLO目标检测——动漫头像数据集下载分享
动漫头像数据集是用于研究和分析动漫头像相关问题的数据集,它包含了大量的动漫风格的头像图像。动漫头像是指以动漫风格绘制的虚构人物的头像图像,常见于动画、漫画、游戏等媒体。 数据集点击下载:YOLO动漫头像数据集50800图片.rar...
学习Vue:Vue3 VS Vue2
Vue 3作为Vue.js的最新版本,带来了一系列令人激动的新特性和改进,让开发者们在构建现代Web应用时体验更加顺畅和高效。本文将全面介绍Vue 3相对于Vue 2的改进,重点解释Composition API的使用,以及新引入的Teleport和Suspense等特性…...
1.2亿成都市城市安全风险综合监测预警平台建设项目
导读:原文《1.2亿!成都市城市安全风险综合监测预警平台建设项目WORD》(获取来源见文尾),本文精选其中精华及架构部分,逻辑清晰、内容完整,为快速形成售前方案提供参考。 部分页面: …...
《树莓派4B家庭服务器搭建指南》第二十期:在树莓派运行rsnapshot, 实现对服务器数据低成本增量本地备份
title: 020《树莓派4B家庭服务器搭建指南》第二十期:在树莓派运行rsnapshot, 实现对服务器数据低成本增量本地备份 我的天翼云服务器有/opt 和 /usr/share/nginx两个目录, 用来存储网站的内容, 数据无价, 为了避免珍贵的数据丢失,我决定使用树莓派运行 …...
大数据 算法
什么是大数据 大数据是指数据量巨大、类型繁多、处理速度快的数据集合。这些数据集合通常包括结构化数据(如数据库中的表格数据)、半结构化数据(如XML文件)和非结构化数据(如文本、音频和视频文件)。大数据…...
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整除的最小的正整数值,设计一个算法,求输入A和B的最小公倍数。 数据范围:1≤a,b≤100000 1≤a,b≤100000 输入描述: 输入两个正整数A和B。 输出描述: 输出A和B…...
JVM - 垃圾收集器
目录 垃圾收集器 串行垃圾收集器 并行垃圾收集器 什么是 吞吐量优先 什么是 响应时间优先 ? CMS(并发)垃圾收集器 G1 垃圾收集器 垃圾收集器 垃圾收集器大概可以分为: 串行垃圾收集器并行垃圾收集器CMS(并发&a…...
华为数通方向HCIP-DataCom H12-821题库(单选题:21-40)
第21题 在广播类型网络中,DIS默认发送Hello时间间隔为多少? A、10s B、3.3s C、5S D、40s 答案:B 解析: 在广播环境中,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的依赖后,找不到数据库找不到表的问题。 排查方向:在引入mybatish2时,是可以正常…...
【C#学习笔记】C#特性的继承,封装,多态
文章目录 封装访问修饰符静态类和静态方法静态构造函数 继承继承原则sealed修饰符里氏替换原则继承中的构造函数 多态接口接口的实例化 抽象类和抽象方法抽象类和接口的异同 虚方法同名方法new覆盖的父类方法继承的同名方法 运行时的多态性编译时的多态性 照理继承封装多态应该…...
常用的电参数
电参数根据电流的特点可以分为直流电参数和交流电参数,在电参数中有些是可以通过电参数表测得,有些参数则为通过测得的参数计算而来。 一、电参数 1.1 直接可测电参数 ——瞬时电压值 ——瞬时电流值 n——采样点数 f——频率 time——时间 其中&…...
Rabbitmq的应用场景
Rabbitmq的应用场景 一、异步处理 场景说明:用户注册后,需要发注册邮件和注册短信,传统的做法有两种 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…...
网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...
搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...
宇树科技,改名了!
提到国内具身智能和机器人领域的代表企业,那宇树科技(Unitree)必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...
jdbc查询mysql数据库时,出现id顺序错误的情况
我在repository中的查询语句如下所示,即传入一个List<intager>的数据,返回这些id的问题列表。但是由于数据库查询时ID列表的顺序与预期不一致,会导致返回的id是从小到大排列的,但我不希望这样。 Query("SELECT NEW com…...
6.9-QT模拟计算器
源码: 头文件: widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QMouseEvent>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);…...
