OpenResty中实现按QPS、时间范围、来源IP进行限流的方法
作者:Eric zhou
OpenResty是一个基于Nginx与Lua的高性能Web平台,它通过LuaJIT在Nginx中运行高效的Lua脚本和模块,可以用来处理复杂的网络请求,并且支持各种流量控制和限制的功能。
近期研究在OpenResty中如何实现,按QPS、时间范围、来源IP进行限流,以及动态更新限流策略。今天将实现方案分享给大家。
一、在OpenResty中如何实现,按QPS、时间范围、来源IP进行限流
使用OpenResty进行限流的几种常见方法:
按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
表示不对超出速率的请求进行延迟处理。
按时间范围限流:
如果你想在特定时间范围内限流,你可能需要编写一些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
按来源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_module
或ngx_http_limit_conn_module
)并且请求被限流,你可以通过返回特定的状态码和错误页面来通知用户。
例如,如果使用limit_req
或limit_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.say
或ngx.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服务,可以通过以下几种方式实现:
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值来进行限流... } } } }
当你需要更新限流策略时,只需修改共享字典中的值即可。
OpenResty的控制API:
OpenResty提供了一个控制API,可以用来动态地调整运行时的Nginx配置。这个API可以通过Lua代码来调用,从而实现不重启服务的情况下更新配置。
外部配置服务:
你可以将限流配置存储在外部服务中,比如数据库、配置文件或者分布式配置系统(如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 }
信号控制:
Nginx支持通过信号来进行控制,例如重新加载配置文件(nginx -s reload
)。虽然这不是实时的,但是它不需要完全重启Nginx进程,只是重新加载配置文件。如果限流策略是通过Nginx配置文件中的参数来控制的,这是一个可行的方法。
选择哪种方法取决于你的具体需求和环境。如果你需要非常快速地更新配置,并且配置更新操作非常频繁,那么使用Lua共享字典或者外部配置服务可能是更好的选择。如果配置更新不是很频繁,使用信号控制来重新加载Nginx配置可能就足够了。
到此这篇关于OpenResty中实现按QPS、时间范围、来源IP进行限流的文章就介绍到这了,更多相关OpenResty限流内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!