简介
在现代系统中,限流是一种重要的机制,用于控制服务端的流量并保护系统免受恶意攻击或请求泛滥的影响。本文将介绍如何利用 Redis 和 Lua 结合实现高效的限流功能。
一、什么是限流
限流指的是对系统中的请求进行控制和调节,确保系统在承受压力时能够正常运行,不会因为突然的大量请求导致系统宕机或服务质量下降。限流在系统中具有至关重要的作用,可以平稳地处理请求流量,防止系统过载。
二、什么是Redis
Redis是一个高性能的内存数据库,具有快速的读写速度和丰富的数据结构支持。在限流场景中,Redis可以作为一个高效的缓存和计数工具,帮助实现限流功能。
三、什么是lua
Lua是一种轻量级的脚本语言,它具有简洁的语法和高效的执行性能。
四、限流要用lua+Redis而不用Java、Python 等语言呢?
-
性能和原子性: Lua脚本可以在Redis服务器端原子性地执行多个命令,避免了多次网络通信的开销,提高了性能和原子性。相比之下,用Java或Python实现的限流算法需要多次与Redis进行通信,性能相对较低。
-
便捷性: Lua脚本可以直接在Redis服务器端执行,无需额外部署其他语言的运行环境,更加灵活和便捷。
-
Redis支持: Redis天然支持Lua脚本,可以直接执行,不需要额外的配置和插件。而如果使用Java或Python,需要额外的库或框架来与Redis进行交互。
五、限流算法选择
5.1 令牌桶算法
令牌桶算法中,存在一个令牌桶,可以往桶中添加令牌。每个令牌代表一个处理请求的许可。如果请求到了,桶中有足够的令牌,择允许处理该请求,同时消耗相应数量的令牌。如果桶中没有足够的令牌,则拒接该请求或将请求放入队列等待令牌。
5.2 漏桶算法
漏桶算法中,存在一个固定容量的漏桶,以固定的速率处理请求。如果请求到来,放入漏桶中,如果漏桶已满,则拒绝请求,如果漏桶未满,则按照固定的速率处理请求。
六、lua+Redis实现令牌桶算法
local key = KEYS[1] -- 获取传入lua脚本的第一个keys参数,用作存储令牌数目的键名
local limit = tonumber(ARGV[1]) -- 将传入lua脚本的第一个ARGV参数转换为整数,表示桶的容量
local current = tonumber(redis.call('get', key) or "0")
-- 通过Redis的GET命令获取当前令牌桶中的令牌数量,如果没有获取到则默认为0,并将其转换为整数。
if current + 1 > limit then -- 判断当前令牌桶中的令牌数量加1后是否超过阈值return 0 -- 超过表示请求被限流,返回0
elseredis.call('INCR', key) -- 通过Redis的INCR命令将令牌桶中的数量加1,表示通过了一个请求redis.call('EXPIRE', key, ARGV[2]) -- 设置令牌桶的过期时间为ARGV 参数中指定的时间return 1 -- 返回1,表示通过限流检查
end
七、lua+Redis实现漏桶算法
local key = KEYS[1] -- 限流器的键名
local capacity = tonumber(ARGV[1]) -- 漏桶的容量
local rate = tonumber(ARGV[2]) -- 漏桶的速率
local now = tonumber(ARGV[3]) -- 当前时间戳
local interval = 1 / rate -- 时间间隔,即每个请求需要等待的时间
local water = tonumber(redis.call('get', key) or "0") -- 获取漏桶中的水滴数量
local lastLeakTime = tonumber(redis.call('get', key .. ':last_leak_time') or "0") -- 上次漏水的时间戳
local elapsed = math.max(0, now - lastLeakTime) -- 计算当前时间与上次漏水的时间间隔
water = water - elapsed * rate -- 根据时间间隔计算漏水数量,并更新漏桶中的水滴数量
if water < 0 thenwater = 0 -- 水滴数量不会低于0
end
water = water + 1 -- 新的请求加入漏桶中
if water > capacity thenreturn 0 -- 漏桶已满,拒绝请求
elseredis.call('set', key, water) -- 更新漏桶中的水滴数量redis.call('set', key .. ':last_leak_time', now) -- 更新上次漏水的时间戳return interval -- 返回请求需要等待的时间间隔
end