首页 > 数据库 >redis+lua实现令牌桶限流

redis+lua实现令牌桶限流

时间:2022-08-30 17:24:42浏览次数:71  
标签:令牌 curTime -- currentTokens redis lua 限流 key local

github下载直接使用。无任何冗余代码: [email protected]:wangbensen/zhongtai-ext.git

Lua脚本

--[[
  1. key - 令牌桶的 key
  2. intervalPerTokens - 生成令牌的间隔(ms)
  3. curTime - 当前时间
  4. initTokens - 令牌桶初始化的令牌数
  5. bucketMaxTokens - 令牌桶的上限
  6. resetBucketInterval - 重置桶内令牌的时间间隔
  7. currentTokens - 当前桶内令牌数
  8. bucket - 当前 key 的令牌桶对象
]] --

local key = KEYS[1]
local intervalPerTokens = tonumber(ARGV[1])
local curTime = tonumber(ARGV[2])
local initTokens = tonumber(ARGV[3])
local bucketMaxTokens = tonumber(ARGV[4])
local resetBucketInterval = tonumber(ARGV[5])

local bucket = redis.call('hgetall', key)
local currentTokens

-- 若当前桶未初始化,先初始化令牌桶
if table.maxn(bucket) == 0 then
    -- 初始桶内令牌
    currentTokens = initTokens
    -- 设置桶最近的填充时间是当前
    redis.call('hset', key, 'lastRefillTime', curTime)
    -- 初始化令牌桶的过期时间, 设置为间隔的 1.5 倍
    redis.call('pexpire', key, resetBucketInterval * 1.5)

-- 若桶已初始化,开始计算桶内令牌
-- 为什么等于 4 ? 因为有两对 field, 加起来长度是 4
-- { "lastRefillTime(上一次更新时间)","curTime(更新时间值)","tokensRemaining(当前保留的令牌)","令牌数" }
elseif table.maxn(bucket) == 4 then

    -- 上次填充时间
    local lastRefillTime = tonumber(bucket[2])
    -- 剩余的令牌数
    local tokensRemaining = tonumber(bucket[4])

    -- 当前时间大于上次填充时间
    if curTime > lastRefillTime then

        -- 拿到当前时间与上次填充时间的时间间隔
        -- 举例理解: curTime = 2620 , lastRefillTime = 2000, intervalSinceLast = 620
        local intervalSinceLast = curTime - lastRefillTime

        -- 如果当前时间间隔 大于 令牌的生成间隔
        -- 举例理解: intervalSinceLast = 620, resetBucketInterval = 1000
        if intervalSinceLast > resetBucketInterval then

            -- 将当前令牌填充满
            currentTokens = initTokens

            -- 更新重新填充时间
            redis.call('hset', key, 'lastRefillTime', curTime)
            
        -- 如果当前时间间隔 小于 令牌的生成间隔
        else

            -- 可授予的令牌 = 向下取整数( 上次填充时间与当前时间的时间间隔 / 两个令牌许可之间的时间间隔 )
            -- 举例理解 : intervalPerTokens = 200 ms , 令牌间隔时间为 200ms
            --           intervalSinceLast = 620 ms , 当前距离上一个填充时间差为 620ms
            --           grantedTokens = 620/200 = 3.1 = 3
            local grantedTokens = math.floor(intervalSinceLast / intervalPerTokens)

            -- 可授予的令牌 > 0 时
            -- 举例理解 : grantedTokens = 620/200 = 3.1 = 3
            if grantedTokens > 0 then

                -- 生成的令牌 = 上次填充时间与当前时间的时间间隔 % 两个令牌许可之间的时间间隔
                -- 举例理解 : padMillis = 620%200 = 20
                --           curTime = 2620
                --           curTime - padMillis = 2600
                local padMillis = math.fmod(intervalSinceLast, intervalPerTokens)

                -- 将当前令牌桶更新到上一次生成时间
                redis.call('hset', key, 'lastRefillTime', curTime - padMillis)
            end

            -- 更新当前令牌桶中的令牌数
            -- Math.min(根据时间生成的令牌数 + 剩下的令牌数, 桶的限制) => 超出桶最大令牌的就丢弃
            currentTokens = math.min(grantedTokens + tokensRemaining, bucketMaxTokens)
        end
    else
        -- 如果当前时间小于或等于上次更新的时间, 说明刚刚初始化, 当前令牌数量等于桶内令牌数
        -- 不需要重新填充
        currentTokens = tokensRemaining
    end
