redis+lua限流
nginx+lua(openresty/kong)
Lua 是一种轻量小巧的脚本语言,动态解释型语言 ,一个完整的Lua解释器不过200k,在目前所有脚本引擎中,Lua的速度是最快的。这一切都决定了Lua是作为嵌入式脚本的最佳选择。
主要特点是:程序代码级热更新,下载最新代码,实时更新,实时更新,不需要编译环节,比如Nginx
1: 比如游戏引擎领域的cocos2d-x、unity,由于引擎自身使用的c++或者c#作为编写语言,游戏前端发布后,发现bug或者发布新逻辑,都需要重新出包,周期过长,lua作为脚本语言,可以以资源的形式下载,重新加载运行,周期快,且效率损失有限
2:服务端使用,比较火的也就是nginx+lua的形式,后来整合成框架openresty,基本也是利用nginx的高性能+lua脚本的灵活性,逻辑修改之后只需要触发重新加载脚本就可以,开发运行效率都比较高,相比传统c++、java等需要重新编译部署,开发效率高很多
京东实现
- 有一个定时任务10秒更新限流配置(黑白名单和限流开关)
local ok, err = new_timer(delay, refresh) - 主要逻辑
-- 规则设置
-- 排队规则,即并发数控制+防刷规则,即请求频率控制(集群TPS,用户TPS,单IP的TPS[取不到用户的时候才生效])
-- AddOn:当前代码无法校验TrackID合法性,故全部使用IP限流,请注意第三个数字!!!(忽略第二个)
global_key_rules = {
['/cart/add_to_cart_ajax.html'] = {1000, -1, 50},
['/cart/fetch_cart_num.html'] = {1000, -1, 50},
['/cart/sync_cart.html'] = {500, -1, 30},
['/order/confirm_order.html'] = {500, -1, 30},
['/order/sync_order_cart.html'] = {500, -1, 30},
['/order/edit_user_address.html'] = {300, -1, 20},
['/order/sync_user_coupon.html'] = {500, -1, 30},
['/order/submit_order.html'] = {200, -1, 20}
}
-- 主要判断逻辑
-- key 为限制的资源
-- size 为TPS值
function limit(red, key, size)
--访问量+1
local count = red:incr(key)
-- 判断是否过期,如果过期,修改过期时间为1秒,ttl返回剩余的生存时间,key不
local ttl = red:ttl(key)
if ttl < 0 then
local time = red:expire(key,1)
end
-- 判断超过TPS,超过则限流
if count > size then
return false
end
return true
end
ttl是为防止某些key在未设置超时时间并长时间已经存在的情况下做的保护的判断;
Redis TTL 命令:
当 key 不存在时,返回 -2 。
当 key 存在但没有设置剩余生存时间时,返回 -1 。
否则,以毫秒为单位,返回 key 的剩余生存时间。
Redis Incr 命令:
将 key 中储存的数字值增一。
如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作
通过redis防止重复提交(无并发限流)
通过redis也可以在服务层做限流,缺点是此时流量已打到服务器,没有在nginx层做防护好。
不过,可以在服务层做些防重复提交的防护,示例如下:
例如:PC端查询订单列表,同一用户和相同查询条件100毫秒内只能查询一次
通过Redis 字符串命令中 SETEX key seconds value实现
setex 惟一标识 过期时间 当前时间
其中:key:为资源惟一标识=uid+param
seconds:过期时间=repeat_request_ttl
valule:当前时间
repeat_request_ttl: 每次查询的时间间隔=100
表示:同一用户和相同查询条件100毫秒内只参查询一次
if(key 存在){
获取key值的value=上次请求时间
if(上次请求时间<repeat_request_ttl){
请求频率过快
}
}
set key 当前时间 过期时间