lua脚本实现Redis令牌桶限流

这篇具有很好参考价值的文章主要介绍了lua脚本实现Redis令牌桶限流。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

背景

令牌桶限流是一种常见的流量控制算法,用于控制系统的请求处理速率,防止系统过载。在令牌桶限流算法中,可以将请求看作是令牌,而令牌桶则表示系统的处理能力。系统在处理请求时,首先需要从令牌桶中获取令牌,如果令牌桶中没有足够的令牌,就需要等待一定时间,直到令牌桶中有足够的令牌。
具体来说,令牌桶限流算法可以通过以下方式实现:
1.系统维护一个固定容量的令牌桶,每秒钟会向桶中添加一定数量的令牌,直到桶的容量达到上限。
2.每次请求来临时,需要先从令牌桶中获取令牌,如果桶中有足够的令牌,则请求被允许通过,并从桶中移除一个令牌;如果桶中的令牌数量不足,则请求被拒绝。具体来说,令牌桶算法会维护一个令牌桶,其中包含一定数量的令牌,每个令牌代表一个可以执行操作的许可。在每个时间段内,如果有令牌可用,就可以执行一个操作,并将令牌桶中的令牌数量减少一。如果没有令牌可用,就不能执行操作,需要等待一定时间,直到令牌桶中有足够的令牌。
3.由于令牌桶的容量是有限的,因此当桶中的令牌数量达到上限时,新的令牌会被丢弃,从而限制了请求的处理速率。
令牌桶限流算法可以在多种场景中进行流量控制,例如 Web 应用程序、消息队列、数据库等。在 Web 应用程序中,可以通过令牌桶限流算法控制 API 的访问速率,防止 API 被恶意攻击或者过载。在消息队列中,可以通过令牌桶限流算法控制消息的生产和消费速率,防止消息堆积和系统崩溃。在数据库中,可以通过令牌桶限流算法控制查询和写入操作的速率,防止数据库过载和响应时间过长。

lua脚本实现令牌桶算法

Lua 脚本可以用来实现 Redis 的令牌桶限流:
1.定义 Redis 数据结构
使用 Redis 的 Hash 数据结构存储当前令牌桶的状态。在 Hash 中,rate 表示速率(每秒生成的令牌数),capacity 表示桶的容量(最多可以同时存储的令牌数),tokens 表示当前桶中的令牌数量,timestamp 表示上次更新令牌数量的时间戳。示例代码:

HSET rdb:token_bucket rate 10 capacity 100 tokens 100 timestamp 0

2.编写 Lua 脚本
编写 Lua 脚本来实现限流逻辑。在脚本中,首先读取当前时间戳和桶的状态,计算出从上次更新时间戳到当前时间应该生成的令牌数量。然后,将当前桶中的令牌数量和应该生成的令牌数量相加,得到当前桶中的令牌数量。如果当前桶中的令牌数量超过了桶的容量,将其限制为桶的容量。
然后,判断当前桶中的令牌数量是否足够执行操作。如果令牌数量足够,将当前桶中的令牌数量减去操作所需的令牌数量,并更新桶的状态。如果令牌数量不足,则返回限流的错误信息。
示例代码:

-- 读取桶的状态
local rate = tonumber(redis.call('HGET', KEYS[1], 'rate'))
local capacity = tonumber(redis.call('HGET', KEYS[1], 'capacity'))
local tokens = tonumber(redis.call('HGET', KEYS[1], 'tokens'))
local timestamp = tonumber(redis.call('HGET', KEYS[1], 'timestamp'))

-- 计算应该生成的令牌数量
local now = redis.call('TIME')
local elapsed = now[1] - timestamp
local generated = math.floor(elapsed * rate)

-- 更新令牌数量并限制桶的容量
tokens = math.min(capacity, tokens + generated)

-- 执行操作
local required = tonumber(ARGV[1])
if tokens >= required then
    tokens = tokens - required
    redis.call('HSET', KEYS[1], 'tokens', tokens)
    redis.call('HSET', KEYS[1], 'timestamp', now[1])
    return 1
else
    return 0
end

3.在应用程序中调用 Lua 脚本
在应用程序中,使用 Redis 的 EVAL 命令来调用 Lua 脚本。示例代码:

