首页 > 其他分享 >OpenResty中如何实现,按QPS、时间范围、来源IP进行限流

OpenResty中如何实现,按QPS、时间范围、来源IP进行限流

时间:2024-02-25 11:44:05浏览次数:24  
标签:IP req local Nginx 限流 limit OpenResty ngx

OpenResty是一个基于Nginx与Lua的高性能Web平台,它通过LuaJIT在Nginx中运行高效的Lua脚本和模块,可以用来处理复杂的网络请求,并且支持各种流量控制和限制的功能。

近期研究在OpenResty中如何实现,按QPS、时间范围、来源IP进行限流,以及动态更新限流策略。今天将实现方案分享给大家。

一、在OpenResty中如何实现,按QPS、时间范围、来源IP进行限流

使用OpenResty进行限流的几种常见方法:

  1. 按QPS(每秒查询率)限流:
    使用ngx_http_limit_req_module模块,可以限制每个客户端的请求速率。这个模块使用漏桶算法来控制请求的速率。

    在Nginx配置文件中,你可以这样设置:

    http {
        limit_req_zone $binary_remote_addr zone=mylimit:10m rate=1r/s;
    
        server {
            location / {
                limit_req zone=mylimit burst=5 nodelay;
            }
        }
    }
    

    上面的配置定义了一个名为mylimit的区域,它根据客户端的IP地址来限流,并且设置了每秒可以处理的请求数(rate)为1。burst参数定义了可以累积的最大请求数,而nodelay表示不对超出速率的请求进行延迟处理。

  2. 按时间范围限流:
    如果你想在特定时间范围内限流,你可能需要编写一些Lua脚本来实现这个逻辑。例如,你可以使用lua-resty-limit-traffic库的limit.req模块,并结合时间判断逻辑:

    local limit_req = require "resty.limit.req"
    local lim, err = limit_req.new("my_limit_req_store", 2, 0)
    if not lim then
        ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err)
        return ngx.exit(500)
    end
    
    local key = ngx.var.binary_remote_addr
    local delay, err = lim:incoming(key, true)
    
    -- 检查当前时间是否在限流时间范围内
    local current_hour = os.date("%H")
    if current_hour >= "09" and current_hour <= "18" then
        -- 在工作时间进行限流
        if delay then
            if delay >= 0.001 then
                -- 延迟处理
                ngx.sleep(delay)
            end
        else
            if err == "rejected" then
                -- 请求超出速率限制
                return ngx.exit(503)
            end
            ngx.log(ngx.ERR, "failed to limit req: ", err)
            return ngx.exit(500)
        end
    end
    
  3. 按来源IP限流:
    使用ngx_http_limit_conn_module模块,可以限制同时处理的连接数。如果你想根据来源IP地址进行限流,可以像这样配置:

    http {
        limit_conn_zone $binary_remote_addr zone=addr:10m;
    
        server {
            location / {
                limit_conn addr 3;
            }
        }
    }
    

    这个配置限制了每个IP地址同时只能有3个活跃连接。

        在实际部署时,需要根据自己的业务需求调整这些配置参数。需要注意的是,对于复杂的限流规则,可能需要结合多个Nginx模块和Lua脚本来实现。

        而且,由于限流策略可能会影响用户体验,应谨慎设计限流规则,确保它们既能保护后端服务,又不会对合法用户造成不必要的麻烦。

二、限流后提示信息处理和请求状态

在OpenResty中,如果你使用了内置的限流模块(如ngx_http_limit_req_modulengx_http_limit_conn_module)并且请求被限流,你可以通过返回特定的状态码和错误页面来通知用户。

例如,如果使用limit_reqlimit_conn指令,你可以设置返回503状态码(服务不可用),然后定义一个自定义的错误页面:

http {
    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
    limit_conn_zone $binary_remote_addr zone=addr:10m;

    server {
        location / {
            limit_req zone=one burst=5 nodelay;
            limit_conn addr 3;

            error_page 503 /custom_503.html;
        }

        location = /custom_503.html {
            root /path/to/your/error/pages;
            internal;
        }
    }
}

