深入解析 Redisson 分布式限流器 RRateLimiter 的原理与实现

文章目录

    • RRateLimiter 介绍
    • 代码实现
    • Lua 脚本
    • 现实场景
    • 1. 初始化限流器
    • 2. 限流器应用场景(客人申请游玩流程)

RRateLimiter 介绍

在分布式系统中,限流(Rate Limiting)是保障系统稳定性、避免过载的重要机制。Redisson 作为一个功能强大的 Redis 客户端,不仅提供了广泛使用的分布式锁,还包含了许多其他实用的分布式工具。其中,RRateLimiter 是 Redisson 提供的分布式限流器,功能强大。本文将详细解析 RRateLimiter 的原理,深入理解其工作机制。

代码实现

首先,通过一个简单的示例了解如何使用 RRateLimiter,它创建了一个限流器并启动多个线程来获取令牌:

import org.redisson.Redisson; // 导入 Redisson 的核心类,用于创建 Redisson 客户端
import org.redisson.api.RRateLimiter; // 导入 RRateLimiter 接口,用于实现分布式限流
import org.redisson.api.RedissonClient; // 导入 RedissonClient 接口,用于与 Redis 进行交互
import org.redisson.config.Config; // 导入 Redisson 的配置类,用于配置 Redis 连接import java.util.concurrent.CountDownLatch; // 导入 CountDownLatch 类,用于控制线程同步public class RateLimiterDemo { // 定义一个公共类 RateLimiterDemopublic static void main(String[] args) throws InterruptedException { // 主方法,程序入口,可能抛出 InterruptedExceptionRRateLimiter rateLimiter = createRateLimiter(); // 创建一个 RRateLimiter 实例int totalThreads = 20; // 定义总线程数为 20CountDownLatch latch = new CountDownLatch(totalThreads); // 创建一个 CountDownLatch 实例,初始计数为 totalThreadslong startTime = System.currentTimeMillis(); // 记录开始时间,用于计算总耗时for (int i = 0; i < totalThreads; i++) { // 循环创建并启动 20 个线程new Thread(() -> { // 创建一个新线程rateLimiter.acquire(1); // 每个线程尝试获取 1 个令牌,若令牌不足则阻塞等待latch.countDown(); // 线程完成后,调用 countDown() 方法减少计数器}).start(); // 启动线程}latch.await(); // 主线程等待,直到所有子线程完成System.out.println("Total elapsed time: " + (System.currentTimeMillis() - startTime) + " ms"); // 打印总耗时}/*** 创建并配置 RRateLimiter 的方法** @return 配置好的 RRateLimiter 实例*/private static RRateLimiter createRateLimiter() { // 创建并配置 RRateLimiter 的方法Config config = new Config(); // 创建一个新的 Redisson 配置实例config.useSingleServer() // 配置使用单一 Redis 服务器.setAddress("redis://127.0.0.1:6379") // 设置 Redis 服务器地址.setTimeout(1000000); // 设置连接超时时间(毫秒)RedissonClient redisson = Redisson.create(config); // 根据配置创建一个 Redisson 客户端实例RRateLimiter rateLimiter = redisson.getRateLimiter("myRateLimiter"); // 获取名为 "myRateLimiter" 的 RRateLimiter 实例rateLimiter.trySetRate(RRateLimiter.RateType.OVERALL, 1, 1, RateIntervalUnit.SECONDS); // 初始化限流器,设置全局速率为每秒 1 个令牌return rateLimiter; // 返回配置好的限流器实例}
}

Lua 脚本

为了更深入地理解 RRateLimiter 的工作原理,将进一步解析其底层的 Lua 脚本,实现分布式限流的核心逻辑。以下内容将逐行解释 Lua 脚本的功能和实现细节。

