Java Redis多限流

[复制链接]
发表于 2026-1-16 06:41:34 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
在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中添加以下依靠:

  1. <dependency>  
  2.     <groupId>redis.clients</groupId>  
  3.     <artifactId>jedis</artifactId>  
  4.     <version>最新版本</version>  
  5. </dependency>
复制代码

请更换最新版本为当前Jedis的最新版本。

1.2 实今世码


下面是一个简朴的Java步伐,利用Jedis和Redis的INCR和EXPIRE下令来实现根本的限流功能。这里我们假设每个用户(或API端点)都有本身的限流键。

  1. import redis.clients.jedis.Jedis;  
  2.   
  3. public class RedisRateLimiter {  
  4.   
  5.     private static final String REDIS_HOST = "localhost";  
  6.     private static final int REDIS_PORT = 6379;  
  7.     private static final long LIMIT = 10; // 每分钟最多请求次数  
  8.     private static final long TIME_INTERVAL = 60; // 时间间隔,单位为秒  
  9.   
  10.     public static void main(String[] args) {  
  11.         try (Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT)) {  
  12.             String userId = "user123"; // 假设这是用户ID或API端点标识符  
  13.             String key = "rate_limit:" + userId;  
  14.   
  15.             // 尝试获取访问权限  
  16.             if (tryAcquire(jedis, key, LIMIT, TIME_INTERVAL)) {  
  17.                 System.out.println("请求成功,未超过限流限制");  
  18.                 // 在这里处理你的请求  
  19.   
  20.             } else {  
  21.                 System.out.println("请求失败,超过限流限制");  
  22.                 // 处理限流情况,如返回错误码或等待一段时间后重试  
  23.             }  
  24.   
  25.         } catch (Exception e) {  
  26.             e.printStackTrace();  
  27.         }  
  28.     }  
  29.   
  30.     /**  
  31.      * 尝试获取访问权限  
  32.      *  
  33.      * @param jedis Redis客户端  
  34.      * @param key   限流键  
  35.      * @param limit 限制次数  
  36.      * @param timeInterval 时间间隔(秒)  
  37.      * @return 是否获取成功  
  38.      */  
  39.     public static boolean tryAcquire(Jedis jedis, String key, long limit, long timeInterval) {  
  40.         String result = jedis.watch(key);  
  41.         if (result != null && result.equalsIgnoreCase("OK")) {  
  42.             String counter = jedis.get(key);  
  43.             if (counter == null || Long.parseLong(counter) < limit) {  
  44.                 // 使用事务,先incr后expire,确保原子性  
  45.                 Transaction transaction = jedis.multi();  
  46.                 transaction.incr(key);  
  47.                 transaction.expire(key, timeInterval);  
  48.                 List<Object> results = transaction.exec();  
  49.                 if (results != null && results.size() == 2 && "OK".equals(results.get(0).toString()) && "1".equals(results.get(1).toString())) {  
  50.                     return true;  
  51.                 }  
  52.             }  
  53.             // 取消watch  
  54.             jedis.unwatch();  
  55.         }  
  56.         // 如果key不存在或超过限制,则直接返回false  
  57.         return false;  
  58.     }  
  59. }
复制代码

注意:上述代码中的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脚本,用于实现令牌桶的限流逻辑。这个脚本会查抄当前桶中的令牌数,如果充足则淘汰令牌数并返回乐成,否则返回失败。

  1. -- Lua脚本:token_bucket_limit.lua  
  2. -- KEYS[1] 是令牌桶的key  
  3. -- ARGV[1] 是请求的令牌数  
  4. -- ARGV[2] 是桶的容量  
  5. -- ARGV[3] 是每秒添加的令牌数  
  6. -- ARGV[4] 是时间间隔(秒),用于计算当前时间应该有多少令牌  
  7.   
  8. local key = KEYS[1]  
  9. local request = tonumber(ARGV[1])  
  10. local capacity = tonumber(ARGV[2])  
  11. local rate = tonumber(ARGV[3])  
  12. local interval = tonumber(ARGV[4])  
  13.   
  14. -- 获取当前时间戳  
  15. local current_time = tonumber(redis.call("TIME")[1])  
  16.   
  17. -- 尝试获取桶的上次更新时间和当前令牌数  
  18. local last_updated_time = redis.call("GET", key .. "_last_updated_time")  
  19. local current_tokens = redis.call("GET", key .. "_tokens")  
  20.   
  21. if last_updated_time == false then  
  22.     -- 如果桶不存在,则初始化桶  
  23.     redis.call("SET", key .. "_last_updated_time", current_time)  
  24.     redis.call("SET", key .. "_tokens", capacity)  
  25.     current_tokens = capacity  
  26.     last_updated_time = current_time  
  27. end  
  28.   
  29. -- 计算自上次更新以来经过的时间  
  30. local delta = current_time - last_updated_time  
  31.   
  32. -- 计算这段时间内应该添加的令牌数  
  33. local tokens_to_add = math.floor(delta * rate)  
  34.   
  35. -- 确保令牌数不会超过容量  
  36. if current_tokens + tokens_to_add > capacity then  
  37.     tokens_to_add = capacity - current_tokens  
  38. end  
  39.   
  40. -- 更新令牌数和更新时间  
  41. current_tokens = current_tokens + tokens_to_add  
  42. redis.call("SET", key .. "_tokens", current_tokens)  
  43. redis.call("SET", key .. "_last_updated_time", current_time)  
  44.   
  45. -- 检查是否有足够的令牌  
  46. if current_tokens >= request then  
  47.     -- 如果有足够的令牌,则减少令牌数  
  48.     redis.call("DECRBY", key .. "_tokens", request)  
  49.     return 1  -- 返回成功  
  50. else  
  51.     return 0  -- 返回失败  
  52. end
