OpenResty+Lua限流实战

本文详细介绍了如何使用OpenResty结合Lua进行限流实战,包括限并发、时间窗请求数限制等场景,通过lua-resty-limit-traffic库实现不同类型的限流策略,如漏桶、令牌桶等,确保服务稳定运行。
摘要由CSDN通过智能技术生成

OpenResty+Lua限流实战

当业务量越来越大的时候,为了能保证服务的运行,限流是必不可少的!OpenResty是一个高性能网关

OpenResty® is a dynamic web platform based on NGINX and LuaJIT.

OpenResty = Nginx + Lua,Lua是高性能脚本语言,有着C语言的执行效率但是又比C简单,能很方便的扩展OpenResty 的功能。

Lua 是由巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组于1993年开发的一种轻量、小巧的脚本语言,用标准 C 语言编写,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

官网:http://www.lua.org/

实战环境

docker + CentOS8 + Openresty 1.17.8.2

Lua限流模块

https://github.com/openresty/lua-resty-limit-traffic

Lua的库一般都是小巧轻便且功能都具备,这个限流库核心文件一共就四个,几百行代码就能实现限流功能,Lua的其他库也是这样,比如redis的库还是Http的库,麻雀虽小五脏俱全!

环境准备

docker run -dit --name gw  --privileged centos /usr/sbin/init
docker exec -it gw bash 

在gw中

# 安装openresty
yum install -y yum-utils
yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
yum install -y openresty

# 安装工具等
yum install -y net-tools vim telnet git httpd

