分布式限流——Redis + Lua脚本实现令牌桶算法

 

主要思路概括如下:

  1. 定义数据结构

    • 使用Redis存储令牌桶的状态,包括当前令牌数(KEYS[1])和上一次令牌填充的时间戳(KEYS[1]:last)。
  2. 计算新增令牌

    • 获取当前系统时间与上次令牌填充时间的时间差,并基于令牌生成速率计算在这段时间内应新增的令牌数。
    • 确保新增令牌数不超过桶的总容量。
  3. 更新令牌数

    • 将令牌桶内的令牌数增加至新的值,确保不超过桶的最大容量。
  4. 判断是否满足请求

    • 如果更新后的令牌数足以满足本次请求消耗的令牌数,则扣除相应数量的令牌并返回1,表示请求通过。
    • 否则,返回0,表示请求被限流。
  5. 原子性操作

    • 利用Redis Lua脚本提供的原子性执行能力,确保在多客户端并发访问时,令牌桶状态的读取、更新等操作是线程安全的。
  6. 实时性

    • 脚本设计考虑到随着时间推移动态补充令牌,但实际应用中可能需要更精确的定时任务或者使用Redis的过期时间特性来定期补充令牌。

整个令牌桶算法在Redis和Lua脚本中的实现旨在提供一种简单且高效的手段来进行流量控制,既可以应对突发流量,又能保持总体速率稳定可控。

-- KEYS[1] 是令牌桶的键名
-- ARGV[1] 是令牌生成速率(每秒产生的令牌数量)
-- ARGV[2] 是桶容量(最大令牌存储量)
-- ARGV[3] 是请求需要消耗的令牌数量
-- ARGV[4] 是当前时间戳(毫秒级,由客户端提供)-- 获取当前桶内令牌数
local tokenCount = tonumber(redis.call('GET', KEYS[1]))-- 如果桶内令牌数为空或不存在,则初始化为桶容量
if tokenCount == nil thentokenCount = tonumber(ARGV[2])
end-- 计算从上次填充到现在应该产生的令牌数
local currentTime = tonumber(ARGV[4])
local lastRefillTimestamp = tonumber(redis.call('GET', KEYS[1] .. ':last'))
if lastRefillTimestamp == nil thenlastRefillTimestamp = currentTime
end
local newTokens = math.min((currentTime - lastRefillTimestamp) * tonumber(ARGV[1]), tonumber(ARGV[2]) - tokenCount)-- 更新令牌数(注意这里假设时间粒度较小,不考虑精确到毫秒的令牌生成)
tokenCount = math.min(tokenCount + newTokens, tonumber(ARGV[2]))-- 设置最后填充令牌的时间为当前时间
redis.call('SET', KEYS[1] .. ':last', currentTime)-- 如果新加入的令牌仍然不够消耗,则请求被限流
if tokenCount < tonumber(ARGV[3]) thenreturn 0  -- 表示限流
else
-- 消耗相应数量的令牌tokenCount = tokenCount - tonumber(ARGV[3])redis.call('SET', KEYS[1], tokenCount)return 1  -- 表示通过
end

在上面的Lua脚本示例中,上次填充令牌的时间戳是通过另一个Redis键来保存的。在脚本中,这个键是通过在令牌桶的主键后面加上:last_refill来构成的,如 KEYS[1] .. ":last_refill"

这意味着对于一个令牌桶的键 bucket_key,它的最后一次填充令牌的时间戳会被保存在 bucket_key:last_refill 这个额外的键中。

在脚本执行过程中,当需要获取上次填充令牌的时间戳时,通过调用 redis.call('GET', KEYS[1] .. ":last_refill") 来获取。如果没有获取到值(即第一次运行或者之前没有设置过),则会将其默认设置为当前时间戳。

同样,在完成令牌填充的逻辑后,也会更新这个时间戳键的值,通过 redis.call('SET', KEYS[1] .. ":last_refill", currentTime) 实现,其中 currentTime 是客户端提供的当前时间戳。

 