@Component
public class TokenBucketLimiter {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    public boolean tryAcquire(String key, int tokens) {
        List<String> keys = Arrays.asList(key);
        List<String> args = Arrays.asList(Integer.toString(tokens));
        Long result = redisTemplate.execute(new DefaultRedisScript<>(
                "local rate = tonumber(redis.call('HGET', KEYS[1], 'rate')) " +
                        "local capacity = tonumber(redis.call('HGET', KEYS[1], 'capacity')) " +
                        "local tokens = tonumber(redis.call('HGET', KEYS[1], 'tokens')) " +
                        "local timestamp = tonumber(redis.call('HGET', KEYS[1], 'timestamp')) " +
                        "local now = redis.call('TIME') " +
                        "local elapsed = now[1] - timestamp " +
                        "local generated = math.floor(elapsed * rate) " +
                        "tokens = math.min(capacity, tokens + generated) " +
                        "if tokens >= tonumber(ARGV[1]) then " +
                        "    tokens = tokens - tonumber(ARGV[1]) " +
                        "    redis.call('HSET', KEYS[1], 'tokens', tokens) " +
                        "    redis.call('HSET', KEYS[1], 'timestamp', now[1]) " +
                        "    return 1 " +
                        "else " +
                        "    return 0 " +
                        "end",
                Long.class), keys, args);
        return result != null && result == 1L;
    }
}

或者如下脚本:文章来源地址https://www.toymoban.com/news/detail-621465.html

-- 返回码 1:通过限流 0:不通过
-- rate ARGV[1] 每秒填充速率
-- now  ARGV[2] 当前时间
-- capacity ARGV[3] 令牌桶最大数量
-- request ARGV[4] 需要令牌数量
local SUCCESS = "1"
local FAIL = "0"
local rate = tonumber(ARGV[1]) -- replenishRate 令令牌桶填充平均速率
local capacity = tonumber(ARGV[2]) -- burstCapacity 令牌桶上限
local now = tonumber(ARGV[3]) -- 机器传入的当前时间 秒
local requested = tonumber(ARGV[4]) -- 消耗令牌数量,默认取1

local fill_time = capacity/rate   -- 计算令牌桶填充满令牌需要多久时间
local ttl = math.floor(fill_time*2) -- *2 保证时间充足

local result = SUCCESS;




-- ttl 防止小于0
if ttl < 1 then
    ttl = 10
end


-- 1、获取桶内令牌剩余数量
local last_tokens = tonumber(redis.call("get", KEYS[1]))
-- 获得令牌桶剩余令牌数
if last_tokens == nil then -- 第一次时,没有数值,所以桶时满的
    last_tokens = capacity
end

-- 2、获取上次更新时间
local last_refreshed = tonumber(redis.call("get", KEYS[2]))
-- 令牌桶最后填充令牌时间
if last_refreshed == nil then
    last_refreshed = 0
end

-- 3、本次验证和上次更新时间的间隔
local delta = math.max(0, now-last_refreshed)
-- 填充令牌,计算新的令牌桶剩余令牌数 填充不超过令牌桶令牌上限。
local filled_tokens = math.min(capacity, last_tokens+(delta*rate))

-- 4、判断令牌数量是否足够
local allowed = filled_tokens >= requested
local new_tokens = filled_tokens
local allowed_num = "0"
if allowed then
    -- 若成功,令牌桶剩余令牌数(new_tokens) 减消耗令牌数( requested ),并设置获取成功( allowed_num = 1 ) 。
    new_tokens = filled_tokens - requested
    allowed_num = SUCCESS
end

-- 5、设置令牌桶剩余令牌数( new_tokens ) ,令牌桶最后填充令牌时间(now) ttl是超时时间
redis.call("setex", KEYS[1], ttl, new_tokens)
redis.call("setex", KEYS[2], ttl, now)

if not allowed then
    return FAIL
end

return SUCCESS