在上面的配置中,当请求被限流并返回503状态码时,Nginx将会发送/path/to/your/error/pages/custom_503.html文件的内容作为响应。

如果你使用Lua脚本来处理限流,你可以更加灵活地设置返回的内容。例如,你可以使用ngx.exit来返回状态码,同时使用ngx.sayngx.send_headers来发送自定义的响应体或者响应头。

access_by_lua_block {
    -- 假设你已经进行了一些限流判断...
    if should_limit then
        ngx.status = ngx.HTTP_SERVICE_UNAVAILABLE
        ngx.header.content_type = 'text/html'
        ngx.say("<html><body>Sorry, we are currently receiving too many requests. Please try again later.</body></html>")
        ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE)
    end
}

在这个Lua代码块中,如果should_limit变量为true,则返回503状态码,并显示一个自定义的HTML错误消息。

要注意的是,返回给用户的信息应该既明确又友好,以确保用户理解为什么他们的请求没有成功,并且知道下一步该做什么。对于API服务,通常返回一个JSON对象,包含错误码和错误信息会更加合适:

access_by_lua_block {
    -- 假设你已经进行了一些限流判断...
    if should_limit then
        ngx.status = ngx.HTTP_TOO_MANY_REQUESTS -- 429 Too Many Requests
        ngx.header.content_type = 'application/json'
        ngx.say([[{"error": "rate_limit", "error_description": "Too many requests. Please try again later."}]])
        ngx.exit(ngx.HTTP_TOO_MANY_REQUESTS)
    end
}

在这个例子中,我们使用了429状态码(太多请求),这是一个更具体的状态码,用来表示客户端发送的请求已经超过了服务器愿意处理的频率。

三、如何动态更新限流策略,实时生效,不需要重启Nginx

动态更新限流策略而不重启Nginx服务,可以通过以下几种方式实现:

  1. Lua共享字典(shared dictionaries):
    OpenResty提供了共享内存字典,这是一种在Nginx工作进程之间共享数据的机制。你可以使用共享字典来存储限流配置,并且在Lua代码中动态读取这些配置。这样,当你更新了共享字典中的配置时,不需要重启Nginx,新的请求将会使用新的限流配置。

    例如,你可以定义一个共享字典来存储限流速率:

    http {
        lua_shared_dict my_limit_req_store 10m;
    
        init_by_lua_block {
            local dict = ngx.shared.my_limit_req_store
            dict:set("rate", 1) -- 设置每秒请求数为1
        }
    
        server {
            location / {
                access_by_lua_block {
                    local dict = ngx.shared.my_limit_req_store
                    local rate = dict:get("rate") -- 动态获取当前的限流速率
                    -- 接下来使用这个rate值来进行限流...
                }
            }
        }
    }
    

    当你需要更新限流策略时,只需修改共享字典中的值即可。

  2. OpenResty的控制API:
    OpenResty提供了一个控制API,可以用来动态地调整运行时的Nginx配置。这个API可以通过Lua代码来调用,从而实现不重启服务的情况下更新配置。

  3. 外部配置服务:
    你可以将限流配置存储在外部服务中,比如数据库、配置文件或者分布式配置系统(如etcd、Consul)。然后在Nginx的Lua代码中定期轮询这些服务,获取最新的限流配置。

    access_by_lua_block {
        local http = require "resty.http"
        local httpc = http.new()
        local res, err = httpc:request_uri("http://config-service/get_rate_limit", {
            method = "GET",
            headers = {
                ["Content-Type"] = "application/json",
            }
        })
    
        if not res then
            ngx.log(ngx.ERR, "failed to request: ", err)
            return
        end
    
        local rate_limit = tonumber(res.body)
        if rate_limit then
            -- 应用新的限流策略...
        end
    }
    
  4. 信号控制:
    Nginx支持通过信号来进行控制,例如重新加载配置文件(nginx -s reload)。虽然这不是实时的,但是它不需要完全重启Nginx进程,只是重新加载配置文件。如果限流策略是通过Nginx配置文件中的参数来控制的,这是一个可行的方法。

        选择哪种方法取决于你的具体需求和环境。如果你需要非常快速地更新配置,并且配置更新操作非常频繁,那么使用Lua共享字典或者外部配置服务可能是更好的选择。如果配置更新不是很频繁,使用信号控制来重新加载Nginx配置可能就足够了。

 

