redis基础限流

本文介绍了在分布式环境中使用Redis和Lua脚本实现限流器的方法,详细讲解了通过Lua脚本保证操作原子性,以及在Java中集成Redis进行限流的操作步骤,包括lua脚本、lua调试、java集成和限流器的测试验证。
摘要由CSDN通过智能技术生成

之前这篇文章中,我大致介绍了一下google guava库中的RateLimiter的实现以及它背后的令牌桶算法原理。但是也有新的问题,在分布式的环境中,我们如何针对多机环境做限流呢?在查阅了一些资料和其他人的博客之后,我采用了redis来作为限流器的实现基础。
原因主要有以下几点:

  • redis作为高性能缓存系统,性能上能够满足多机之间高并发访问的要求
  • redis有比较好的api来支持限流器令牌桶算法的实现
  • 对于我们的系统来说,通过spring data redis来操作比较简单和常见,避免了引入新的中间件带来的风险

但是我们也知道,限流器在每次请求令牌和放入令牌操作中,存在一个协同的问题,即获取令牌操作要尽可能保证原子性,否则无法保证限流器是否能正常工作。在RateLimiter的实现中使用了mutex作为互斥锁来保证操作的原子性,那么在redis中就需要一个类似于事务的机制来保证获取令牌中多重操作的原子性。
面对这样的需求,我们有几个选择:

  • 用redis实现分布式锁来保证操作的原子性,这个方案实现起来应该比较简单,分布式锁有现成的例子,然后就是把Rate Limiter的代码套用分布式锁就行了,但是这样的话效率会显得不太高,特别是在大量访问的情况下。
  • 用redis的transaction,在我查阅redis官方文档和stackoverflow之后发现redis的transaction官方并不推荐,并且有可能在未来取消事务,因此不可取。
  • 通过redis分布式锁和本地锁组成一个双层结构,每次分布式获取锁之后可以预支一部分令牌量,然后放到本地通过本地的锁来分配这些令牌,消耗完之后再到请求redis。这样的好处是相比第一个方案,网络访问延迟开销会比较好,但是实现难度和复杂程度比较难估量,而且这样的做法如果在多机不能保证均匀分配流量的情况下并不理想
  • 通过将获取锁封装到lua脚本中,提交给redis进行eval和evalsha操作来完成lua脚本的执行,由于lua脚本在redis中天然的原子性,我们的需求能够比较好的满足,问题是将业务逻辑封装在lua中,对于开发人员自身的能力和调试存在一定的问题。

经过权衡,我采用了第四种方式,通过redis和lua来编写令牌桶算法来完成分布式限流的需求。

lua脚本

话不多说,先贴出lua代码

 


-- 返回码 1:操作成功 0:未配置 -1: 获取失败 -2:修改错误,建议重新初始化 -500:不支持的操作
-- redis hashmap 中存放的内容:
-- last_mill_second 上次放入令牌或者初始化的时间
-- stored_permits 目前令牌桶中的令牌数量
-- max_permits 令牌桶容量
-- interval 放令牌间隔
-- app 一个标志位,表示对于当前key有没有限流存在

local SUCCESS = 1
local NO_LIMIT = 0
local ACQUIRE_FAIL = -1
local MODIFY_ERROR = -2
local UNSUPPORT_METHOD = -500

local ratelimit_info = redis.pcall("HMGET",KEYS[1], "last_mill_second", "stored_permits", "max_permits", "interval", "app")
local last_mill_second = ratelimit_info[1]
local stored_permits = tonumber(ratelimit_info[2])
local max_permits = tonumber(ratelimit_info[3])
local interval = tonumber(ratelimit_info[4])
local app = ratelimit_info[5]

local method = ARGV[1]

--获取当前毫秒
--考虑主从策略和脚本回放机制,这个time由客户端获取传入
--local curr_time_arr = redis.call('TIME')
--local curr_timestamp = curr_time_arr[1] * 1000 + curr_time_arr[2]/1000
local curr_timestamp = tonumber(ARGV[2])


-- 当前方法为初始化
if method == 'init' then
    --如果app不为null说明已经初始化过,不要重复初始化
    if(type(app) ~='boolean' and app ~=nil) then
        return SUCCESS
    end

    redis.pcall("HMSET", KEYS[1],
        "last_mill_second", curr_timestamp,
        "stored_permits", ARGV[3],
        "max_permits", ARGV[4],
        "interval", ARGV[5],
        "app", ARGV[6])
    --始终返回成功
    return SUCCESS
end

-- 当前方法为修改配置
if method == "modify" then
    if(type(app) =='boolean' or app =
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值