一. 限制并发

场景1:按照 ip 限制其并发连接数

原理:lua_share_dict是nginx所有woker和lua runtime共享的,当一个请求进来,往lua_share_dict记录键值对ip地址:1,当请求完成时再-1,再来一个在+1,设置一个上限5,当超过5时则拒绝请求,一定要注意内部重定向的问题!

  1. 新建utils/limit_conn.lua模块
  2. -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;
}
最后修改:2023 年 12 月 20 日 09 : 45 PM
如果觉得我的文章对你有用,请随意赞赏