Nginx 多机下的请求限制

上一篇单机下的请求限制,如果我们有多台服务器就可能不太合适了,大多数情况下我们会使用负载均衡,这样就会有多台服务器同时提供服务,那么我们就需要在多台服务器上做请求限制。因为服务的后端,比如 Redis MySQL 它们可以抗住的流量是有限的,我们要整体来确保服务的可用性。

单机下的限制信息是通过共享内存,多机下我们需要用到 Redis ,这里我们会使用 OpenResty 来替换 Nginx ,OpenResty 是一个基于 Nginx 的可伸缩的 Web 平台,它集成了大量的 Nginx 模块和 Lua 库,用于通过 Lua 脚本语言来控制 Nginx ,这样可以在 Nginx 层面处理复杂的业务逻辑。

OpenResty 就像文档里面提到的,安装太麻烦了,我尝试了非常多次也没有成功,最终我还是使用了官方提供的 Docker 镜像。本文完整的代码位于: https://github.com/hongfs/env/tree/main/nginx-openresty-ratelimit

我们需要准备一个基础镜像,里面需要下载好依赖的 Lua 脚本,其中 lua-resty-redis 用于连接 Redis ,lua-resty-redis-ratelimit 用于通过 Redis 进行限制请求。

Dockerfile

1
2
3
4
5
6
7
8
9
10
FROM openresty/openresty:1.21.4.1-7-alpine-apk

RUN apk update && \
apk add git && \
mkdir -p /etc/nginx/lua && \
cd /etc/nginx/lua && \
git clone https://github.com/timebug/lua-resty-redis-ratelimit && \
git clone https://github.com/openresty/lua-resty-redis && \
cd && \
rm -rf /var/cache/apk

在镜像的基础上,优化我们的配置文件,把限流的配置加入进去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# 引入 Lua 脚本
lua_package_path "/etc/nginx/lua/?.lua;;";

server {
listen 80;
server_name _;

location / {
access_by_lua_block {
# 引入 ratelimit 库
local ratelimit = require "lua-resty-redis-ratelimit.lib.resty.redis.ratelimit"

# 实例化 ratelimit 对象
# 第一个参数是 redis 的 key 前缀
# 第二个参数是的限流配置
# 第三个参数是突发流量的最大值
# 第四个参数是恢复正常状态的时间,这里我理解的应该是被限流后,多久后恢复正常
local lim, err = ratelimit.new("hongfs", "800r/s", 200, 0)
if not lim then
ngx.log(ngx.ERR, "failed to instantiate a resty.redis.ratelimit object: ", err)
return ngx.exit(500)
end

# redis 的配置
local red = { host = "10.0.16.15", port = 6379, timeout = 1 }

# 获取客户端 ip 作为 key
local key = ngx.var.binary_remote_addr
local delay, err = lim:incoming(key, red)
if not delay then
if err == "rejected" then
return ngx.exit(503)
end
ngx.log(ngx.ERR, "failed to limit req: ", err)
return ngx.exit(500)
end

# 如果 delay 大于 0,说明被限流了,需要延迟 delay 秒后再处理
if delay >= 0.001 then
local excess = err

ngx.sleep(delay)
end
}

# 经过测试,没有被限流的请求要通过 nginx 的 proxy_pass 转发到另一个端口才可以被正常处理。
proxy_pass http://127.0.0.1:81;
}
}

server {
listen 81;
server_name _;

location / {
return 200 "success";
}
}

现在我们用 WRK 来测试一下,可以看见每秒的请求量在我们设定数的附近波动,说明限流生效了。

这里可能会奇怪,用压测工具为什么会数量是 800,而没有大量的错误,主要是我们配置了延迟导致的,可以看见 WRK 里面的 MAX 值是比较高的。

1
2
3
4
5
6
7
8
9
$ wrk -c100 -t10 -d1m http://local.hongfs.cn/
Running 1m test @ http://local.hongfs.cn/
10 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 124.83ms 3.73ms 140.08ms 99.63%
Req/Sec 80.62 24.93 181.00 81.68%
48023 requests in 1.00m, 7.97MB read
Requests/sec: 800.08
Transfer/sec: 135.95KB

另外可以于 limit_conn 一起使用,限制单服务器的最大连接数。

往上