# Openresty自带了lua-resty-limit-traffic组件,如果没有带,下载到/usr/local/openresty/lualib/resty/limit/文件夹即可
# 下载lua-resty-limit-traffic组件
[ `ls /usr/local/openresty/lualib/resty/limit/ | wc -l` = 0 ] &&  echo '请安装限速组件' || echo '已经安装限速组件'
# 安装了请忽略
cd ~ && git clone https://github.com/openresty/lua-resty-limit-traffic.git
mkdir -p /usr/local/openresty/lualib/resty/limit/
cp  lua-resty-limit-traffic/lib/resty/limit/*.lua /usr/local/openresty/lualib/resty/limit/

# 启动openresy
openresty

限并发

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

参考:
https://moonbingbing.gitbooks.io/openresty-best-practices/content/ngx_lua/lua-limit.html
https://github.com/openresty/lua-resty-limit-traffic/blob/master/lib/resty/limit/conn.md
https://developer.aliyun.com/article/759299

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

  • OpenResty执行阶段 tag:lua执行流程;执行阶段;openresty执行流程
  • [为啥access_by_lua执行两次](参考文章/openresty why does access_by_lua_file call twice when accessing the root directory - Stack Overflow.md)

环境搭建

新建utils/limit_conn.lua模块

mkdir -p /usr/local/openresty/lualib/utils
cat > /usr/local/openresty/lualib/utils/limit_conn.lua <<EOF
-- utils/limit_conn.lua
local limit_conn = require "resty.limit.conn"

-- new 的第四个参数用于估算每个请求会维持多长时间,以便于应用漏桶算法
local limit, limit_err = limit_conn.new("limit_conn_store", 8, 2, 0.05)
if not limit then
    error("failed to instantiate a resty.limit.conn object: ", limit_err)
end

local _M = {}

function _M.incoming()
    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
        end
        ngx.log(ngx.ERR, "failed to limit req: ", err)
        return ngx.exit(500)
    end

    if limit:is_committed() then
        local ctx = ngx.ctx
        ctx.limit_conn_key = key
        ctx.limit_conn_delay = delay
    end

    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
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
end

return _M

EOF

重点在于这句话local limit, limit_err = limit_conn.new("limit_conn_store", 8, 2, 0.05),允许的最大并发为常规的8个,突发的2个,一共8+2=10个并发,详情参考https://github.com/openresty/lua-resty-limit-traffic/blob/master/lib/resty/limit/conn.md#new

被拒绝的请求直接返回503

if err == "rejected" then
    return ngx.exit(503) -- 超过的请求直接返回503
end

修改nginx配置文件

# 备份一下配置文件
cd /usr/local/openresty/nginx/conf/ && \cp nginx.conf nginx.conf.bak

# 添加配置
echo '' > /usr/local/openresty/nginx/conf/nginx.conf
vim /usr/local/openresty/nginx/conf/nginx.conf

添加如下内容

worker_processes  1;

events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    lua_code_cache on;    
   
    # 注意 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;
        location / {
            access_by_lua_block {
                local limit_conn = require "utils.limit_conn"
                -- 对于内部重定向或子请求,不进行限制。因为这些并不是真正对外的请求。
                if ngx.req.is_internal() then
                    ngx.log(ngx.INFO,">> 内部重定向")
                    return
                end
                limit_conn.incoming()
                ngx.log(ngx.INFO,">>> 请求进来了!")
            }
            content_by_lua_block {
                -- 模拟请求处理时间,很重要,不加可能测试不出效果
                -- 生产中没有请求是只返回一个静态的index.html的!
                ngx.sleep(0.5)
            }
            log_by_lua_block {
                local limit_conn = require "utils.limit_conn"
                limit_conn.leaving()
                ngx.log(ngx.INFO,">>> 请求离开了!")
            }
            
        }
    }
}

重点在于这句话,模拟每个请求0.5秒处理完成

content_by_lua_block {
	ngx.sleep(0.5)
}

注意在限制连接的代码里面,我们用 ngx.ctx 来存储 limit_conn_key。这里有一个坑。内部重定向(比如调用了 ngx.exec)会销毁 ngx.ctx,导致 limit_conn:leaving() 无法正确调用。 如果需要限连业务里有用到 ngx.exec,可以考虑改用 ngx.var 而不是 ngx.ctx,或者另外设计一套存储方式。只要能保证请求结束时能及时调用 limit:leaving() 即可。

重新加载配置文件

openresty -s reload 

测试

上面的配置是每个请求处理0.5秒,并发是10

  • 10个请求,并发为1
ab -n 10 -c 1  127.0.0.1/

# 请求全部成功,用时5s左右
Concurrency Level:      1
Time taken for tests:   5.012 seconds 
Complete requests:      10 
Failed requests:        0

  • 10个请求,并发为10
ab -n 10 -c 10  127.0.0.1/

# 请求全部成功,用时1.5s左右
Concurrency Level:      10
Time taken for tests:   1.505 seconds
Complete requests:      10
Failed requests:        0

  • 20个请求,并发为10,并发为10并不会触发限制条件,所以能成功!注意和下面并发11的区别!
ab -n 20 -c 10  127.0.0.1/

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在 2012 年的时候,我(作者)加入到奇虎 360 公司,为新的产品做技术选型。由于之前一直混迹在 Python 圈子 里面,也接触过 Nginx c 模块的高性能开发,一直想找到一个兼备 Python 快速开发和 Nginx c 模块高性能的产 品。看到 OpenResty 后,有发现新大陆的感觉。 于是我在新产品里面力推 OpenResty ,团队里面几乎没有人支持,经过几轮性能测试,虽然轻松击败所有的其 他方案,但是其他开发人员并不愿意参与到基于 OpenResty 这个“陌生”框架的开发中来。于是我一个人开始 了 OpenResty 之旅,刚开始经历了各种技术挑战,庆幸有详细的文档,以及春哥和邮件列表里面热情的帮 助,我成了团队里面 bug 最少和几乎不用加班的同学。 2014 年,团队进来了一批新鲜血液,他们都很有技术品味,先后都选择 OpenResty 来作为技术方向。我不再 是一个人在战斗,而另外一个新问题摆在团队面前,如何保证大家都能写出高质量的代码,都能对 OpenResty 有深入的了解?知识的沉淀和升华,成为一个迫在眉睫的问题。 我们选择把这几年的一些浅薄甚至可能是错误的实践,通过 Gitbook 的方式公开出来,一方面有利于团队自身的 技术积累,另一方面,也能让更多的高手一起加入,让 OpenResty 的使用变得更加简单,更多的应用到服务端 开发中,毕竟人生苦短,少一些加班,多一些陪家人。 这本书的定位是最佳实践,并不会对 OpenResty 做基础的介绍。想了解基础的同学,请不要看书,而是马上安 装 OpenResty ,把官方网站的 Presentations 浏览和实践几遍。 希望你能 enjoy OpenResty 之旅!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码狂魔v

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值