nginx作为api网关进行限流的核心目的是保护后端服务,防止突发流量导致崩溃。1. 使用ngx_http_limit_req_module实现请求速率限流,在http块定义共享内存区域并设置请求速率限制,在location中使用limit_req指令,并通过burst和nodelay控制突发流量处理方式;2. 使用ngx_http_limit_conn_module限制并发连接数,在http块定义共享内存区域并在location中应用limit_conn指令;3. 可自定义限流错误页面并返回指定状态码;4. 令牌桶允许突发流量,适合提高吞吐量,漏桶平滑流量输出,适合严格控制速率;5. 动态调整可通过nginx plus api、openresty+redis或consul/etcd结合热加载实现;6. 监控可通过prometheus+grafana等工具统计被限流请求数、响应时间及nginx资源使用情况;7. 复杂场景如基于用户id或api key限流可使用nginx plus、openresty+redis或自定义模块实现,具体选择取决于需求和技术栈。
微服务架构下,Nginx 作为 API 网关进行限流,主要是为了保护后端服务,防止突发流量导致服务崩溃。核心在于控制请求速率,避免过载。
解决方案
Nginx 可以通过 ngx_http_limit_req_module 和 ngx_http_limit_conn_module 模块实现限流。前者用于限制请求速率(例如,每秒请求数),后者用于限制并发连接数。
1. 基于请求速率的限流 (Rate Limiting): ngx_http_limit_req_module
首先,需要在 http 块中定义一个共享内存区域,用于存储请求状态:
http { limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s; # ... other configurations }
这里,$binary_remote_addr 是客户端 IP 地址的二进制表示,zone=mylimit:10m 定义了一个名为 mylimit 的共享内存区域,大小为 10MB。 rate=10r/s 表示允许每秒 10 个请求。
然后,在需要限流的 server 或 location 块中使用 limit_req 指令:
server { location /api/ { limit_req zone=mylimit burst=20 nodelay; # ... other configurations } }
burst=20 允许在短时间内突发 20 个请求,超过这个数量的请求将被延迟处理或拒绝。 nodelay 表示立即处理突发请求,而不是排队等待。 如果不加 nodelay,突发请求会排队等待,直到满足速率限制。
2. 基于并发连接数的限流 (Connection Limiting): ngx_http_limit_conn_module
类似地,首先在 http 块中定义一个共享内存区域:
http { limit_conn_zone $binary_remote_addr zone=myconnlimit:10m; # ... other configurations }
然后,在 server 或 location 块中使用 limit_conn 指令:
server { location /api/ { limit_conn myconnlimit 10; # ... other configurations } }
这里,limit_conn myconnlimit 10 表示允许每个 IP 地址最多 10 个并发连接。
3. 错误处理
可以自定义错误页面,当请求被限流时返回:
http { # ... limit_req_status 503; # 返回 503 状态码 error_page 503 /503.html; server { location = /503.html { return 503 "Service Unavailable"; # 可以指向一个自定义的 HTML 页面 } } }
如何选择合适的限流策略?令牌桶 vs. 漏桶
令牌桶和漏桶是两种常见的限流算法。Nginx 的 ngx_http_limit_req_module 实际上是基于漏桶算法的变体实现的。
- 令牌桶 (Token Bucket): 以恒定速率生成令牌,请求需要获取令牌才能通过。如果桶中没有令牌,请求将被拒绝或延迟。 优点是允许一定程度的突发流量。
- 漏桶 (Leaky Bucket): 请求进入桶中,以恒定速率流出。如果桶满了,请求将被拒绝。 优点是平滑流量,防止后端服务被突发流量冲击。
选择哪种算法取决于你的具体需求。如果你的服务可以容忍一定程度的突发流量,并且希望提高吞吐量,那么令牌桶可能更适合。如果你的服务对延迟敏感,并且需要严格控制流量速率,那么漏桶可能更适合。 Nginx 的 ngx_http_limit_req_module 通过 burst 参数允许一定程度的突发,因此实际上是一个混合的策略。
微服务架构下,如何动态调整 Nginx 限流配置?
静态配置在微服务架构下通常是不够的,因为流量模式可能会随着时间变化。动态调整限流配置可以通过以下几种方式实现:
-
Nginx Plus API: Nginx Plus 提供了 API,可以动态修改配置,包括限流参数。 这是一个商业解决方案。
-
OpenResty + redis: 使用 OpenResty (基于 Nginx 的 lua 平台) 和 redis。 Lua 脚本可以从 Redis 中读取限流配置,并动态应用到 Nginx。 这种方案更灵活,但需要一定的开发工作。 例如,可以编写一个 Lua 脚本,定期从 Redis 中获取限流规则,并使用 ngx.shared.DICT 在 Nginx 工作进程之间共享这些规则。
-- 从 Redis 获取限流配置 local redis = require "resty.redis" local red = redis:new() red:set_timeout(1000) -- 1 second local ok, err = red:connect("127.0.0.1", 6379) if not ok then ngx.log(ngx.ERR, "failed to connect to redis: ", err) return end local rate_limit_config, err = red:get("rate_limit_config") if not rate_limit_config then ngx.log(ngx.ERR, "failed to get rate_limit_config from redis: ", err) return end -- 解析 JSON 配置 local cjson = require "cjson" local config = cjson.decode(rate_limit_config) -- 应用限流规则 (假设使用了 ngx.shared.DICT) local shared_data = ngx.shared.my_rate_limit_data shared_data:set("rate", config.rate) shared_data:set("burst", config.burst) -- ... 在 Nginx 配置中使用 Lua 脚本读取 shared_data 中的值
-
Consul/Etcd + Nginx 配置热加载: 将限流配置存储在 Consul 或 Etcd 等配置中心,然后使用工具(如 Consul Template 或 confd)监听配置变化,并自动更新 Nginx 配置文件,然后触发 Nginx 的热加载。
如何监控 Nginx 的限流效果?
监控是限流策略的重要组成部分。需要监控以下指标:
- 被限流的请求数量: 通过 Nginx 的访问日志分析,或者使用 Nginx Plus 的监控功能,可以统计被限流的请求数量。
- 后端服务的响应时间: 监控后端服务的响应时间,可以判断限流是否有效缓解了服务的压力。
- Nginx 的 CPU 和内存使用率: 监控 Nginx 的 CPU 和内存使用率,可以判断 Nginx 本身是否成为瓶颈。
可以使用 Prometheus + Grafana 等工具来收集和展示这些指标。 例如,可以使用 nginx-exporter 来收集 Nginx 的指标,然后使用 Prometheus 存储这些指标,最后使用 Grafana 创建仪表盘。
如何处理复杂的限流场景,例如基于用户 ID 或 API Key 的限流?
对于更复杂的限流场景,例如基于用户 ID 或 API Key 的限流,可以使用以下方法:
-
Nginx Plus API: Nginx Plus 提供了更高级的限流功能,可以基于请求的 Header 或 Cookie 等信息进行限流。
-
OpenResty + Redis: 使用 OpenResty 可以编写 Lua 脚本,从请求中提取用户 ID 或 API Key,然后使用 Redis 存储每个用户或 API Key 的请求计数器。 例如,可以编写一个 Lua 脚本,从请求头中获取 API Key,然后使用 Redis 的 INCR 命令原子地增加该 API Key 的请求计数器。
-- 从请求头中获取 API Key local api_key = ngx.req.get_headers()["X-API-Key"] if not api_key then ngx.log(ngx.ERR, "API Key not found in request header") ngx.exit(ngx.HTTP_UNAUTHORIZED) end -- 连接 Redis local redis = require "resty.redis" local red = redis:new() red:set_timeout(1000) -- 1 second local ok, err = red:connect("127.0.0.1", 6379) if not ok then ngx.log(ngx.ERR, "failed to connect to redis: ", err) return end -- 构建 Redis Key local key = "api_key_limit:" .. api_key -- 增加请求计数器 local count, err = red:incr(key) if not count then ngx.log(ngx.ERR, "failed to incr api key limit: ", err) return end -- 设置过期时间 (例如,1 分钟) if count == 1 then red:expire(key, 60) end -- 获取限流配置 (例如,每分钟 100 个请求) local rate_limit = 100 -- 检查是否超过限制 if count > rate_limit then ngx.log(ngx.WARN, "API Key ", api_key, " exceeded rate limit") ngx.exit(ngx.HTTP_TOO_MANY_REQUESTS) end -- ... 其他处理
-
自定义 Nginx 模块: 如果需要非常复杂的限流逻辑,可以考虑编写自定义的 Nginx 模块。 这需要 C 语言编程技能。
选择哪种方法取决于你的具体需求和技术栈。 Nginx Plus API 最简单,但需要付费。 OpenResty + Redis 更加灵活,但需要一定的开发工作。 自定义 Nginx 模块最强大,但也最复杂。