本文轉載自:http://www.itzh.org/2018/01/14/openresty_rate_limiter_methods/node
在開發 api
網關的時,作過一些簡單的限流,好比說靜態攔截和動態攔截;靜態攔截說白了就是限流某一個接口在必定時間窗口的請求數。用戶能夠在系統上給他們的接口配置一個每秒最大調用量,若是超過這個限制,則拒絕服務此接口,而動態攔截其實也是基於靜態攔截進行改進,咱們能夠依據當前系統的響應時間來動態調整限流的閾值,若是響應較快則能夠把閾值調的大一些,放過更多請求,反之則自動下降限流閾值,只使少許請求經過。nginx
其實這就是一個很簡單的限流方式。可是由於這些場景在咱們開發的時候常常遇到,因此在這裏用 OpenResty
大概實現一些常見的限流方式。(此處使用OpenResty1.13.6.1
版本自帶lua-resty-limit-traffic
模塊 ,實現起來更爲方便)算法
限制接口總併發數
場景:
按照 ip
限制其併發鏈接數後端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
|
lua_shared_dict my_limit_conn_store 100m; ... location /hello { access_by_lua_block { local limit_conn = require "resty.limit.conn" local lim, err = limit_conn.new("my_limit_conn_store", 1, 0, 0.5) if not lim then ngx.log(ngx.ERR, "failed to instantiate a resty.limit.conn object: ", err) return ngx.exit(500) end
local key = ngx.var.binary_remote_addr local delay, err = lim:incoming(key, true) if not delay then if err == "rejected" then return ngx.exit(503) end ngx.log(ngx.ERR, "failed to limit req: ", err) return ngx.exit(500) end
if lim:is_committed() then local ctx = ngx.ctx ctx.limit_conn = lim ctx.limit_conn_key = key ctx.limit_conn_delay = delay end
local conn = err if delay >= 0.001 then ngx.sleep(delay) end }
log_by_lua_block { local ctx = ngx.ctx local lim = ctx.limit_conn if lim then local key = ctx.limit_conn_key local conn, err = lim:leaving(key, 0.5) if not conn then ngx.log(ngx.ERR, "failed to record the connection leaving ", "request: ", err) return end end } proxy_pass http://10.100.157.198:6112; proxy_set_header Host $host; proxy_redirect off; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_connect_timeout 60; proxy_read_timeout 600; proxy_send_timeout 600; }
|
說明:
其實此處沒有設置 burst
的值,就是單純的限制最大併發數,若是設置了 burst
的值,而且作了延時處理,其實就是對併發數使用了漏桶算法,可是若是不作延時處理,其實就是使用的令牌桶算法。參考下面對請求數使用漏桶令牌桶的部分,併發數的漏桶令牌桶實現與之類似api
限制接口時間窗請求數
場景:
限制 ip
每分鐘只能調用 120 次 /hello
接口(容許在時間段開始的時候一次性放過120個請求)服務器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
lua_shared_dict my_limit_count_store 100m; ...
init_by_lua_block { require "resty.core" } ....
location /hello { access_by_lua_block { local limit_count = require "resty.limit.count"
local lim, err = limit_count.new("my_limit_count_store", 120, 60) if not lim then ngx.log(ngx.ERR, "failed to instantiate a resty.limit.count object: ", err) return ngx.exit(500) end
local key = ngx.var.binary_remote_addr local delay, err = lim:incoming(key, true) if not delay then if err == "rejected" then return ngx.exit(503) end
ngx.log(ngx.ERR, "failed to limit count: ", err) return ngx.exit(500) end }
proxy_pass http://10.100.157.198:6112; proxy_set_header Host $host; proxy_redirect off; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_connect_timeout 60; proxy_read_timeout 600; proxy_send_timeout 600; }
|
平滑限制接口請求數
場景:
限制 ip
每分鐘只能調用 120 次 /hello
接口(平滑處理請求,即每秒放過2個請求)併發
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
lua_shared_dict my_limit_req_store 100m; ....
location /hello { access_by_lua_block { local limit_req = require "resty.limit.req" -- 這裏設置rate=2/s,漏桶桶容量設置爲0,(也就是來多少水就留多少水) -- 由於resty.limit.req代碼中控制粒度爲毫秒級別,因此能夠作到毫秒級別的平滑處理 local lim, err = limit_req.new("my_limit_req_store", 2, 0) if not lim then ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err) return ngx.exit(500) end
local key = ngx.var.binary_remote_addr local delay, err = lim:incoming(key, true) if not delay then if err == "rejected" then return ngx.exit(503) end ngx.log(ngx.ERR, "failed to limit req: ", err) return ngx.exit(500) end }
proxy_pass http: proxy_set_header Host $host; proxy_redirect off; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_connect_timeout 60; proxy_read_timeout 600; proxy_send_timeout 600; }
|
漏桶算法限流
場景:
限制 ip
每分鐘只能調用 120 次 /hello
接口(平滑處理請求,即每秒放過2個請求),超過部分進入桶中等待,(桶容量爲60),若是桶也滿了,則進行限流ide
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
lua_shared_dict my_limit_req_store 100m; ....
location /hello { access_by_lua_block { local limit_req = require "resty.limit.req" local lim, err = limit_req.new("my_limit_req_store", 2, 60) if not lim then ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err) return ngx.exit(500) end
local key = ngx.var.binary_remote_addr local delay, err = lim:incoming(key, true) if not delay then if err == "rejected" then return ngx.exit(503) end ngx.log(ngx.ERR, "failed to limit req: ", err) return ngx.exit(500) end if delay >= 0.001 then ngx.sleep(delay) end }
proxy_pass http://10.100.157.198:6112; proxy_set_header Host $host; proxy_redirect off; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_connect_timeout 60; proxy_read_timeout 600; proxy_send_timeout 600; }
|
令牌桶算法限流
令牌桶其實能夠看着是漏桶的逆操做,看咱們對把超過請求速率而進入桶中的請求如何處理,若是是咱們把這部分請求放入到等待隊列中去,那麼其實就是用了漏桶算法,可是若是咱們容許直接處理這部分的突發請求,其實就是使用了令牌桶算法。ui
場景:
限制 ip
每分鐘只能調用 120 次 /hello
接口(平滑處理請求,即每秒放過2個請求),可是容許必定的突發流量(突發的流量,就是桶的容量(桶容量爲60),超過桶容量直接拒絕lua
這邊只要將上面漏桶算法關於桶中請求的延時處理的代碼修改爲直接送到後端服務就能夠了,這樣即是使用了令牌桶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
lua_shared_dict my_limit_req_store 100m; ....
location /hello { access_by_lua_block { local limit_req = require "resty.limit.req"
local lim, err = limit_req.new("my_limit_req_store", 2, 0) if not lim then ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err) return ngx.exit(500) end
local key = ngx.var.binary_remote_addr local delay, err = lim:incoming(key, true) if not delay then if err == "rejected" then return ngx.exit(503) end ngx.log(ngx.ERR, "failed to limit req: ", err) return ngx.exit(500) end if delay >= 0.001 then end }
proxy_pass http://10.100.157.198:6112; proxy_set_header Host $host; proxy_redirect off; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_connect_timeout 60; proxy_read_timeout 600; proxy_send_timeout 600; }
|
說明:
其實nginx
的ngx_http_limit_req_module
這個模塊中的delay
和nodelay
也就是相似此處對桶中請求是否作延遲處理的兩種方案,也就是分別對應的漏桶和令牌桶兩種算法