一. 限制并发
场景1:按照 ip 限制其并发连接数
原理:lua_share_dict是nginx所有woker和lua runtime共享的,当一个请求进来,往lua_share_dict记录键值对ip地址:1,当请求完成时再-1,再来一个在+1,设置一个上限5,当超过5时则拒绝请求,一定要注意内部重定向的问题!
- 新建utils/limit_conn.lua模块
- -p D:devopenresty-1.19.9.1luautils
limit_conn.lua文件
-- utils/limit_conn.lua
local limit_conn = require "resty.limit.conn"
-- new 的第四个参数用于估算每个请求会维持多长时间,以便于应用漏桶算法(0.05是预估的并发处理时间)
-- 允许的最大并发为常规的8个,突发的2个,一共8+2=10个并发
local limit, limit_err = limit_conn.new("limit_conn_store", 8, 2, 0.05)
if not limit then
ngx.log(ngx.ERR, "failed to instantiate a resty.limit.conn object: ", limit_err) return ngx.exit(500)
end
local _M = {}
function _M.incoming()
-- 获取IP
local key = ngx.var.binary_remote_addr
local delay, err = limit:incoming(key, true)
if not delay then
if err == "rejected" then
return ngx.exit(503) -- 超过的请求直接返回503(被拒绝的请求直接返回503)
end
ngx.log(ngx.ERR, "failed to limit req: ", err)
return ngx.exit(500)
end
-- 请求连接等信息会被加到shared dict中,在ctx中记录下,因为后面要告知连接断开,以处理其他连接
if limit:is_committed() then
local ctx = ngx.ctx
ctx.limit_conn_key = key
ctx.limit_conn_delay = delay
end
-- 其实这里的 delay 是上面说的并发处理时间的整数倍,
-- 举个例子,每秒处理100并发,桶容量200个,当同时来500个并发,则200个拒绝请求
-- 100个正在被处理,然后200个进入桶中暂存,被暂存的这200个连接中,0-100个连接其实应该延后0.05秒处理,
-- 101-200个则应该延后0.05*2=0.1秒处理(0.05是上面预估的并发处理时间)
if delay >= 0.001 then
ngx.log(ngx.WARN, "delaying conn, excess ", delay, "s per binary_remote_addr by limit_conn_store")
ngx.sleep(delay)
end
return ngx.var.uri
end
function _M.leaving()
local ctx = ngx.ctx
local key = ctx.limit_conn_key
if key then
local latency = tonumber(ngx.var.request_time) - ctx.limit_conn_delay
local conn, err = limit:leaving(key, latency)
if not conn then
ngx.log(ngx.ERR,
"failed to record the connection leaving ",
"request: ", err)
end
end
return ngx.var.uri
end
return _M
注意 limit_conn_store 的大小需要足够放置限流所需的键值。
每个 $binary_remote_addr 大小不会超过 16 字节(IPv6 情况下),算上 lua_shared_dict 的节点大小,总共不到 64 字节。
100M 可以放 1.6M 个键值对
lua_shared_dict limit_conn_store 100m;
server {
listen 80;
server_name 192.168.8.188;
location /limit/conn {
default_type text/html;
access_by_lua_block {
-- limit_conn.lua放在D:\dev\openresty-1.19.9.1\lua\目录文件夹下
local limit_conn = require "utils.limit_conn"
-- 对于内部重定向或子请求,不进行限制。因为这些并不是真正对外的请求。
if ngx.req.is_internal() then
ngx.log(ngx.INFO,">> 内部重定向")
return
end
local uri = limit_conn.incoming()
ngx.log(ngx.INFO, string.format(">>> [%s]请求进来了!", uri))
}
content_by_lua_block {
-- 模拟请求处理时间,很重要,不加可能测试不出效果
-- 生产中没有请求是只返回一个静态的index.html的!
-- 模拟每个请求0.5秒处理完成
ngx.sleep(0.5)
ngx.say('/limit/conn')
}
log_by_lua_block {
local limit_conn = require "utils.limit_conn"
local uri = limit_conn.leaving()
ngx.log(ngx.INFO, string.format(">>> [%s]请求离开了!", uri))
}
}
}
重点在于这句话,模拟每个请求0.5秒处理完成
content_by_lua_block {
-- 模拟请求处理时间,很重要,不加可能测试不出效果
-- 生产中没有请求是只返回一个静态的index.html的!
-- 模拟每个请求0.5秒处理完成
ngx.sleep(0.5)
ngx.say('/limit/conn')
}
反向代理
location /limit/conn {
default_type text/html;
access_by_lua_block {
-- limit_conn.lua放在D:\dev\openresty-1.19.9.1\lua\目录文件夹下
local limit_conn = require "utils.limit_conn"
-- 对于内部重定向或子请求,不进行限制。因为这些并不是真正对外的请求。
if ngx.req.is_internal() then
ngx.log(ngx.INFO,">> 内部重定向")
return
end
local uri = limit_conn.incoming()
ngx.log(ngx.INFO, string.format(">>> [%s]请求进来了!", uri))
}
log_by_lua_block {
local limit_conn = require "utils.limit_conn"
local uri = limit_conn.leaving()
ngx.log(ngx.INFO, string.format(">>> [%s]请求离开了!", uri))
}
# 反向代理
proxy_pass http://172.17.0.3:8080;
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;
}