openresty实现限流几个方式
在开发api网关时,有时我们为了防止爬虫等恶意爬取我们网站数据,我们可以做一些简单限流,比如静态拦截和动态拦截,所谓静态拦截就是限制某一个接口请求数。用户可以在系统上给其配置每秒最大调用量,超过限制则拒绝此接口。而动态拦截其实就是基于静态拦截进行改进,我们可以依据当前系统响应时间来动态调整限制阀值,如果响应较快可以把阀值调大一些,放过更多请求,反之则自动降低限流阀值。
我们使用openresty实现一些常用简单限流方式(此处用openresty自带lua-resty-limit-traffic模块).
1.限制ip并发连接数
-- 声明一个共享内存区域 name,以充当基于 Lua 字典 ngx.shared.<name> 的共享存储。共享内存总是被当前 Nginx 服务器实例中所有的 Nginx worker 进程所共享。
lua_shared_dict my_limit_conn_store 100m;
...
location / {
access_by_lua_block {
local limit_conn = require "resty.limit.conn"
-- 限制一个 ip 客户端最大 1 个并发请求
-- burst 设置为 0,如果超过最大的并发请求数,则直接返回503,
-- 如果此处要允许突增的并发数,可以修改 burst 的值(漏桶的桶容量)
-- 最后一个参数其实是你要预估这些并发(或者说单个请求)要处理多久,以便于对桶里面的请求应用漏桶算法
-- 如果我设置local lim, err = limit_conn.new("my_limit_conn_store", 50, 25, 0.5)则表示限制50个并发请求,和一个25个并发额外的突发请求, 也就是一个客户端访问50 +25 次之后就会抛出503
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
-- commit 为true 代表要更新shared dict中key的值,
-- false 代表只是查看当前请求要处理的延时情况和前面还未被处理的请求数
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
-- 如果请求连接计数等信息被加到shared dict中,则在ctx中记录下,
-- 因为后面要告知连接断开,以处理其他连接
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
-- 其实这里的 delay 肯定是上面说的并发处理时间的整数倍,
-- 举个例子,每秒处理100并发,桶容量200个,当时同时来500个并发,则200个拒掉
-- 100个在被处理,然后200个进入桶中暂存,被暂存的这200个连接中,0-100个连接其实应该延后0.5秒处理,
-- 101-200个则应该延后0.5*2=1秒处理(0.5是上面预估的并发处理时间)
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
-- 这个连接处理完后应该告知一下,更新shared dict中的值,让后续连接可以接入进来处理
-- 此处可以动态更新你之前的预估时间,但是别忘了把limit_conn.new这个方法抽出去写,
-- 要不每次请求进来又会重置
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://127.0.0.1:5000;
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;
}
只时单纯限制并发,超过允许要求并发量就会抛出403.
2.限制接口时间窗请求数
限制ip每分钟只能调用120次,允许在开始时候一次性放过120
lua_shared_dict my_limit_count_store 100m;
-- 用于重置时间 这里设置为1分钟
init_by_lua_block {
require "resty.core"
}
access_by_lua_block {
local limit_count = require "resty.limit.count"
-- rate: 10/min
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)
-- 如果请求数在限制范围内,则当前请求被处理的延迟(这种场景下始终为0,因为要么被处理要么被拒绝)和将被处理的请求的剩余数
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
}
3.平滑限制接口请求数
限制ip每分钟调用120次,(进行平滑处理请求,每秒放过2个请求)
lua_shared_dict my_limit_req_store 100m;
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
}
4.漏桶算法限流
限制ip每分钟稚嫩调用120次接口(平滑处理请求,每秒2个请求),超过部分进入桶中等待,(桶容量为60),如果桶也满了,则进行限流
lua_shared_dict my_limit_req_store 100m;
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
}
5.令牌桶算法限流
令牌桶其实可以看着是漏桶的逆操作,看我们对把超过请求速率而进入桶中的请求如何处理,如果是我们把这部分请求放入到等待队列中去,那么其实就是用了漏桶算法,但是如果我们允许直接处理这部分的突发请求,其实就是使用了令牌桶算法。
限制 ip
每分钟只能调用 120 次 接口(平滑处理请求,即每秒放过2个请求),但是允许一定的突发流量(突发的流量,就是桶的容量(桶容量为60),超过桶容量直接拒绝
这边只要将上面漏桶算法关于桶中请求的延时处理的代码修改成直接送到后端服务就可以了,这样便是使用了令牌桶
lua_shared_dict my_limit_req_store 100m;
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
-- 此方法返回,当前请求需要delay秒后才会被处理,和他前面对请求数
-- 此处忽略桶中请求所需要的延时处理,让其直接返送到后端服务器,
-- 其实这就是允许桶中请求作为突发流量 也就是令牌桶桶的原理所在
if delay >= 0.001 then
-- ngx.sleep(delay)
end
}
最新评论