这个Lua脚本简化了令牌桶算法的实现,实际应用中可能还需要考虑更多细节,例如如何精确地按照时间间隔补充令牌、如何处理多个请求同时竞争令牌等情况。客户端在调用这个Lua脚本时,应当传递正确的参数,包括令牌桶的键名以及令牌相关的配置信息。此外,在高并发场景下,为了避免多个客户端同时修改令牌数,Lua脚本的执行必须是原子性的,这正是Redis.eval/evalsha命令所提供的功能。

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

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

相关文章

信息系统项目管理师0053:设计和实施(4信息系统管理—4.1管理方法—4.1.3设计和实施)

点击查看专栏目录 文章目录 4.1.3设计和实施1.设计方法2.架构模式4.1.3设计和实施 开展信息系统设计和实施,首先需要将业务需求转换为信息系统架构,信息系统架构为将组织业务战略转换为信息系统的计划提供了蓝图。信息系统是支持组织中信息流动和处理的所有基础,包括硬件、软…

Ubuntu 18.04等 修改root的账号密码,删除root账号密码,并破解登陆密码!【需要有内核代码】

文章目录 1、命令总结2、已知账号的密码&#xff0c;能登陆Linux系统2.1、修改密码&#xff0c;使用 passwd <账号名> 命令2.2、删除密码&#xff0c;使用 passwd -d <账号名> 命令 3、未知账号的密码&#xff0c;不能登陆Linux系统&#xff08;并一定适用于大家&a…

前端请求发送成功,后端收到null

1、dishId为64&#xff0c;有数据 2、但是后端调试接不到数据&#xff0c;为null 3、形参部分缺少RequestBody接收JSON数据&#xff0c;加上即可

数据赋能(58)——要求:数据赋能实施部门能力

“要求&#xff1a;数据赋能实施部门能力”是作为标准的参考内容编写的。 在实施数据赋能中&#xff0c;数据赋能实施部门的能力体现在多个方面&#xff0c;关键能力如下图所示。 在实施数据赋能的过程中&#xff0c;数据赋能实施部门应具备的关键能力如下。 理性思维与逻辑分…

网络协议——IS-IS协议详解

1. IS-IS是什么 IS-IS是一种基于链路状态并使用最短路径优先算法进行路由计算的一种IGP协议。IS-IS属于内部网关协议&#xff0c;用于自治系统内部。IS-IS是一种链路状态协议&#xff0c;使用最短路径优先算法进行路由计算。 2. 应用场景&#xff08;园区网和骨干网&#xff0…

Xamarin.Android中“ADB0020: Android ABI 不匹配。你正将应用支持的“armeabi-v7a;arm64-v8a”异常处理

这里写自定义目录标题 1、问题2、解决 1、问题 在Xamarin.Android中出现ADB0020: Android ABI 不匹配。你正将应用支持的“armeabi-v7a;arm64-v8a”ABI 部署到 ABI“x86_64;x86”的不兼容设备。应创建匹配其中一个应用 ABI 的仿真程序&#xff0c;或将“x86_64”添加到应用生成…

【独立全开源】点大商城V2-2.5.7 新增 快递查询AppCode自定义

独立全开源版本&#xff1a;点大商城V2小程序公众号模块&#xff0c;版本更新至2.5.7&#xff0c;前端为UNiapp、这个是源码后端开源&#xff0c;购买包更新&#xff0c;包修复、 更新为覆盖升级&#xff0c;源码更新了&#xff1a;新增 快递查询AppCode自定义 测试环境&#…

线程和进程的区别(面试)

线程和进程的区别 进程和线程的区别线程的优点 进程和线程的区别 1. 进程是系统进行资源分配和调度的一个独立单位,线程是程序执行的最小单位. 2. 进程有自己的内存地址空间,线程只独享指令流执行的必要资源,如寄存器和栈. 3. 由于同一进程的各线程共享内存和文件资源,可以不通…

手撸词法分析器(C/C++)

手撸词法分析器&#xff08;C/C&#xff09; 一.背景二.什么是词法分析器&#xff1f;三.代码四.思考 一.背景 这学期开设了编译原理&#xff0c;要求写个基本的词法分析器。所以博主就自己写了一份代码&#xff0c;也比较简单基础。 二.什么是词法分析器&#xff1f; 简单来…

