openresty(nginx)+ lua + redis实现接口防刷

在上一篇文章防刷的配置完成之后

  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 还会被锁一段时间,但是对于阈值的设定必须要往大了设置,避免刷掉有效的请求

猜你喜欢

转载自blog.csdn.net/qq_30029665/article/details/130597733