Java Redis多限流

在Java中实现Redis多限流通常涉及使用Redis的某些特性,如INCREXPIRELua脚本或者更高级的Redis数据结构如Redis BitmapsRedis Streams结合Redis Pub/Sub,或者使用Redis的第三方库如Redis Rate Limiter(基于Lua脚本或Redis自身功能实现)。然而,为了直接和易于实现,这里我们将使用Jedis库(Java的Redis客户端)结合Redis的INCREXPIRE命令来模拟一个基本的分布式多限流系统。

1. 使用Jedis库结合Redis的INCREXPIRE命令模拟一个基本的分布式多限流系统

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的INCREXPIRE命令来实现基本的限流功能。这里我们假设每个用户(或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的WATCHMULTI/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 StreamsRedis 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多限流是一种强大而灵活的技术手段,通过合理的策略设计和实现方式,可以有效地保护系统资源和服务质量。

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

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

相关文章

解决WSL2突然没网络问题

解决WSL2突然没网络问题 在使用Windows Subsystem for Linux&#xff08;WSL&#xff09;时&#xff0c;有时可能会遇到网络连接问题。以下步骤可以帮助你解决这些问题。 一、启动命令提示符&#xff08;cmd&#xff09; 1. 关闭WSL 在开始任何网络配置前&#xff0c;先关闭…

Oracle11g_RAC for vmware workstation 安装教程(on suse11)

一、前言 本文介绍在vmware workstation环境下&#xff0c;基于suse11sp1操作系统安装Oracle11g RACASM 数据库&#xff08;两节点&#xff09;。 1.1 RAC中的基本概念 安装ORACLE RACASM前&#xff0c;您可能需要事先简要的了解RAC&#xff0c;CRS&#xff0c;ASM的概念。 1.1…

easyExcel 不规则模板导入数据

文章目录 前言一、需求和效果二、难点和思路三、全部代码踩坑 前言 之前分享的 EasyExcel 批量导入并校验数据&#xff0c;仅支持规则excel&#xff0c;即首行表头&#xff0c;下面对应数据&#xff0c;无合并单元格情况。 本篇主要解决问题&#xff1a; 模板excel 表头不在首…

【python】 对上市银行的年报信息进行语义挖掘,计算各银行年报中与金融科技有关的关键词的词向量的余弦相似性,衡量银行的金融科技发展程度。

目录 引言 文本预处理 数据收集 ​编辑​编辑 文本清洗 词向量的训练 Word2Vec 的两种主要模型 Word2Vec 的工作原理 训练过程 Word2Vec 的应用 训练模型 建立银行应用金融科技的关键词词库 关键词 计算余弦相似度 统计关键词词频 引言 随着金融科技的迅猛发展&a…

在Linux系统,高效管理Python数据采集程序!

在当今数字化时代&#xff0c;数据的获取和处理变得至关重要。Python 凭借其强大的功能和丰富的库&#xff0c;成为了数据采集的热门选择。而 Linux 系统以其稳定性和高效性&#xff0c;为 Python 数据采集程序的运行提供了理想的环境。 一、Anaconda 的安装 Anaconda 是一个包…

react多级组件间如何传递props

1.使用props属性一级级传递 针对父&#xff0c;子&#xff0c;孙子&#xff0c;如何实现将props从父级传递给孙子。 父&#xff1a; <ParentComponent parent{this} /> //传递this子&#xff1a; <childComponent propsContext{this.props.parent} />孙子&#x…

前端面试题54(断点续传讲解)

断点续传是一种在上传或下载大文件时&#xff0c;如果因为网络问题中断&#xff0c;可以从已经上传或下载的部分继续&#xff0c;而不是重新开始的技术。这对于提高用户体验和节省带宽非常有帮助。下面我将分别从HTTP协议层面、前端实现思路以及一个简单的前端实现示例来讲解断…

【代码随想录算法训练营第六十五天|卡码网94.城市间货物运输IIIIII】

文章目录 94.城市间货物运输ISPFA(bellman_ford队列优化)Bellman_ford判断负权回路 96.城市间货物运输IIIBellman_ford 94.城市间货物运输I SPFA(bellman_ford队列优化) 在bellman_ford的基础上&#xff0c;在每次松弛的时候&#xff0c;只有和前面的结点相连的边的松弛才是有…

linux权限深度解析——探索原理

前言&#xff1a;本节内容主要讲述的是linux权限相关的内容&#xff0c; linux的权限如果使用root账号是感受不到的&#xff0c; 所以我们要使用普通账号对本节相关内容进行学习&#xff0c;以及一些实验的测试。 然后&#xff0c; 通过linux权限的学习我们可以知道为什么有时候…

合合TextIn - 大模型加速器

TextIn是合合信息旗下的智能文档处理平台&#xff0c;在智能文字识别领域深耕17年&#xff0c;致力于图像处理、模式识别、神经网络、深度学习、STR、NLP、知识图谱等人工智能领域研究。凭借行业领先的技术实力&#xff0c;为扫描全能王、名片全能王等智能文字识别产品提供强大…

使用GPT-4和ChatGPT构建应用项目

文章目录 项目1:构建新闻稿生成器项目2:YouTube视频摘要项目3:打造《塞尔达传说:旷野之息》专家项目4:语音控制项目1:构建新闻稿生成器 GPT-4和ChatGPT等LLM专用于生成文本。我们可以使用GPT-4和ChatGPT在各种场景中生成文本,举例如下。 电子邮件合同或正式文档创意写作…

SpringBoot相关

SpringBoot 1. what springboot也是spring公司开发的一款框架。为了简化spring项目的初始化搭建的。 spring项目搭建的缺点&#xff1a; 配置麻烦依赖繁多tomcat启动慢 2 .springboot的特点(why) 自动配置 springboot的自动配置是一个运行时(更准确地说&#xff0c;是应用程…

关于斯坦福TTT,大家难道没啥可唠的嘛~?

TTT与transformer也好或manba也好它们之间背后的本质思想&#xff0c;表面上来看是对上下文进行状态表征压缩&#xff0c;再细想来看&#xff0c;均是一种对输入自身结构的一种线性建模变换&#xff0c;不过三者间所采用线性建模方法和策略各有不同和优劣&#xff0c;而TTT在这…

加载预训练后的深度网络,使用pytorch框架

用 PyTorch 框架加载预训练模型并进行预测的过程包括以下几个步骤&#xff1a;加载模型、进行图像预处理、进行前向传播以及处理预测结果。以下是一个完整的示例&#xff0c;展示了如何使用预训练的 ResNet50 模型在一张图像上进行预测。 import torch from torchvision impor…

【C++ Primer Plus】学习笔记1

文章目录 前言一、预备知识二、基本语法1.main函数2.有返回值的函数C程序应当为程序中使用的每个函数提供原型3.自定义函数 总结 前言 一直没系统学过C&#xff0c;最近接触了一段时间Java发现还是不太喜欢&#xff0c;所以转向C开发了qaq。因为学过C语言不算零基础了&#xf…

软件杂志软件杂志社软件编辑部2024年第4期目录

基金项目论文 “互联网”环境下智慧教育支撑平台的架构研究 黄孔曜; 1-3 基于机器学习的Web网络爬虫算法优化研究 刘俊培;贾继洋;班岚;迟欢;孙沛叶; 4-7 基于Ant Design Pro的物流系统前端开发与用户体验优化研究 王菊雅; 8-10《软件》投稿&#xff1a;cnqikantg12…

JavaScript 数组常用方法详细教程

在 JavaScript 中&#xff0c;数组是一种非常重要的数据结构&#xff0c;用于存储多个值。JS 提供了许多内置方法来操作数组&#xff0c;使得数据处理变得更加简单和高效。本文将详细介绍一些常用的 JavaScript 数组方法&#xff0c;这些不但是平时开发常用的方法&#xff0c;也…

【建议收藏】一万字图文并茂,终于有人把GPT的玩法整理全了

1. 学生常用 1.1 辅导作业、写作业 打数学建模和写期末作业~ Openai GPT-4o 模型从 2024 年 5 月发布以来&#xff0c;作为各项性能评测综合第一的 GPT。 对于法律类&#xff0c;语言类的作业&#xff0c;随意秒杀了&#xff01;&#xff01; 所以我决定让他做一道高等数学…

抽象代数精解【1】

文章目录 群概述一、群的定义二、群的基本性质三、群的分类与例子四、群的应用 难点与例子 参考文献 群 概述 下面由文心一言生成 数学中的“群”&#xff08;group&#xff09;是一个重要的代数结构概念&#xff0c;它起源于对方程解析解的探索&#xff0c;由伽罗瓦&#xff…

keepalived+nginx实现高可用

1. keepalived需要了解的知识 1.1 业务场景&#xff1a; 如果我们有个网站&#xff0c;最开始只有一台服务器对用户提供服务&#xff0c;业务架构图如下&#xff1a; 当业务量增大时&#xff0c;这台服务器支撑不了那么大的流量&#xff0c;随时会出现宕机的风险&#xff0c;…