nginx-ingress + redis 限流
简介
下面的代码会在redis里面保存固定长度一个队列
每次请求向插入一个时间值
比较最新值和最老值之间的时间差(单位是秒)
如果时间差小于某个值说明请求过快
redis.lua准备
查看一下nginx-ingress里面是否存在redis.lua, 默认文件位置: /usr/local/lib/lua/resty/redis.lua
如果没有需要挂载一个redis.lua
redis.lua可以从openresty项目里拿, 链接地址: https://github.com/openresty/lua-resty-redis/blob/master/lib/resty/redis.lua
ingress配置
nginx.ingress.kubernetes.io/server-snippet: |
access_by_lua_block {
local header = ngx.req.get_headers()
if header.token then
local LIMIT = 100
local DELAY = 10
local red = require "resty.redis"
local redis = red:new()
redis:set_timeout(1000)
local ok, err = redis:connect("redis", 6679)
if not ok then
ngx.status = 500
ngx.say("<h1>系统开小差了</h1>")
return
end
local res, err = redis:auth("123456")
if not res then
ngx.status = 500
ngx.say("<h1>系统开小差了</h1>")
return
end
local now = ngx.now()
local ok, err = redis:eval('local oldest = redis.call("lindex", ARGV[1], -1);if oldest then if redis.call("llen", ARGV[1]) >= tonumber(KEYS[1]) then if (ARGV[2] - oldest) < tonumber(KEYS[2]) then return nil end end end;redis.call("lpush", ARGV[1], ARGV[2]);redis.call("expire", ARGV[1], KEYS[1]);redis.call("ltrim", ARGV[1], 0, KEYS[1]); return 1', 2, LIMIT-1, DELAY, "limit:"..ngx.md5(header.token), now)
if ok ~= 1 then
ngx.status = 519
ngx.say("<h1>系统繁忙</h1>")
redis:set_keepalive(10000, 100)
return
end
redis:set_keepalive(10000, 100)
end
}
代码解释
LIMIT 是限制请求的数量, redis保存的队列长度等于LIMIT, 队列长度过长会占用redis的内存
DELAY 表示在多少秒内接受LIMIT个请求
redis:eval 执行了一段lua代码
KEYS[1] KEYS[2] 分别表示 LIMIT 和 DELAY
ARGV[1] 表示限速的key, 上面代码中是取的 header中的token, 加上 "limit:" 前缀当作redis的key
ARGV[2] 是当前的时间, 比如: 1604731480.085
lindex 取出队列最早的元素oldest(其实就是队列中最早的时间), 拿当前时间-oldest 如果小于 DELAY 说明访问过快
如果没有达到限速阈值, 使用 lpush 把当前时间放到队列中, 然后使用 ltrim 把队列裁剪成LIMIT长度, 其实就是去除了最老的值