一、針對大流量大併發網絡請求下,爲了保證服務的正常運行,不得不針對性採起限流的方式來解決大流量帶來的服務器的壓力。html
二、在目前項目中對於接入了不一樣的平臺,因此須要針對具體的平臺作相對應的限流,或者針對全部的平臺作ip白名單的限制,針對ip限流。nginx
三、如下代碼是經過平臺上報的ip對平臺作相對應的限流,主要使用的是redis+openresty來作處理;涉及代碼只作過基本的壓測,未投入實際生產web
相關代碼記錄以下:redis
1 -- 2 -- Created by IntelliJ IDEA. 3 -- User: tiemeng 4 -- Date: 2019/3/3 5 -- Time: 10:00 6 -- To change this template use File | Settings | File Templates. 7 -- 8 ngx.header.content_type = "text/html;charset=utf8" 9 10 11 -- redis配置 12 local redisConfig = { 13 redis_a = { 14 host = '127.0.0.1', 15 port = 6379, 16 pass = '', 17 timeout = 200, 18 database = 0, 19 } 20 } 21 22 local limitCount = 5 23 24 local time = 10000 -- 時間,單位爲毫秒 25 26 --[[ 27 獲取請求IP 28 ]] 29 local function getIp() 30 local headers = ngx.req.get_headers() 31 local ip = headers["X-REAL-IP"] or headers["X_FORWARDED_FOR"] or ngx.var.remote_addr or "0.0.0.0" 32 return ip 33 end 34 35 36 37 --[[ 38 鏈接redis 39 ]] 40 local function redisConn() 41 local redis = require('resty.redis_factory')(redisConfig) 42 local ok, redis_a = redis:spawn('redis_a') 43 if ok ~= nil then 44 return redis, redis_a 45 end 46 47 return redis, nil 48 end 49 50 51 --[[ 52 經過ip獲取平臺名稱 53 ]] 54 local function getPlatformNameByIp(ip) 55 local handle, redis = redisConn() 56 if redis == nil then 57 return nil 58 end 59 local platform = redis:hget('iplist', ip) 60 handle.destruct() 61 if platform ~= ngx.null then 62 return platform 63 end 64 ngx.log(ngx.ERR, "ip:" .. ip .. ",未在白名單中,禁止訪問") 65 return nil 66 end 67 68 local function forbid2() 69 local ip = getIp(); 70 -- 二、獲取當前ip是那個平臺 71 local platfromName = getPlatformNameByIp(ip) 72 if platfromName == nil then 73 return false 74 end 75 -- 三、獲取當前平臺的總數 76 local key = 'forbid_' .. platfromName 77 local handle, redis = redisConn() 78 if redis == nil then 79 return nil 80 end 81 local curTime = ngx.now() * 1000 82 local ok, err = redis:eval([[ 83 local len = redis.call('llen',KEYS[1]) 84 if len < 10 then 85 redis.call('rpush',KEYS[1],ARGV[2]) 86 return true 87 end 88 local times = redis.call('lrange',KEYS[1],0,0) 89 local timeSum = tonumber(times[1])+tonumber(ARGV[1]) 90 if timeSum > tonumber(ARGV[2]) then 91 return false 92 end 93 redis.call('lpop',KEYS[1]) 94 redis.call('rpush',KEYS[1],ARGV[2]) 95 return true 96 ]], 1, key, time, curTime) 97 handle.destruct() 98 return ok 99 end 100 101 102 103 if forbid2() ~= 1 then 104 ngx.exit(403) 105 end
測試中出現的問題:服務器
起初是使用如下代碼實現的,從代碼表面看是沒有任何問題,可是在壓力測試下併發數達到50的時候就會出現限流失效;出現失效的主要緣由是,在redis中list的操做並非所謂的原子操做,因此經過翻閱相關資料瞭解到,能夠在redis中嵌入相關的lua腳本,能夠達到原子的操做;因此在一開始的代碼82-96行使用redis的eval函數來調用lua的腳本,已達到原子操做的要求;修改後通過壓測後達到相對用的效果網絡
1 local function isForbid() 2 local ip = getIp(); 3 -- 二、獲取當前ip是那個平臺 4 local platfromName = getPlatformNameByIp(ip) 5 if platfromName == nil then 6 return false 7 end 8 -- 三、獲取當前平臺的總數 9 local key = 'forbid_' .. platfromName 10 local handle, redis = redisConn() 11 if redis == nil then 12 return nil 13 end 14 -- 四、校驗是否超過限制 15 local len = redis:llen(key) 16 17 if len < limitCount then 18 redis:rpush(key, ngx.now() * 1000) 19 handle.destruct() 20 return true 21 end 22 local times = redis:lrange(key, 0, 0) 23 if times == ngx.null then 24 return false 25 end 26 27 if tonumber(times[1]) + time >= ngx.now() * 1000 then 28 ngx.log(ngx.ERR, "forbid_platform :" .. platfromName) 29 return false 30 end 31 os.execute("sleep " .. 1) 32 redis:lpop(key) 33 redis:rpush(key, ngx.now() * 1000) 34 handle.destruct() 35 return true 36 end
nginx部分相關配置:併發
http { include mime.types; default_type application/octet-stream; lua_package_path '/websys/nginx/lua/?.lua;/websys/lualib/?/init.lua;;'; #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"'; #access_log logs/access.log main; sendfile on; #tcp_nopush on; #keepalive_timeout 0; #keepalive_timeout 65; # resolver 127.0.0.1 192.168.1.1 8.8.8.8; #gzip on; access_by_lua_file lua/access.lua;
此限流主要在openresty的access層作了限制,主要引入方式爲上方紅色字體app
起初想的是經過redis的incr來實現針對ip作限流,可是其中會有鍵失效的時間問題;若是使用incr作相對應的操做,若是10秒鐘請求量爲50的話,是無法保證時間的連續性;因此最後採用了經過list來保證了時間的連續性;tcp
本文主要記錄相關的問題及知識點,如簡述和實現方式有問題歡迎吐槽函數