openresty開發系列37--nginx-lua-redis實現訪問頻率控制

openresty開發系列37--nginx-lua-redis實現訪問頻率控制

一)需求背景

在高併發場景下爲了防止某個訪問ip訪問的頻率太高,有時候會須要控制用戶的訪問頻次
在openresty中,能夠找到:
set_by_lua,rewrite_by_lua,access_by_lua,content_by_lua等方法。
那麼訪問控制應該是,access階段。
咱們用Nginx+Lua+Redis來作訪問限制主要是考慮到高併發環境下快速訪問控制的需求。

二)設計方案

咱們用redis的key表示用戶,value表示用戶的請求頻次,再利用過時時間實現單位時間;

如今咱們要求10秒內只能訪問10次frequency請求,超過返回403

1)首先爲nginx.conf配置文件,nginx.conf部份內容以下:

location /frequency {
    access_by_lua_file /usr/local/lua/access_by_limit_frequency.lua;
    echo "訪問成功";
}css

 

2)編輯/usr/local/lua/access_by_limit_frequency.luahtml

 

local function close_redis(red)  
    if not red then  
        return
    end 
    --釋放鏈接(鏈接池實現)  
    local pool_max_idle_time = 10000 --毫秒  
    local pool_size = 100 --鏈接池大小  
    local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)  
    if not ok then  
        ngx.say("set keepalive error : ", err)  
    end  
end

local function errlog(...)
    ngx.log(ngx.ERR, "redis: ", ...)
end

local redis = require "resty.redis"  --引入redis模塊
local red = redis:new()  --建立一個對象,注意是用冒號調用的

--設置超時(毫秒)  
red:set_timeout(1000) 
--創建鏈接  
local ip = "10.11.0.215"  
local port = 6379
local ok, err = red:connect(ip, port)
if not ok then  
    close_redis(red)
    errlog("Cannot connect");
    return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)   
end  

local key = "limit:frequency:login:"..ngx.var.remote_addr

--獲得此客戶端IP的頻次
local resp, err = red:get(key)
if not resp then  
    close_redis(red)
    return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) --redis 獲取值失敗
end 

if resp == ngx.null then   
    red:set(key, 1) -- 單位時間 第一次訪問
    red:expire(key, 10) --10秒時間 過時
end  

if type(resp) == "string" then 
    if tonumber(resp) > 10 then -- 超過10次
        close_redis(red)
        return ngx.exit(ngx.HTTP_FORBIDDEN) --直接返回403
    end
end

--調用API設置key  
ok, err = red:incr(key)  
if not ok then  
    close_redis(red)
    return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) --redis 報錯 
end  

close_redis(red)  

# 當redis設置了密碼時,須要用red:auth() 方法進行驗證前端

# vim /usr/local/lua/access_by_limit_frequency.lua

local function close_redis(red)
    if not red then
        return
    end

    local pool_max_idle_time = 10000
    local pool_size = 100
    local ok, err = red:set_keepalive(pool_max_idle_tme, pool_size)
    if not ok then
        ngx.say("set keepalive err : ", err)
    end
end

local function errlog(...)
    ngx.log(ngx.ERR, "redis: ", ...)
end

local redis = require "resty.redis"
local red = redis:new()

red:set_timeout(1000)
local ip = "10.11.0.215"
local port = 6379
local ok,err = red:connect(ip, port)
if not ok then
    close_redis(red)
    errlog("connot connect");
    return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end

local count, err = red:get_reused_times()
if 0 == count then ----新建鏈接,須要認證密碼
    ok, err = red:auth("redis123")
    if not ok then
        ngx.say("failed to auth: ", err)
        return
    end
elseif err then  ----從鏈接池中獲取鏈接,無需再次認證密碼
    ngx.say("failed to get reused times: ", err)
    return
end

local key = "limit:frequency:login: " ..ngx.var.remote_addr

--獲得此客戶端IP的頻次
local resp,err = red:get(key)
if not resp then
    close_redis(red)
    return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end

if resp == ngx.null then
    red:set(key, 1)
    red:expire(key, 100) -- 設置過時時間,即返回403的時間爲100秒
end

--ngx.say("connect ok")
--ngx.say("resp:",resp)
if type(resp) == "string" then
    if tonumber(resp) > 10 then
        close_redis(red)
        return ngx.exit(ngx.HTTP_FORBIDDEN)
    end
end

ok, err = red:incr(key)
if not ok then
    close_redis(red)
    return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end

close_redis(red)

請求地址:/frequency

10秒內 超出10次 ,返回403

10秒後,又能夠訪問了


在Nginx須要限速的location中引用上述腳本配置示例:

location /user/ {
    set $business "USER";
    access_by_lua_file /usr/local/openresty/nginx/conf/lua/access.lua;
    proxy_redirect off;
    proxy_set_header        Host $host;
    proxy_set_header        X-Real-IP $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass http://user_224/user/;
}

注:對於有大量靜態資源文件(如:js、css、圖片等)的前端頁面能夠設置只有指定格式的請求才進行訪問限速,示例代碼以下:

location /h5 {
if ($request_uri ~ .*\.(html|htm|jsp|json)) {
    set $business "H5";
    access_by_lua_file /usr/local/openresty/nginx/conf/lua/access.lua;
}
    proxy_redirect off;
    proxy_set_header        Host $host;
    proxy_set_header        X-Real-IP $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass http://h5_224/h5;
}

若是咱們想整個網站 都加上這個限制條件,那隻要把
access_by_lua_file /usr/local/lua/access_by_limit_frequency.lua;
這個配置,放在server部分,讓全部的location 適用就好了nginx

相關文章
相關標籤/搜索