周国庆

2024/2/25

标签:IP,req,local,Nginx,限流,limit,OpenResty,ngx
From: https://www.cnblogs.com/tianqing/p/18032211

相关文章

  • 洛谷题单指南-贪心-P5019 [NOIP2018 提高组] 铺设道路
    原题链接:https://www.luogu.com.cn/problem/P5019题意解读:最短时间内填满道路,连在一起的不为0的坑可以一起填解题思路:方法1:分治法对于一段连续不同深度的坑,可以最多连续填的天数是最小深度在填满最小深度之后,分别针对其左边和右边的区域再进行填充,这就是此题分治法的理论基......
  • NOIP游记
    NOIP游记DAY0补觉...写赛前注意:文操,时间安排,对拍,细节...主要是怕想不到题,水原板典...总结一下思路,T1不怕切不掉,T4打一个1h内的部分分主要区分在T2T3,都深度思考,先各分0.5思考,会出现3中情况:T2T3都不会,那就在分1h左右思考也许可做题,然后暴力滚满(2.5h+)T2T3会一个,那就速通一......
  • 跟着思兼学习Klipper(29):行空板遇上Klipper之一: 安装Klipper全家桶
    前言原创文章,转载引用请务必注明链接,水平有限,如有疏漏,欢迎交流指正。文章如有更新请访问DFRobot社区及cnblogs博客园,前者内容较全,后者排版及阅读体验更佳。先有三后有一,你说奇怪不奇怪。去年Ash老板问我行空板能否安装使用Klipper全家桶作为3D打印机上位机使用,答案......
  • 代码随想录算法训练营第二十七天| 93.复原IP地址 78.子集 90.子集II
    复原IP地址 题目链接:93.复原IP地址-力扣(LeetCode)思路:投降。在判断字符串是否合法这部分遇到了困难。classSolution{private:vector<string>result;//记录结果//startIndex:搜索的起始位置,pointNum:添加逗点的数量voidbacktracking(string&s,int......
  • 【安卓逆向】一款小说app去除广告与解锁vip分析
    这次的受害者是一款破解圈里面大名鼎鼎的:56yU6Laj6ZiB......
  • 【安装记录】sourceinsight使用小tips
    1、官网下载安装包,一路next(中间安装目录自己选择)2、免费的只有30天,因此选择破解,参考的下面教程:https://www.jb51.net/article/259589.htm3、更改字体大小一致笔者第一次使用SourceInsight,刚一打开发现,哎呀妈呀,这个字体为什么有的大有的小,看得好晕(真的!十分钟都不到我就已经头......
  • JavaScript语法-字符串模板
    [TOC]##JavaScript模板字符串###代码以下是index.js的部分代码:```onShareAppMessage({const{toName,mainText,fromName}=this.data;debugger;return{title:'叮,您收到一张贺卡~',path:'pages/index/index?toname=${toName}&mai......
  • javascript前端过略
    则关闭javascriptF12+F1然后:127.0.0.1;ls/发现无法注册,可以想到如果能注册就好了,然后联想到javascript前端(打开javascript[f12+f1)......
  • 代码随想录算法训练营第二十七天 | 90.子集II , 78.子集, 93.复原IP地址
    93.复原IP地址 已解答中等 相关标签相关企业 有效IP地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。例如:"0.1.2.201" 和"192.168.1.1" 是 有效 IP地址,但是 "0.011.255.245"、"1......
  • 在K8S中,如何具体实现Pod的IP地址发生变化时,不影响正常服务使用?
    在Kubernetes中,Pod的IP地址变化通常是由调度器重新调度Pod、节点故障、Pod升级或缩放等原因引起的。为了确保PodIP变化时服务不受影响,你可以采取以下具体步骤:使用Service:创建一个Service,指定其Selector以匹配你的Pod标签。当Pod的IP地址发生变化时,Kubernetes会自动更新Serv......