Java Redis多限流
Java Redis多限流
在Java中实现Redis多限流通常涉及使用Redis的某些特性,如INCR
、EXPIRE
、Lua
脚本或者更高级的Redis数据结构如Redis Bitmaps
、Redis Streams
结合Redis Pub/Sub
,或者使用Redis的第三方库如Redis Rate Limiter
(基于Lua脚本或Redis自身功能实现)。然而,为了直接和易于实现,这里我们将使用Jedis
库(Java的Redis客户端)结合Redis的INCR
和EXPIRE
命令来模拟一个基本的分布式多限流系统。
1. 使用Jedis
库结合Redis的INCR
和EXPIRE
命令模拟一个基本的分布式多限流系统
1.1 准备工作
(1)Redis安装:确保Redis服务在我们的开发环境中已经安装并运行。
(2)Jedis依赖:在我们的Java项目中添加Jedis依赖。如果我们使用Maven,可以在pom.xml
中添加以下依赖:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>最新版本</version>
</dependency>
请替换最新版本
为当前Jedis的最新版本。
1.2 实现代码
下面是一个简单的Java程序,使用Jedis和Redis的INCR
和EXPIRE
命令来实现基本的限流功能。这里我们假设每个用户(或API端点)都有自己的限流键。
import redis.clients.jedis.Jedis;
public class RedisRateLimiter {
private static final String REDIS_HOST = "localhost";
private static final int REDIS_PORT = 6379;
private static final long LIMIT = 10; // 每分钟最多请求次数
private static final long TIME_INTERVAL = 60; // 时间间隔,单位为秒
public static void main(String[] args) {
try (Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT)) {
String userId = "user123"; // 假设这是用户ID或API端点标识符
String key = "rate_limit:" + userId;
// 尝试获取访问权限
if (tryAcquire(jedis, key, LIMIT, TIME_INTERVAL)) {
System.out.println("请求成功,未超过限流限制");
// 在这里处理你的请求
} else {
System.out.println("请求失败,超过限流限制");
// 处理限流情况,如返回错误码或等待一段时间后重试
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 尝试获取访问权限
*
* @param jedis Redis客户端
* @param key 限流键
* @param limit 限制次数
* @param timeInterval 时间间隔(秒)
* @return 是否获取成功
*/
public static boolean tryAcquire(Jedis jedis, String key, long limit, long timeInterval) {
String result = jedis.watch(key);
if (result != null && result.equalsIgnoreCase("OK")) {
String counter = jedis.get(key);
if (counter == null || Long.parseLong(counter) < limit) {
// 使用事务,先incr后expire,确保原子性
Transaction transaction = jedis.multi();
transaction.incr(key);
transaction.expire(key, timeInterval);
List<Object> results = transaction.exec();
if (results != null && results.size() == 2 && "OK".equals(results.get(0).toString()) && "1".equals(results.get(1).toString())) {
return true;
}
}
// 取消watch
jedis.unwatch();
}
// 如果key不存在或超过限制,则直接返回false
return false;
}
}
注意:上述代码中的tryAcquire
方法使用了Redis的WATCH
和MULTI
/EXEC
命令来尝试实现操作的原子性,但这种方法在Redis集群环境中可能不是最佳实践,因为WATCH
/UNWATCH
是基于单个Redis实例的。在分布式环境中,我们可能需要考虑使用Redis的Lua脚本来确保操作的原子性,或者使用专门的限流库。
此外,上述代码在并发极高的情况下可能不是最优的,因为它依赖于Redis的WATCH
机制来避免竞态条件,这在性能上可能不是最高效的。对于高并发的限流需求,我们可能需要考虑使用更专业的限流算法或库,如令牌桶(Token Bucket)或漏桶(Leaky Bucket)。
2. 基于Jedis和Lua脚本的限流示例
在Java中使用Redis进行多限流时,我们通常会选择更健壮和高效的方案,比如使用Redis的Lua脚本来保证操作的原子性,或者使用现成的Redis限流库。不过,为了保持示例的简洁性和易于理解,我将提供一个基于Jedis和Lua脚本的限流示例。
在这个示例中,我们将使用Redis的Lua脚本来实现一个简单的令牌桶限流算法。Lua脚本可以在Redis服务器上以原子方式执行多个命令,这对于限流等需要原子操作的场景非常有用。
2.1 Java Redis多限流(Lua脚本示例)
首先,我们需要有一个Redis服务器运行在我们的环境中,并且我们的Java项目中已经添加了Jedis依赖。
2.1.1 Lua脚本
以下是一个简单的Lua脚本,用于实现令牌桶的限流逻辑。这个脚本会检查当前桶中的令牌数,如果足够则减少令牌数并返回成功,否则返回失败。
-- Lua脚本:token_bucket_limit.lua
-- KEYS[1] 是令牌桶的key
-- ARGV[1] 是请求的令牌数
-- ARGV[2] 是桶的容量
-- ARGV[3] 是每秒添加的令牌数
-- ARGV[4] 是时间间隔(秒),用于计算当前时间应该有多少令牌
local key = KEYS[1]
local request = tonumber(ARGV[1])
local capacity = tonumber(ARGV[2])
local rate = tonumber(ARGV[3])
local interval = tonumber(ARGV[4])
-- 获取当前时间戳
local current_time = tonumber(redis.call("TIME")[1])
-- 尝试获取桶的上次更新时间和当前令牌数
local last_updated_time = redis.call("GET", key .. "_last_updated_time")
local current_tokens = redis.call("GET", key .. "_tokens")
if last_updated_time == false then
-- 如果桶不存在,则初始化桶
redis.call("SET", key .. "_last_updated_time", current_time)
redis.call("SET", key .. "_tokens", capacity)
current_tokens = capacity
last_updated_time = current_time
end
-- 计算自上次更新以来经过的时间
local delta = current_time - last_updated_time
-- 计算这段时间内应该添加的令牌数
local tokens_to_add = math.floor(delta * rate)
-- 确保令牌数不会超过容量
if current_tokens + tokens_to_add > capacity then
tokens_to_add = capacity - current_tokens
end
-- 更新令牌数和更新时间
current_tokens = current_tokens + tokens_to_add
redis.call("SET", key .. "_tokens", current_tokens)
redis.call("SET", key .. "_last_updated_time", current_time)
-- 检查是否有足够的令牌
if current_tokens >= request then
-- 如果有足够的令牌,则减少令牌数
redis.call("DECRBY", key .. "_tokens", request)
return 1 -- 返回成功
else
return 0 -- 返回失败
end
2.1.2 Java代码
接下来是Java中使用Jedis调用上述Lua脚本的代码。
import redis.clients.jedis.Jedis;
public class RedisRateLimiter {
private static final String REDIS_HOST = "localhost";
private static final int REDIS_PORT = 6379;
private static final String LUA_SCRIPT = "path/to/your/token_bucket_limit.lua"; // Lua脚本的路径(或者你可以直接加载脚本内容)
public static void main(String[] args) {
try (Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT)) {
String key = "rate_limit_bucket:user123";
int requestTokens = 1;
int capacity = 10;
double rate = 1.0; // 每秒添加1个令牌
int interval = 60; // 时间间隔为60秒
// 加载Lua脚本(这里假设你已经有了Lua脚本的内容或路径)
// 实际应用中,你可能需要从文件加载Lua脚本内容
String scriptContent = // ... 从文件或其他地方加载Lua脚本内容
// 注册Lua脚本到Redis
String sha1 = jedis.scriptLoad(scriptContent);
// 执行Lua脚本
Object result = jedis.evalsha(sha1, 1, key, String.valueOf(requestTokens), String.valueOf(capacity), String.
在之前的代码中,我们留下了加载Lua脚本和执行它的部分未完成。以下是完整的Java代码示例,包括如何加载Lua脚本并执行它以进行限流检查。
2.1.3 完整的Java代码示例
import redis.clients.jedis.Jedis;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class RedisRateLimiter {
private static final String REDIS_HOST = "localhost";
private static final int REDIS_PORT = 6379;
private static final String LUA_SCRIPT_PATH = "path/to/your/token_bucket_limit.lua"; // Lua脚本的文件路径
public static void main(String[] args) {
try (Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT)) {
String key = "rate_limit_bucket:user123";
int requestTokens = 1;
int capacity = 10;
double rate = 1.0; // 每秒添加1个令牌
int interval = 1; // 时间间隔为1秒(这里仅为示例,实际中可能更长)
// 加载Lua脚本
String luaScript = loadLuaScript(LUA_SCRIPT_PATH);
// 注册Lua脚本到Redis(获取SHA1哈希值)
String sha1 = jedis.scriptLoad(luaScript);
// 执行Lua脚本进行限流检查
// KEYS[1] 是 key, ARGV 是其他参数
Long result = (Long) jedis.evalsha(sha1, 1, key, String.valueOf(requestTokens), String.valueOf(capacity), String.valueOf(rate), String.valueOf(interval));
if (result == 1L) {
System.out.println("请求成功,有足够的令牌。");
// 处理请求...
} else {
System.out.println("请求失败,令牌不足。");
// 拒绝请求或进行其他处理...
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 从文件加载Lua脚本内容
private static String loadLuaScript(String filePath) throws IOException {
StringBuilder sb = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
}
return sb.toString();
}
}
2.1.4 注意事项
(1)Lua脚本路径:确保LUA_SCRIPT_PATH
变量指向正确的Lua脚本文件路径。
(2)错误处理:在实际应用中,我们可能需要添加更详细的错误处理逻辑,比如处理Redis连接失败、Lua脚本加载失败等情况。
(3)性能考虑:虽然Lua脚本在Redis中执行是高效的,但在高并发场景下,频繁的脚本执行仍然可能对Redis服务器造成压力。我们可能需要考虑使用Redis的内置限流功能(如Redis 6.0及以上版本的Redis Streams
和Redis Bloom Filters
),或者通过增加Redis实例、使用集群等方式来扩展我们的系统。
(4)Lua脚本的复杂性:随着业务逻辑的复杂化,Lua脚本可能会变得难以维护。在这种情况下,我们可能需要考虑将部分逻辑移到Java代码中,或者通过其他方式(如使用Redis的模块)来扩展Redis的功能。
(5)时间同步:Lua脚本中的时间计算依赖于Redis服务器的时间。确保Redis服务器的时间与我们的应用服务器时间保持同步,以避免因时间差异导致的问题。
3. Redis多限流
Redis作为一种高性能的键值对存储系统,支持多种数据结构和操作,非常适合用于实现限流算法。以下是关于Redis多限流的一些详细信息:
3.1 Redis限流算法概述
Redis实现限流主要依赖于其原子操作、高速缓存和丰富的数据结构(如字符串、列表、集合、有序集合等)。常见的限流算法包括令牌桶算法(Token Bucket)、漏桶算法(Leaky Bucket)以及基于计数器的简单限流算法。
(1)令牌桶算法:
- 初始化一个固定容量的令牌桶,以固定速率向桶中添加令牌。
- 每个请求尝试从桶中获取一个令牌,如果成功则处理请求,否则拒绝或等待。
- 令牌桶的容量和添加速率决定了系统的最大处理能力和平均处理速率。
(2)漏桶算法:
- 请求被放入一个桶中,桶以恒定速率漏出请求。
- 如果桶满,则新到的请求被拒绝或等待。
- 漏桶算法对突发流量有很好的抑制作用,但可能无法高效利用资源。
(3)计数器算法:
- 在每个时间窗口内记录请求次数,达到阈值时拒绝新请求。
- 时间窗口结束后计数器重置。
- 实现简单但可能存在临界问题,限流不准确。
3.2 Redis多限流实现方式
在分布式系统中,Redis可以实现全局的限流,支持多种限流策略的组合使用。
(1)使用Redis数据结构:
- 字符串:记录当前时间窗口内的请求次数或令牌数。
- 列表:记录请求的时间戳,用于滑动窗口算法。
- 有序集合(ZSet):记录请求的时间戳和唯一标识,用于精确控制时间窗口内的请求数。
- 哈希表:存储令牌桶的状态,包括当前令牌数和上次更新时间。
(2)Lua脚本:
- 利用Redis的Lua脚本功能,可以编写复杂的限流逻辑,并通过原子操作执行,确保并发安全性。
- Lua脚本可以在Redis服务器端执行,减少网络传输和延迟。
(3)分布式锁:
- 在高并发场景下,为了防止多个实例同时修改同一个限流键,可以使用Redis的分布式锁机制。
- 但需要注意分布式锁的性能和可用性问题。
3.3 Redis多限流实际应用
在实际应用中,Redis多限流可以用于多种场景,如API接口限流、用户行为限流、系统资源访问限流等。通过组合不同的限流算法和数据结构,可以实现复杂的限流策略,满足不同业务需求。
例如,一个电商平台可能需要对用户登录、商品浏览、下单等行为进行限流。对于登录行为,可以使用令牌桶算法限制用户登录频率;对于商品浏览行为,可以使用漏桶算法控制突发流量;对于下单行为,则可能需要结合用户身份、订单金额等多个因素进行综合限流。
3.4 注意事项
(1)性能问题:在高并发场景下,Redis的性能可能会成为瓶颈。需要合理设计限流策略和Redis的部署架构,确保系统稳定运行。
(2)持久化问题:Redis是内存数据库,数据丢失风险较高。在需要持久化限流数据的场景下,需要考虑Redis的持久化机制。
(3)分布式问题:在分布式系统中,需要确保Redis集群的稳定性和可用性,以及限流数据的一致性和准确性。
综上所述,Redis多限流是一种强大而灵活的技术手段,通过合理的策略设计和实现方式,可以有效地保护系统资源和服务质量。
Java Redis多限流的更多相关文章
- 基于Redis的限流系统的设计
本文讲述基于Redis的限流系统的设计,主要会谈及限流系统中限流策略这个功能的设计:在实现方面,算法使用的是令牌桶算法来,访问Redis使用lua脚本. 1.概念 In computer netw ...
- 深入Redis漏斗限流
漏斗限流是最常用的限流方法之一,漏斗流水的速率大于灌水的速率,漏斗就永远装不满,反之水就会溢出. 所以漏斗的剩余空间就代表当前行为可以持续进行的数量,水流出的速率代表系统允许该行为的最大频率. imp ...
- Java分布式IP限流和防止恶意IP攻击方案
前言 限流是分布式系统设计中经常提到的概念,在某些要求不严格的场景下,使用Guava RateLimiter就可以满足.但是Guava RateLimiter只能应用于单进程,多进程间协同控制便无能为 ...
- Envoy实现.NET架构的网关(五)集成Redis实现限流
什么是限流 限流即限制并发量,限制某一段时间只有指定数量的请求进入后台服务器,遇到流量高峰期或者流量突增时,把流量速率限制在系统所能接受的合理范围之内,不至于让系统被高流量击垮.而Envoy可以通过e ...
- 限流(三)Redis + lua分布式限流
一.简介 1)分布式限流 如果是单实例项目,我们使用Guava这样的轻便又高性能的堆缓存来处理限流.但是当项目发展为多实例了以后呢?这时候我们就需要采用分布式限流的方式,分布式限流可以以redis + ...
- 分布式限流组件-基于Redis的注解支持的Ratelimiter
原文:https://juejin.im/entry/5bd491c85188255ac2629bef?utm_source=coffeephp.com 在分布式领域,我们难免会遇到并发量突增,对后端 ...
- Redis除了做缓存--Redis做消息队列/Redis做分布式锁/Redis做接口限流
1.用Redis实现消息队列 用命令lpush入队,rpop出队 Long size = jedis.lpush("QueueName", message);//返回存放的数据条数 ...
- Redis解读(4):Redis中HyperLongLog、布隆过滤器、限流、Geo、及Scan等进阶应用
Redis中的HyperLogLog 一般我们评估一个网站的访问量,有几个主要的参数: pv,Page View,网页的浏览量 uv,User View,访问的用户 一般来说,pv 或者 uv 的统计 ...
- 库存秒杀问题-redis解决方案- 接口限流
<?php/** * Created by PhpStorm. * redis 销量超卖秒杀解决方案 * redis 文档:http://doc.redisfans.com/ * ab -n 1 ...
- 【分布式架构】--- 基于Redis组件的特性,实现一个分布式限流
分布式---基于Redis进行接口IP限流 场景 为了防止我们的接口被人恶意访问,比如有人通过JMeter工具频繁访问我们的接口,导致接口响应变慢甚至崩溃,所以我们需要对一些特定的接口进行IP限流,即 ...
随机推荐
- dotnet 命令行工具解决方案 PomeloCli
PomeloCli 是什么 中文版 English version 我们已经有相当多的命令行工具实现或解析类库,PomeloCli 并不是替代版本,它基于 Nate McMaster 的杰出工作 Co ...
- salesforce零基础学习(一百三十九)Admin篇之Begins/Contains/Starts With 是否区分大小写
本篇参考: https://help.salesforce.com/s/articleView?id=sf.customize_functions_begins.htm&type=5 http ...
- linux curl命令的重要用法:发送GET/POST请求,获取网页内容
curl是一个利用URL规则在命令行下工作的文件传输工具,可以说是一款很强大的http命令行工具.它支持文件的上传和下载,是综合 传输工具,但按传统,习惯称url为下载工具. #使用curl发送GET ...
- 解决linux家目录模板文件被删之后显示不正常的问题
想必经常使用linux的小伙伴都遇到过下面这种情况: 下面讲解遇到这种问题之后如何解决: [root@node5 ~]# rm -rf /home/elk/.bash* [root@node5 ~]# ...
- 鸿蒙HarmonyOS实战-Stage模型(信息传递载体Want)
前言 应用中的信息传递是为了实现各种功能和交互.信息传递可以帮助用户和应用之间进行有效的沟通和交流.通过信息传递,应用可以向用户传递重要的消息.通知和提示,以提供及时的反馈和指导.同时,用户也可以通过 ...
- Debian中配置NIS:用户账号管理
1.添加指定gid的组 groupadd -g 1001 upload # 添加了一个指定gid为1001的upload用户 2.添加指定uid的用户,并加入到指定组 useradd -u 1001 ...
- Linux命令行优化,历史记录优化
#命令行优化: echo "export PS1='\[\033[01;31m\]\u\[\033[00m\]@\[\033[01;32m\]\h\[\033[00m\][\[\033[01 ...
- manjaro安装错误的显卡驱动导致无法开机(解决办法)
手欠的我 最近发觉我的manjaro有时候开机会卡死在clean ....这个地方,然后我去看了下系统的日至,发现是nvidia的一个报错,我也不太懂什么意思 然后我就自己动手想改改显卡驱动(从pri ...
- Flashcat与出行科技企业一起实践多云可观测
当前架构 某出行科技企业从单个公有云往多云转型,依托于国内领先的公有云提供商,采用多云架构,在可用性.弹性.成本.供应商依赖方面,拥有了显著的优势. 相应的,多云架构也给技术团队带来了一定的复杂度和技 ...
- Cursor是什么?基于ChatGPT代码编辑器的cursor如何使用?VS Code如何迁移到Cursor的步骤
Cursor是什么 Cursor 是一个基于 Visual Studio Code(VS Code)技术构建的高级代码编辑器,专为提高编程效率并更深度地整合 AI 功能而设计.它不仅继承了 VS Co ...