首页 > 其他分享 >OpenResty+Lua限流实战--resty.limit.conn(用于限制并发连接数)

OpenResty+Lua限流实战--resty.limit.conn(用于限制并发连接数)

时间:2022-09-19 13:55:45浏览次数:91  
标签:resty 请求 -- 并发 限流 limit ngx conn

限制并发

场景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

相关文章

  • 网络编程 一
    IP地址联网设备(电脑)每次介入网络,都会按照网络的规则,分配身份证号码,每个IP地址对应一个设备。每次进入网络IP地址未必相同。查看电脑IP地址windows:ipconfigIP地址就是I......
  • 第十五章 ACL安全策略
    一、ACL概述在Redis6之前的版本,我们只能使用requirepass参数给default用户配置登录密码,同一个redis集群的所有开发都共享default用户,难免会出现误操作把别人的key删掉或者......
  • 容器化 | 在 Kubernetes 上部署 RadonDB MySQL 集群
    容器化|在Kubernetes上部署RadonDBMySQL集群RadonDBMySQL是一款基于MySQL的开源、高可用、云原生集群解决方案。支持一主多从高可用架构,并具备安全、自动备份......
  • go之方法
    什么是方法方法其实就是一个函数,在func这个关键字和方法名中间加入一个特殊的接收器类型,接收器可以是结构体类型或者是非结构体类型,接收器是可以在方法的内部访问的......
  • US1MF-ASEMI贴片薄体封装二极管US1M
    编辑:llUS1MF-ASEMI贴片薄体封装二极管US1M型号:US1MF品牌:ASEMI封装:SMA-F正向电流:1A反向电压:1000V引线数量:2芯片个数:1芯片尺寸:50MIL漏电流:<5ua恢复时间:50~100ns......
  • 通过外网服务器搭建frp服务实现本地内网穿透
    背景:疫情期间在家远程办公,一般公司办公室开发测试环境是内网服务,为了远程使用公司的内网服务,那就需要做内网穿透,本文已阿里云服务搭建,其它云服务也是一样的。1、下载部署......
  • 如何从头开始使用 Docker 映像作为大型 ML 资产(例如模型、语料库)的资产库
    如何从头开始使用Docker映像作为大型ML资产(例如模型、语料库)的资产库MJT+StableDiffusion2022我开始处理大型模型和其他与ML相关的资产,并且需要一个解决方案......
  • 机器学习的机器学习?不,当我弄清楚发生了什么时,ML 笑了
    机器学习的机器学习?不,当我弄清楚发生了什么时,ML笑了fromsablediffusion这是一个鼓舞人心的介绍的占位符,关于机器学习有多神奇以及我有多想学习它。插入改变世界、帮......
  • kubernetes1.24环境搭建实验——docker准备
    Docker准备1、dockeryum源安装参考网址:docker-ce镜像-docker-ce下载地址-docker-ce安装教程-阿里巴巴开源镜像站(aliyun.com)curl-o/etc/yum.repos.d/docker-ce.rep......
  • PowerShell 函数
    Powershell中的函数使用关键字(function)声明,后面依次跟函数名称、左右大括号。函数将执行的代码包含在这些大括号中。#创建函数FunctionGetPSversion{......