复制代码

2.1.2 Java代码


接下来是Java中利用Jedis调用上述Lua脚本的代码。

  1. import redis.clients.jedis.Jedis;  
  2.   
  3. public class RedisRateLimiter {  
  4.   
  5.     private static final String REDIS_HOST = "localhost";  
  6.     private static final int REDIS_PORT = 6379;  
  7.     private static final String LUA_SCRIPT = "path/to/your/token_bucket_limit.lua"; // Lua脚本的路径(或者你可以直接加载脚本内容)  
  8.   
  9.     public static void main(String[] args) {  
  10.         try (Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT)) {  
  11.             String key = "rate_limit_bucket:user123";  
  12.             int requestTokens = 1;  
  13.             int capacity = 10;  
  14.             double rate = 1.0; // 每秒添加1个令牌  
  15.             int interval = 60; // 时间间隔为60秒  
  16.   
  17.             // 加载Lua脚本(这里假设你已经有了Lua脚本的内容或路径)  
  18.             // 实际应用中,你可能需要从文件加载Lua脚本内容  
  19.             String scriptContent = // ... 从文件或其他地方加载Lua脚本内容  
  20.   
  21.             // 注册Lua脚本到Redis  
  22.             String sha1 = jedis.scriptLoad(scriptContent);  
  23.   
  24.             // 执行Lua脚本  
  25.             Object result = jedis.evalsha(sha1, 1, key, String.valueOf(requestTokens), String.valueOf(capacity), String.
复制代码

在之前的代码中,我们留下了加载Lua脚本和实行它的部分未完成。以下是完备的Java代码示例,包罗怎样加载Lua脚本并实行它以举行限流查抄。

2.1.3 完备的Java代码示例


  1. import redis.clients.jedis.Jedis;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.FileReader;  
  5. import java.io.IOException;  
  6.   
  7. public class RedisRateLimiter {  
  8.   
  9.     private static final String REDIS_HOST = "localhost";  
  10.     private static final int REDIS_PORT = 6379;  
  11.     private static final String LUA_SCRIPT_PATH = "path/to/your/token_bucket_limit.lua"; // Lua脚本的文件路径  
  12.   
  13.     public static void main(String[] args) {  
  14.         try (Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT)) {  
  15.             String key = "rate_limit_bucket:user123";  
  16.             int requestTokens = 1;  
  17.             int capacity = 10;  
  18.             double rate = 1.0; // 每秒添加1个令牌  
  19.             int interval = 1; // 时间间隔为1秒(这里仅为示例,实际中可能更长)  
  20.   
  21.             // 加载Lua脚本  
  22.             String luaScript = loadLuaScript(LUA_SCRIPT_PATH);  
  23.   
  24.             // 注册Lua脚本到Redis(获取SHA1哈希值)  
  25.             String sha1 = jedis.scriptLoad(luaScript);  
  26.   
  27.             // 执行Lua脚本进行限流检查  
  28.             // KEYS[1] 是 key, ARGV 是其他参数  
  29.             Long result = (Long) jedis.evalsha(sha1, 1, key, String.valueOf(requestTokens), String.valueOf(capacity), String.valueOf(rate), String.valueOf(interval));  
  30.   
  31.             if (result == 1L) {  
  32.                 System.out.println("请求成功,有足够的令牌。");  
  33.                 // 处理请求...  
  34.             } else {  
  35.                 System.out.println("请求失败,令牌不足。");  
  36.                 // 拒绝请求或进行其他处理...  
  37.             }  
  38.   
  39.         } catch (Exception e) {  
  40.             e.printStackTrace();  
  41.         }  
  42.     }  
  43.   
  44.     // 从文件加载Lua脚本内容  
  45.     private static String loadLuaScript(String filePath) throws IOException {  
  46.         StringBuilder sb = new StringBuilder();  
  47.         try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {  
  48.             String line;  
  49.             while ((line = reader.readLine()) != null) {  
  50.                 sb.append(line).append("\n");  
  51.             }  
  52.         }  
  53.         return sb.toString();  
  54.     }  
  55. }
复制代码

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多限流是一种强大而机动的技能本事,通过公道的计谋计划和实现方式,可以有用地掩护体系资源和服务质量。

   文章转载自:TechSynapse
  原文链接:
  体验地点:引迈 - JNPF快速开辟平台_低代码开辟平台_零代码开辟平台_流程计划器_表单引擎_工作流引擎_软件架构
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表