end

-- 如果当前桶内令牌小于 0,抛出异常
assert(currentTokens >= 0)

-- 如果当前令牌 == 0 ,更新桶内令牌, 返回 0
if currentTokens == 0 then
    redis.call('hset', key, 'tokensRemaining', currentTokens)
    return 0
else
    -- 如果当前令牌 大于 0, 更新当前桶内的令牌 -1 , 再返回当前桶内令牌数
    redis.call('hset', key, 'tokensRemaining', currentTokens - 1)
    return currentTokens
end

  限流逻辑:

 

标签:令牌,curTime,--,currentTokens,redis,lua,限流,key,local
From: https://www.cnblogs.com/xiaowangbangzhu/p/16640108.html

相关文章

  • redis zset 延迟合并任务处理
    rediszset延迟合并任务处理@AutowiredpublicRedisTemplateredisTemplate;##1.发送端:在接口中收集任务ID,累计时间段之后,合并处理。##rediszset主键,任......
  • redission同时加多个锁
    业务场景:比如:给某条记录点赞时,有两个条件:(1)本条记录有点赞限制  (2)点赞人有点赞限制。问题:并发时,需要加锁,而且需要同时加两把锁。工具类:@Servicepublicclass......
  • redis(linux)
    官网下载安装包https://redis.io/download/安装gcc,命令yuminstallgcc进入安装目录make命令进行编译如果报错-jemalloc/jemalloc.h:没有哪个文件,首先看有没有gcc,然后......
  • redis分布式锁
    什么是分布式锁?Redis因为单进程、性能高常被用于分布式锁;锁在程序中作用是同步工具,保证共享资源在同一时刻只能被一个线程访问。Java中经常用的锁synchronized、Lock,但是......
  • 50道Redis高频面试题
    一、Redis到底是单线程还是多线程Redis6.0版本之前的单线程指的是其网络I/O和键值对读写是由一个线程完成。也就是只有网络请求模块和数据操作模块是单线程的,而其他的持......
  • lua 判断table是否为空
    项目中会经常要判断表是否是空表。直接用表与{}比较,是错误的。locala={}ifa=={}thenprint("a是空表")elseprint("a不是空表")end#输出:a不是空......
  • Django使用Redis进行缓存详细流程
    1.背景和意义服务器数据非经常更新。若每次都从硬盘读取一次,浪费服务器资源、拖慢响应速度。而且数据更新频率较高,服务器负担比较大。若保存到数据库,还需要额外建立一张对......
  • 10 分钟彻底理解 Redis 的持久化和主从复制
    在这篇文章,一起了解一下其中一个非常重要的内容:Redis的持久化机制。什么是Redis持久化?Redis作为一个键值对内存数据库(NoSQL),数据都存储在内存当中,在处理客户端请求时,所......
  • RedisInsight :Redis 官方可视化工具
    RedisInsight是Redis官方出品的可视化管理工具,可用于设计、开发、优化你的Redis应用。支持深色和浅色两种主题,界面非常炫酷。可支持String、Hash、Set、List、JSON等多种......
  • Redis主要数据结构以及应用场景
    String最常用的各式,以kv格式进行存储常用的场景在于对象json存储,以及对象缓存、分布式锁、计数器等。SETKEYVALUE存入字符串的键值对MSETkeyvalue[keyvalue......