在上一篇文章防刷的配置完成之后
nginx 使用自带的 ngx_http_limit_req_module 模块实现接口防刷
使用了一段时间之后发现体验感和可配置性不是很高,不太灵活,所以想着尝试尝试领导说过的 lua 脚本实现防刷和限流
其实防刷和限流一直是两个概念,之前还是搞混淆了
防刷主要还是针对爬虫或者是恶意请求,对于超过我们限定的规则的请求直接返回异常,然后禁封这个 IP(禁封一段时间或者永久禁封)
而限流是指比如是在搞活动期间流量突增,假设我们的后端的服务器有个接口最大能抗住 1000QPS,但是在这个时间点突然进来了 2000QPS,请求经过限流之后,按照请求的时间先后,先放过我们设定 900 个请求,其余的请求会在队列里等待之前的请求处理完成,而不是像防刷一样直接返回异常。当然这个等待的时间不能太久了,不然超过了等待时间,还是会返回异常,或者客户端就自己主动放弃请求了。
老规矩,我们还是以实验结果说话,看我操作
OpenResty 的安装
由于 nginx 本身没有 lua 相关的模块,还需要自己去安装 lua 相关的组件,在了解 lua 的期间又了解到了 OpenResty 这个强大的工具。OpenResty 是集成了 nginx 和 各种第三方模块(贴别的重量级的lua/luajit)的软件平台,所以我们就可以使用它来进行相关的功能开发
OpenResty 官方下载地址: OpenResty - 下载
解压我们下载的源码包,然后编译安装
[root@jiangexing src]# yum install -y readline-devel pcre-devel openssl-devel gcc #安装依赖和编译用的包
[root@jiangexing src]# tar -xzf openresty-1.15.8.1.tar.gz
[root@jiangexing src]# cd openresty-1.15.8.1/
[root@jiangexing openresty-1.15.8.1]# ./configure --prefix=/usr/local/openresty --with-luajit --without-http_redis2_module --with-http_iconv_module --with-http_stub_status_module --with-http_ssl_module --with-http_v2_module --with-http_realip_module --with-file-aio --with-threads
[root@jiangexing openresty-1.15.8.1]# gmake && gmake install
#./configure 所带的模块除了加上 –with-luajit –without-http_redis2_module 和 lua 开发相关的,其余的和 nginx 一致就好了
[root@jiangexing openresty-1.15.8.1]# cd /usr/local/openresty
[root@jiangexing openresty-1.15.8.1]# ls
进到 nginx 目录之后就是我们熟悉的了,还是原来的味道
关于 nginx 配置文件的配置,就不多说了,由原先的配置搬过来就可以使用了
redis 的安装我这边就先不讲了
lua 防刷配置实操
首先我们来做防刷
nginx 配置
在需要做限制的接口(当然也可以对所有的接口生效)的 location 节点下加入引用的 lua 脚本(目录可以自己定义,能读取到就可以)
test.lua
--设置限量数据---
local worktime = 20 --设置多少秒的工作时间秒
local worknu = 20 --设置单个 IP 工作时间内的访问次数限制
local blocktime = 20 --超过限制后锁定的时间
--redis 连接池设置------
local function close_redis(red)
if not red then
return
end
local pool_max_idle_time = 10000
local pool_size = 500
local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
local log = ngx_log
if not ok then
log(ngx_ERR, "set redis keepalive error : ", err)
end
end
-- 连接 Redis--------
local redis = require('resty.redis')
local red = redis.new()
red:set_timeout(1000) --连接 Redis 超时时间
local ip = "127.0.0.1"
local port = "6379"
local ok, err = red:connect(ip,port)
if not ok then
ngx.log(ngx.ERR, "redis_conn_status:", ok )
ngx.log(ngx.ERR, "redis_conn_err:", err )
else
red:auth('123456')
red:select('0')
local clientIP = ngx.req.get_headers()["X-Real-IP"]
if clientIP == nil then
clientIP = ngx.req.get_headers()["x_forwarded_for"]
end
if clientIP == nil then
clientIP = ngx.var.remote_addr
end
local suoKey = "suo"..clientIP
local incrKey = "incr"..clientIP
local is_block,err = red:get(suoKey)
if tonumber(is_block) == 1 then
ngx.exit(503)
local ok,err = close_redis(red)
else
local start_time, err = red:get("time"..clientIP)
local inc = red:incr(incrKey)
local now_time = os.time()
if start_time == ngx.null then
res, err = red:set("time"..clientIP, now_time)
res, err = red:set(incrKey, 1)
else
local cost_time = now_time - start_time
if tonumber(cost_time) >= tonumber(worktime) then
local res, err = red:set("time"..clientIP, now_time)
local res, err = red:del(incrKey)
else
if inc >= worknu - 1 then
res, err = red:set(suoKey,1)
res, err = red:expire(suoKey, blocktime)
end
end
end
local ok,err = close_redis(red)
end
end
注解:
local worktime = 10 –设置多少秒的工作时间秒
local worknu = 20 –设置单个 IP 工作时间内的访问次数限制
local blocktime = 20 –超过限制后锁定的时间
关于这三个参数的 设定 需要自己好好的斟酌
当然还有一个值得注意的点,就是在当我们的 redis 宕机之后,就要放行所有的请求,本文已经实现
下面进行测验环节
10 秒内请求不超过 20 次的结果
10 秒内请求如下两次
ab -n 18 -c 1 http://49.234.105.172/abc/abc.html
的结果,第一次出现的结果和上面(上图)一致,第二次的结果如下
10 秒内请求如下一次
ab -n 25 -c 1 http://49.234.105.172/abc/abc.html
结果如下
我们的防刷已经完成了,超量的请求就到不了后端了,而且这个 IP 还会被锁一段时间,但是对于阈值的设定必须要往大了设置,避免刷掉有效的请求