Postman之接口测试

接口测试的必要条件 &#xff1a;请求方式、请求协议、请求地址、请求头、请求参数 常用请求方式 &#xff1a;Get请求&#xff08;get请求一般是获取数据&#xff09;、Post请求&#xff08;post请求一般是提交数据&#xff09; 传参格式 &#xff1a;表单提交、请求体提交 注…

什么是神经网络和机器学习?【云驻共创】

什么是神经网络和机器学习&#xff1f; 一.背景 在当今数字化浪潮中&#xff0c;神经网络和机器学习已成为科技领域的中流砥柱。它们作为人工智能的支柱&#xff0c;推动了自动化、智能化和数据驱动决策的进步。然而&#xff0c;对于初学者和专业人士来说&#xff0c;理解神经…

设计模式-构建者模式

作者持续关注 WPS二次开发专题系列&#xff0c;持续为大家带来更多有价值的WPS二次开发技术细节&#xff0c;如果能够帮助到您&#xff0c;请帮忙来个一键三连&#xff0c;更多问题请联系我&#xff08;QQ:250325397&#xff09; 目录 定义 特点 使用场景 优缺点 (1) 优点 …

内网穿透部署流程

内网穿透 又称为NAT穿透&#xff0c;是一种技术手段&#xff0c;允许内网设备&#xff08;如运行在公司内部网络、家庭网络中的服务器、智能设备等&#xff09;的服务能够被外网用户访问。通常情况下&#xff0c;由于路由器、防火墙等设备的网络地址转换&#xff08;NAT&#x…

HTML + CSS 编程规范

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 &#x1f361;编程规范 HTML CSS&#x1f366;命名规范&#x1f366;常用命名…

Jmeter BeanShell调用Java方法加密

1、添加BeanShell前置处理器 由于请求接口时&#xff0c;会传加密参数。加密过程会在请求之前完成&#xff0c;所以需要使用前置处理器中beanshell preprocessor 2、编写BeanShell脚本 ①定义一个beashell变量&#xff1a;phoneNum&#xff0c;在Beanshell中可以直接调用Jmete…

小程序出现 非 h5 平台 :key 不支持表达式 ‘xx‘ 解决方法

目录 1. 问题所示2. 原理分析3. 解决方法1. 问题所示 执行代码的时候,uniapp的h5平台可以,但是微信小程序终端报如下错误: 19:37:46.620 提示:非 h5 平台 :key 不支持表达式 docheck_+index,详情参考: https://uniapp.dcloud.io/use

EFK环境搭建(基于K8S环境部署)

目录 一.环境信息二.安装nfs供应商三.安装elasticsearch四.安装kibana组件五.安装fluentd 一.环境信息 1.服务器及k8s版本 IP地址主机名称角色版本192.168.40.180master1master节点1.27192.168.40.181node1node1节点1.27192.168.40.182node2node2节点1.27 2.部署组件版本 序…

go语言并发实战——日志收集系统(三) 利用sarama包连接KafKa实现消息的生产与消费

环境的搭建 Kafka以及相关组件的下载 我们要实现今天的内容&#xff0c;不可避免的要进行对开发环境的配置&#xff0c;Kafka环境的配置比较繁琐&#xff0c;需要配置JDK,Scala,ZoopKeeper和Kafka&#xff0c;这里我们不做赘述&#xff0c;如果大家不知道如何配置环境&#x…

元宇宙-虚拟世界的安全风险如何应对

元宇宙&#xff08;Metaverse&#xff09;是一个虚拟时空间的集合&#xff0c;由一系列的增强现实&#xff08;AR&#xff09;、虚拟现实&#xff08;VR&#xff09;和互联网&#xff08;Internet&#xff09;所组成。这个虚拟时空间是一个持续存在的、由众多虚拟世界互相连接而…

MySQL常见问题汇总

1、"Host is not allowed to connect to this MySQL server" 方法1&#xff1a;GRANT ALL PRIVILEGES ON *.* TO root% WITH GRANT OPTION //赋予任何主机访问数据的权限 方法2&#xff1a;update user set host % where user root; 重启mysql 服务 2、mysqldum…