限制并发
场景1:按照 ip 限制其并发连接数
原理:lua_share_dict是nginx所有woker和lua runtime共享的,当一个请求进来,往lua_share_dict记录键值对ip地址:1,当请求完成时再-1,再来一个在+1,设置一个上限5,当超过5时则拒绝请求,一定要注意内部重定向的问题!
1. 新建utils/limit_conn.lua模块
mkdir -p D:\dev\openresty-1.19.9.1\lua\utils
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) 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
重点在于这句话local limit, limit_err = limit_conn.new("limit_conn_store", 8, 2, 0.05),允许的最大并发为常规的8个,突发的2个,一共8+2=10个并发
2. 修改nginx配置文件
# 注意 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') }
注意在限制连接的代码里面,我们用 ngx.ctx 来存储 limit_conn_key。这里有一个坑。内部重定向(比如调用了 ngx.exec)会销毁 ngx.ctx,导致 limit_conn:leaving() 无法正确调用。 如果需要限制连接业务里有用到 ngx.exec,可以考虑改用 ngx.var 而不是 ngx.ctx,或者另外设计一套存储方式。只要能保证请求结束时能及时调用 limit:leaving() 即可。
3.重新加载配置文件
nginx.exe -s reload
4.测试
上面的配置是每个请求处理0.5秒,并发是10
a. 10个请求,并发为1
ab -n 10 -c 1 http://localhost/limit/conn # 请求全部成功,用时5s左右 Concurrency Level: 1 Time taken for tests: 5.012 seconds Complete requests: 10 Failed requests: 0
b. 10个请求,并发为10
ab -n 10 -c 10 http://localhost/limit/conn # 请求全部成功,用时1.5s左右 Concurrency Level: 10 Time taken for tests: 1.505 seconds Complete requests: 10 Failed requests: 0
c. 20个请求,并发为10,并发为10并不会触发限制条件,所以能成功!注意和下面并发11的区别!
ab -n 20 -c 10 http://localhost/limit/conn # 请求全部成功,用时2s左右 Concurrency Level: 10 Time taken for tests: 2.005 seconds Complete requests: 20 Failed requests: 0
d. 22个请求,并发为11
ab -n 22 -c 11 http://localhost/limit/conn # 11个成功,11个失败 Concurrency Level: 11 Time taken for tests: 1.506 seconds Complete requests: 22 Failed requests: 11 Non-2xx responses: 11 # HTTP状态非2xx的有11个,说明限并发成功(只有有非2xx的返回才会显示这句话)
22个请求,并发为11 重点解释一下:
1.并发不是qps,并发11不是说第一秒发11个请求,然后第二秒再发送11个请求,而是发完第一波紧接着发第二波,每一波的间隔时间不一定是1秒,下面的1.506 seconds就能看出来,按理应该是2s但是并不是
2.第一波11个请求发送过去了,但是只能处理10个,所以成功了10个,紧接着第二波11个请求发过去了,但是第一波大部分未处理完成所以第二波的都失败了,也有处理完成了的可以接着处理,所以至少会成功10个,下面显示的是11个
3.此处的大量失败应该是并发超过了10,触发了限制条件让nginx worker线程睡眠了,所以导致后面的请求大量失败
4.-- 触发限制条件
if delay >= 0.001 then ngx.sleep(delay) -- ngx worker睡眠 end
标签:resty,请求,--,并发,限流,limit,ngx,conn From: https://www.cnblogs.com/linjiqin/p/16707462.html