基于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终端命令执行情况

首先先讲讲返回的 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

验证

从结果来看,它并不是字符串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

验证3

当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

https://my.oschina.net/jrfcc/blog/866446

猜你喜欢

转载自blog.csdn.net/weixin_46920376/article/details/110325360