Redis|基于Redis+Lua的滑动窗口式限流方案

分布式限流方案 概述 分布式锁 为什么要使用分布式锁
分布式锁应该具备的条件
分布式锁的实现方案
基于数据库实现 基于缓存redis实现 无论是分布式锁还是常规的锁,其目的都是在于:让多个线/进程在竞争某一个资源的时候,获取访问的权限。分布式锁无非是将线程竞争的层面拔高到进程竞争。
使用redis实现分布式锁的思想:

  • 获取锁的时候,使用set命令加锁,
Redis有个事务锁,就是如下的命令,这个命令的含义是将一个value设置到一个key中,如果不存在将会赋值并且设置超时时间为30秒,如何这个key已经存在了,则不进行设置。
SET key value NX PX 30000
这个事务锁很好的解决了两个单独的命令,一个设置set key value nx,即该key不存在的话将对其进行设置,另一个是expire key seconds,设置该key的超时时间。
因为redis在执行lua脚本的时候,确保了原子性——同一时刻只会有一个lua脚本被执行,并且执行的结果要么成功,要么失败;所以我们可以使用 set + expire + del命令来实现一个分布式锁。
实现的实现大致上是:
获取锁的时候,根据1,2步的返回结果,来判断是否操作成功
  1. 获取锁的时候,将竞争的信息设置为key,使用set命令添加 键值对
  2. 设置key的存活时间,避免死锁的发生
释放锁的时候
  1. 根据key来查找键值对,如果value匹配,则进行删除该键值对,根据返回值来判断释放锁是否成功
在介绍对应的脚本之前,我们先来介绍一下redis返回值和lua返回值的对应情况
首先明确一点: redis.call()函数的返回结果就是 Redis命令的执行结果;所以当编写lua脚本时,遇到不明确地方可以直接在redis终端执行对应的命令,看返回值是什么。
Redis命令的返回值有5种类型,redis.call函数会将这5种类型的回复转换成对应的Lua的数据类型,具体的对应规则如下(空结果nil比较特殊,其对应Lua的false)
redis返回值类型和Lua数据类型转换规则
redis返回值类型 Lua数据类型 整数回复 数字类型 字符串回复 字符串类型 多行字符串回复 table类型(数组形式) 状态回复 table类型(只有一个ok字段存储状态信息) 错误回复 table类型(只有一个err字段存储错误信息)

接下来编写lua伪代码
if(set key 操作成功) { if(更新存活时间成功) { return true } return false } else{ return false }

然后我们来看看相关的命令在redis终端上执行的返回值情况
Redis|基于Redis+Lua的滑动窗口式限流方案
文章图片

首先先讲讲返回的 OK是个啥,从上面的介绍的redis返回值类型来看,我们可能会猜测它返回的是字符串回复,也就是字符串OK,那我们就来写个脚本验证一下。
local res = redis.call('set', 'test_key', 'test_value') if res == 'OK' then return 'i am OK' else return 'i am not OK' end

Redis|基于Redis+Lua的滑动窗口式限流方案
文章图片

从结果来看,它并不是字符串OK,那它到底是什么呢?它其实是redis的状态回复,代表的是操作成功;而它对应的lua类型是:table类型(只有一个ok字段存储状态信息)。
弄完了返回值类型的确认,我们来来看看set命令的第二个问题,我刚刚故意多次执行了多次 set 命令,大伙看看它返回了个啥,每次set操作redis都返回 状态回复OK,这样又如何判断这个key本来就存在呢?从中我们可以看出,如果真的要使用set + expire命令,就必须在前面再加一个判断: 使用get命令判断这个key是否存在
调整后的伪代码
local v = redis.call(获取key) //说明key不存在,那么就进行设置锁 if(tostring(v) == 'false') { //。。。上述设置锁的操作 } else { return false }

具体的lua代码如下:

为什么这么做可以实现呢? 我们可以看看redis 的get命令返回情况,如果key存在则返回对应的value值,如果不存在返回的是空nil,对应lua就是false,所以我们tostring后与false进行比较,就能判定这个key是否存在。
总结来说,我们可以使用 get + set + expire 来做到加锁, del 来解锁;搭配实现分布式锁。
其实还有一个比较便捷的组合来实现分布式锁: setnx + del
Redis有个事务锁,就是如下的命令,这个命令的含义是将一个value设置到一个key中,如果不存在将会赋值并且设置超时时间为30秒(返回结果是:状态回复),如何这个key已经存在了,则不进行设置(返回结果是:nil空值)。
SET key value NX PX 30000
Redis|基于Redis+Lua的滑动窗口式限流方案
文章图片

当key已经存在的时候,则不进行设置并且返回的结果是nil,这个信息很有帮助,我们可以通过这个信息来实现获取锁的唯一性。
基于ZK实现 开源框架Redisson 滑动窗口式的限流方案 方案讲解
实现措施
参考:
https://www.cnblogs.com/huangqingshi/p/10290615.html
https://blog.csdn.net/qq_35042060/article/details/99680719
https://blog.csdn.net/weixin_43603149/article/details/107262478
https://blog.csdn.net/xlgen157387/article/details/79036337
【Redis|基于Redis+Lua的滑动窗口式限流方案】https://my.oschina.net/jrfcc/blog/866446

    推荐阅读