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