本文转载自: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
也就是相似此处对桶中请求是否作延迟处理的两种方案,也就是分别对应的漏桶和令牌桶两种算法