到了这里,关于lua脚本实现Redis令牌桶限流的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 68. redis计数与限流中incr+expire的坑以及解决办法(Lua+TTL)

    在日常工作中,经常会遇到对某种操作进行频次控制或者统计次数的需求,此时常用的做法是采用 redis 的 incr 来递增,记录访问次数, 以及 expire 来设置失效时间。本文将以一个实际的例子来说明 incr 存在的一个\\\"坑\\\",以及给出解决方案。 如: 26.redis实现日限流、周限流(含黑

    2024年01月21日
    浏览(34)
  • 【实战】使用Lua脚本怎么清理redis中的数据【实战】使用Lua脚本怎么清理redis中的数据

    首先我们通过hiredis 向redis 中写入了数据,这里我们主要以测试为目的,所以,Key 值设定为毫秒级时间戳。 但是当我们测试完成之后,需要验证实际情况,这里我们直接使用redis-cli 登录数据库看看。 本次测试完成,接下来要结合业务开始测试,需要清理数据库,但是一条一

    2024年02月13日
    浏览(91)
  • Redis入门 - Lua脚本

    原文首更地址,阅读效果更佳! Redis入门 - Lua脚本 | CoderMast编程桅杆 https://www.codermast.com/database/redis/redis-scription.html Redis 脚本使用 Lua 解释器来执行脚本。 Redis 2.6 版本通过内嵌支持 Lua 环境。执行脚本的常用命令为 EVAL。 Eval 命令的基本语法如下: EVAL script numkeys key [key ...]

    2024年02月09日
    浏览(42)
  • Redis之Lua脚本

    目录 Lua脚本 编写Lua脚本  springboot整合redis使用lua Lua脚本        Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行。使用脚本的好处如下: 1. 减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在redis服务器上完成。使

    2024年01月23日
    浏览(38)
  • 使用lua脚本操作redis

    redis中实现事务有两种方法: 1.WATCH监视键的变动,然后MULTI开始事务,EXEC提交事务 WATCH key [key…]:监视一个或多个键,如果在事务执行之前被修改,则事务被打断。 MULTI:标记一个事务的开始。 EXEC:执行事务中的所有命令。 DISCARD:取消一个事务,放弃执行事务中的所有命

    2024年02月16日
    浏览(49)
  • Redis之Lua脚本讲解

    当涉及 Lua 编程时,以下是对前述12个关键概念的详细说明,附带Lua代码示例以帮助更深入了解这门编程语言 1.1.1 注释 注释在 Lua 中用于添加说明和注解。单行注释以 -- 开始,多行注释则使用 --[[ ... ]] 。 1.1.2 变量 变量在 Lua 中无需显式声明类型。使用 local 创建局部变

    2024年02月08日
    浏览(32)
  • 【Redis】Redis 的学习教程(十二)之在 Redis使用 lua 脚本

    lua 菜鸟教程:https://www.runoob.com/lua/lua-tutorial.html 在 Redis 使用 lua 脚本的好处: 减少网络开销 。可以将多个请求通过脚本的形式一次发送,减少网络时延及开销 原子性操作 。Redis会将整个脚本作为一个整体执行,中间不会被其他请求插入。因此在脚本运行过程中无需担心会出

    2024年02月07日
    浏览(49)
  • Redis命令行使用Lua脚本

    Lua脚本在Redis中的使用非常有用,它允许你在Redis服务器上执行自定义脚本,可以用于复杂的数据处理、原子性操作和执行多个Redis命令。以下是Lua脚本在Redis中的基本使用详细讲解: 运行Lua脚本: 在Redis中,你可以使用 EVAL 或 EVALSHA 命令来运行Lua脚本。 EVAL 用于执行未经缓存

    2024年02月07日
    浏览(48)
  • Springboot+Redis执行lua脚本

            随着Redis数据库的广泛应用,它在服务器端应用程序中的作用越来越重要。Redis具有快速读写、数据持久化、发布订阅、事务处理等诸多特性,而这些特性使得它在处理高并发、实时数据操作等方面表现出色。然而,单纯使用Redis还不足以满足一些复杂业务逻辑的需

    2024年02月09日
    浏览(46)
  • 【Redis】5、Redis 的分布式锁、Lua 脚本保证 Redis 命令的原子性

    🎄 分布式锁:满足分布式系统或集群模式下 多进程可见 并且 互斥的 锁 🎄 分布式锁的核心是实现多进程之间锁的互斥 ,而满足这一点的方式有很多,常见的有三种: 🎄锁获取了,还没有来得及设置过期时间服务器就宕机了 🎄保证 setnx(获取锁)和 expire 设置过期时间两

    2024年02月15日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包