redis.call('hsetnx', KEYS[1], 'rate', ARGV[1]); -- 将速率设置到哈希表中,只有当 'rate' 字段不存在时才设置
redis.call('hsetnx', KEYS[1], 'interval', ARGV[2]); -- 将时间区间设置到哈希表中,只有当 'interval' 字段不存在时才设置
return redis.call('hsetnx', KEYS[1], 'type', ARGV[3]); -- 将类型设置到哈希表中,只有当 'type' 字段不存在时才设置,并返回结果
-- ARGV[1] 为请求令牌数
-- ARGV[2] 为请求时间戳
-- ARGV[3] 为请求类型-- 获取限流器的速率、时间区间和类型
local rate = redis.call("hget", KEYS[1], "rate") -- 从哈希表中获取速率
local interval = redis.call("hget", KEYS[1], "interval") -- 获取时间区间(毫秒)
local type = redis.call("hget", KEYS[1], "type") -- 获取限流器的类型(单机或集群)
assert(rate ~= false and interval ~= false and type ~= false, "RateLimiter is not initialized") -- 确保限流器已初始化-- 默认情况下,使用 {name}:value 和 {name}:permits
local valueName = KEYS[2] -- 当前令牌数的键名
local permitsName = KEYS[4] -- 记录请求的有序集合键名-- 如果类型为 "1"(单机模式),则使用不同的键名
if type == "1" thenvalueName = KEYS[3] -- 单机模式下的令牌数键名permitsName = KEYS[5] -- 单机模式下的有序集合键名
end-- 确保请求的令牌数不超过限流器的速率
assert(tonumber(rate) >= tonumber(ARGV[1]), "Requested permits amount could not exceed defined rate")-- 获取当前剩余的令牌数
local currentValue = redis.call("get", valueName)
-- 第一次请求直接走else
-- 第二次请求因为 valueName 更新有值,走if
if currentValue ~= false then-- 获取已过期的请求(初始时间 至 (当前时间(ARGV[2]-时间间隔(interval)) 准备清理失效的令牌数据local expiredValues = redis.call("zrangebyscore", permitsName, 0, tonumber(ARGV[2]) - interval)local released = 0 -- 初始化拟新增失效令牌数-- 遍历过期的请求,释放相应的令牌for i, v in ipairs(expiredValues) dolocal random, permits = struct.unpack("fI", v)released = released + permitsend-- 如果有释放的令牌,更新当前可用令牌数并移除过期的请求if released > 0 thenredis.call("zrem", permitsName, unpack(expiredValues)) -- 清除 permitsName 中包含 expiredValues 的数据currentValue = tonumber(currentValue) + released -- 清理失效令牌后计算总可用令牌数redis.call("set", valueName, currentValue) -- 更新可用令牌end-- 如果当前令牌数不足以满足请求  if tonumber(currentValue) < tonumber(ARGV[1]) then-- 计算需要等待的时间local nearest = redis.call('zrangebyscore', permitsName, '(' .. (tonumber(ARGV[2]) - interval), tonumber(ARGV[2]), 'withscores', 'limit', 0, 1) -- 找到最近一次的请求时间 nearest local random, permits = struct.unpack("fI", nearest[1]) -- 解压为时间戳+请求令牌数-- 返回等待时间,也可以写为 tonumber(nearest[2])+interval-tonumber(ARGV[2])return tonumber(nearest[2]) - (tonumber(ARGV[2]) - interval) -- nearest[2] 为上行的 randomelse-- 当前可用令牌数足够,记录此次请求并减少可用令牌数redis.call("zadd", permitsName, ARGV[2], struct.pack("fI", ARGV[3], ARGV[1])) -- 记录请求记录redis.call("decrby", valueName, ARGV[1]) -- 更新可用令牌数 valueName -= ARGV[1](请求令牌数)return nil -- 成功获取令牌end
else-- 第一次请求,初始化令牌数和有序集合redis.call("set", valueName, rate) -- 设置当前令牌数为最大速率值redis.call("zadd", permitsName, ARGV[2], struct.pack("fI", ARGV[3], ARGV[1])) -- 记录请求记录redis.call("decrby", valueName, ARGV[1]) -- 更新可用令牌数 valueName -= ARGV[1](请求令牌数)return nil -- 成功获取令牌
end

现实场景

冰雪大世界的热门项目每天吸引着络绎不绝的顾客。为了避免人流过于集中,影响顾客的体验和项目的正常运行,管理团队制定了以下规则:

  • 每小时只接待6位客人。
  • 每位客人在进入项目游玩,一个小时后自动将入场票归还到废票区,确保不影响后续客人的入场。
    限流机制的设置

1. 初始化限流器

项目每天一开始,第一位客人进入游玩时,系统会进行以下操作:

  • 统计系统剩余票数量:记录为(valueName),代表同一时间段内的最大客容量。
  • 记录每次申请的客人及进场时间:存储在(permitsName)中。
  • 刷新实际剩余票数量:更新为(currentValue = valueName),确保系统实时掌握当前剩余的入场票数。
    通过这些步骤,系统为当天的限流工作做好了准备。

2. 限流器应用场景(客人申请游玩流程)

当一位客人申请游玩项目时,系统会按照以下流程操作:

步骤一:查询可用票

  • 计算实际剩余票(currentValue = valueName)。

步骤二:回收废票

  • 从废票区 根据入场记录(permitsName)计算(当前时间-时间间隔)之前的所有废弃入场票(released),这意味着已进入游玩的客人在系统时间间隔后已不再影响项目后续游客的体验,归还的票可以重新使用。
  • 更新实际剩余票(currentValue):将回收的票数加到实际剩余票(currentValue += released)
  • 更新系统剩余票(valueName):(valueName = currentValue),确保系统知道当前有多少可用的入场票,反映最新的入场票状态。

步骤三:判断票数是否足够

  • 检查实际剩余票 (currentValue):与当前游客申请票数(tonumber(ARGV[1]))进行比较。
    • 如果票够用:

      • 记录此次请求:将客人的申请信息和进场时间记录到(permitsName)中。
      • 更新系统剩余票:(valueName -= 申请票数)中扣除相应的票数。
      • 允许客人进入:客人成功进入项目游玩。
    • 如果票不够:

      • 计算等待时间:根据上一位客人的入场时间和设定的时间间隔,计算出客人需要等待时间(上一位客人的入场时间+间隔时间-当前时间)。
      • 告知客人:将计算出的等待时间返回给客人,游客异步再尝试进入。

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

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

相关文章

【贪心算法】贪心算法七

贪心算法七 1.整数替换2.俄罗斯套娃信封问题3.可被三整除的最大和4.距离相等的条形码5.重构字符串 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我们一起努力吧!&#x1f603;&#x1f…

如何在 Ubuntu 22.04 上部署 Nginx 并优化以应对高流量网站教程

简介 本教程将教你如何优化 Nginx&#xff0c;使其能够高效地处理高流量网站。 Nginx 是一个强大且高性能的 Web 服务器&#xff0c;以其高效处理大量并发连接的能力而闻名&#xff0c;这使得它成为高流量网站的流行选择。 正确优化 Nginx 可以显著提高服务器的性能&#xff0…

活动预告 |【Part1】Microsoft Azure 在线技术公开课:数据基础知识

课程介绍 参加“Azure 在线技术公开课&#xff1a;数据基础知识”活动&#xff0c;了解有关云环境和数据服务中核心数据库概念的基础知识。通过本次免费的介绍性活动&#xff0c;你将提升在关系数据、非关系数据、大数据和分析方面的技能。 活动时间&#xff1a;01 月 07 日…

4G报警器WT2003H-16S低功耗语音芯片方案开发-实时音频上传

一、引言 在当今社会&#xff0c;安全问题始终是人们关注的重中之重。无论是家庭、企业还是公共场所&#xff0c;都需要一套可靠的安全防护系统来保障人员和财产的安全。随着科技的飞速发展&#xff0c;4G 报警器应运而生&#xff0c;为安全防范领域带来了全新的解决方案。…

短视频矩阵源码开发提供api/saas短视频矩阵快速对接搭建

上周&#xff0c;我有幸接待了一批来自教育行业的伙伴。令人惊讶的是&#xff0c;他们目前主要依赖于传统的线下推荐和地面推广方式进行业务拓展&#xff0c;对线上营销策略了解不多。这种情况引发了我对当前实体行业向线上转型的思考。 在当今社会&#xff0c;随着短视频营销逐…

GPU 进阶笔记(一):高性能 GPU 服务器硬件拓扑与集群组网

记录一些平时接触到的 GPU 知识。由于是笔记而非教程&#xff0c;因此内容不求连贯&#xff0c;有基础的同学可作查漏补缺之用 1 术语与基础 1.1 PCIe 交换芯片1.2 NVLink 定义演进&#xff1a;1/2/3/4 代监控1.3 NVSwitch1.4 NVLink Switch1.5 HBM (High Bandwidth Memory) 由…

24年收尾之作------动态规划<六> 子序列问题(含对应LeetcodeOJ题)

目录 引例 经典LeetCode OJ题 1.第一题 2.第二题 3.第三题 4.第四题 5.第五题 6.第六题 7.第七题 引例 OJ传送门 LeetCode<300>最长递增子序列 画图分析: 使用动态规划解决 1.状态表示 dp[i]表示以i位置元素为结尾的子序列中&#xff0c;最长递增子序列的长度 2.…

使用 ASP.NET Core wwwroot 上传和存储文件

在 ASP.NET Core 应用程序中上传和存储文件是用户个人资料、产品目录等功能的常见要求。本指南将解释使用wwwroot存储图像&#xff08;可用于文件&#xff09;的过程以及如何在应用程序中处理图像上传。 步骤 1&#xff1a;设置项目环境 确保您的 ASP.NET 项目中具有必要的依…

格式化输出年月日

直接上图 结论&#xff1a;老老实实用yyyy&#xff0c;得到的年月日是我们口头上说的时间&#xff0c;而YYYY有点反人类.... 对于一年的最后一周的一些日子&#xff0c;会统计成下一年&#xff1b; 对于下一年的第一周的一些日子&#xff0c;会统计成上一年&#xff1b; 你猜…

【超级详细】七牛云配置阿里云域名详细过程记录

0. 准备一个阿里云域名&#xff0c;记得要备案&#xff01;&#xff01;&#xff01;&#xff01; 1. 创建七牛云存储空间 首先&#xff0c;登录七牛云控制台&#xff0c;创建一个新的存储空间&#xff08;Bucket&#xff09;。这个存储空间将用于存放你的文件&#xff0c;并…

【C++】2029:【例4.15】水仙花数

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;题目描述&#x1f4af;我的做法思路分析优势不足之处 &#x1f4af;老师的做法思路分析优势不足 &#x1f4af;对比和优化实现方式对比优化思路和操作1. 直接分解数字的各位…

RabbitMQ基础篇之Java客户端 Work Queues

文章目录 模型概述需求实现步骤创建队列定义消费者定义消息发送测试执行观察结论多消费者的作用性能差异生产环境中的应用 处理速度差异的情况 优化示例总结 模型概述 Work Queues 模型也称为任务模型&#xff0c;多个消费者绑定到同一个队列&#xff0c;共同消费队列中的消息…

vscode代码AI插件Continue 安装与使用

“Continue” 是一款强大的插件&#xff0c;它主要用于在开发过程中提供智能的代码延续功能。例如&#xff0c;当你在编写代码并且需要进行下一步操作或者完成一个代码块时&#xff0c;它能够根据代码的上下文、语法规则以及相关的库和框架知识&#xff0c;为你提供可能的代码续…

ubuntu 如何使用vrf

在Ubuntu或其他Linux系统中&#xff0c;您使用ip命令和sysctl命令配置的网络和内核参数通常是临时的&#xff0c;这意味着在系统重启后这些配置会丢失。为了将这些配置持久化&#xff0c;您需要采取一些额外的步骤。 对于ip命令配置的网络接口和路由&#xff0c;您可以将这些配…

二、SQL语言,《数据库系统概念》,原书第7版

文章目录 一、概览SQL语言1.1 SQL 语言概述1.1.1 SQL语言的提出和发展1.1.2 SQL 语言的功能概述 1.2 利用SQL语言建立数据库1.2.1 示例1.2.2 SQL-DDL1.2.2.1 CREATE DATABASE1.2.2.2 CREATE TABLE 1.2.3 SQL-DML1.2.3.1 INSERT INTO 1.3 用SQL 语言进行简单查询1.3.1 单表查询 …

【ArcGIS Pro/GeoScene Pro】可视化时态数据

可视化过去二十年新西兰国际旅游业的发展变化 工程数据下载 ArcGIS Pro 快速入门指南—ArcGIS Pro | 文档 添加数据 数据为中国旅客数据 转置表字段 列数据转行数据

git在idea中操作频繁出现让输入token或用户密码,可以使用凭证助手(使用git命令时输入的用户密码即可) use credential helper

1、打开 idea 设置&#xff0c;找到 git 路径 File | Settings | Version Control | Git 2、勾选 Use credential helper 即可

CPT203 Software Engineering 软件工程 Pt.5 软件测试(中英双语)

文章目录 8. 软件测试8.1 Testing&#xff08;测试&#xff09;8.1.1 A note of testing under the V & A framework8.1.2 The Basics8.1.3 The Goals8.1.4 The Stages 8.2 Developing testing&#xff08;开发测试&#xff09;8.2.1 Unit testing&#xff08;单元测试&…

Docker基础知识 Docker命令、镜像、容器、数据卷、自定义镜像、使用Docker部署Java应用、部署前端代码、DockerCompose一键部署

目录 1.Docker 2.镜像和容器 2.1 定义 2.2 开机自动启动容器 3.docker命令 3.1 docker run 参数说明 3.2 常见命令 3.3 命令演示 3.4 命令别名 4.Docker命令详解 5.数据卷 5.1 定义 5.2 数据卷的相关命令 5.3 数据卷命令 5.4 挂载本地目录或文件 5.4.1 定义 5.4.2 mysql容器目录…

探索CSDN博客数据:使用Python爬虫技术

探索CSDN博客数据&#xff1a;使用Python爬虫技术 在数字化的浪潮中&#xff0c;数据的获取与分析变得日益关键。CSDN作为中国领先的IT社区和服务平台&#xff0c;汇聚了海量的技术博客与文章&#xff0c;成为一座蕴藏丰富的数据宝库。本文将引领您穿梭于Python的requests和py…