OpenResty 中实现限流(Rate Limiting)的实战案例
OpenResty 实战:使用 Lua 脚本实现限流(Rate Limiting)
在高并发系统中,限流是保障服务稳定性的重要手段。常见的限流场景包括:
- API 网关防止恶意请求突发
- 防止单个用户/接口过载影响整体服务
- 与熔断、降级机制结合,提升系统鲁棒性
OpenResty 作为高性能 Web 平台,可以借助 Lua 脚本在 Nginx 层实现灵活的限流策略,例如 令牌桶(Token Bucket)、漏桶(Leaky Bucket) 等。本文将通过一个实战案例,演示如何在 OpenResty 中实现 基于 Redis 的令牌桶限流。
一、限流算法简介
常见的限流算法有两种:
- 固定窗口计数:在一个时间窗口内只允许 N 次请求,超出则拒绝。实现简单,但存在“突刺流量”问题。
- 令牌桶算法:系统按照固定速率往桶里放令牌,请求需要消耗令牌才能通过;如果桶空了,就拒绝请求。相比更平滑。
本文采用 Redis + Lua 脚本 来实现 分布式令牌桶限流,适用于集群环境。
二、环境准备
- 已安装 OpenResty
- 已安装并运行 Redis
- 已安装
lua-resty-redis
库
安装命令(CentOS 举例):
luarocks install lua-resty-redis
三、Nginx 配置示例
编辑 nginx.conf
:
worker_processes 1;events {worker_connections 1024;
}http {server {listen 8080;# 限流接口location /api/limit {content_by_lua_file conf/lua/rate_limit.lua;}}
}
在 conf/lua/rate_limit.lua
中编写限流逻辑。
四、Lua 脚本实现限流
conf/lua/rate_limit.lua
:
local redis = require "resty.redis"
local red = redis:new()-- Redis 连接
red:set_timeout(1000)
local ok, err = red:connect("127.0.0.1", 6379)
if not ok thenngx.say("failed to connect: ", err)return
end-- 限流参数
local key = "limit:api" -- 限流 key
local rate = 5 -- 每秒生成令牌数
local capacity = 10 -- 桶容量
local now = ngx.now() -- 当前时间戳(秒.毫秒)-- 获取上次请求时间和剩余令牌数
local last_time, tokens = red:hmget(key, "last_time", "tokens")if not last_time or last_time == ngx.null thenlast_time = nowtokens = capacity
elselast_time = tonumber(last_time)tokens = tonumber(tokens)
end-- 计算时间差,生成新令牌
local delta = now - last_time
local add_tokens = math.floor(delta * rate)
tokens = math.min(capacity, tokens + add_tokens)-- 更新最后访问时间
last_time = now-- 判断是否有令牌
if tokens > 0 thentokens = tokens - 1red:hmset(key, "last_time", last_time, "tokens", tokens)ngx.say("请求成功,剩余令牌:", tokens)
elsered:hmset(key, "last_time", last_time, "tokens", tokens)ngx.status = 429ngx.say("请求过多,请稍后再试!")
end-- 释放 Redis 连接
red:set_keepalive(10000, 100)
五、测试效果
-
在 Redis 中初始化限流桶(可选):
redis-cli hset limit:api last_time 0 tokens 10
-
使用
ab
压测工具模拟请求:ab -n 20 -c 5 http://127.0.0.1:8080/api/limit
-
结果:
- 前几次请求返回
请求成功,剩余令牌:X
- 超过令牌桶容量后,返回
429 Too Many Requests
- 前几次请求返回
这样就实现了 基于令牌桶的限流。
六、优化方向
-
不同用户限流:将
key
动态拼接用户 ID 或 IP,实现精细化控制。local client_ip = ngx.var.remote_addr local key = "limit:" .. client_ip
-
Lua 脚本原子化:为了避免并发条件竞争,可以将逻辑写成 Redis Lua 脚本,用
EVAL
保证原子性。 -
本地共享内存限流:如果不需要分布式,可以用
lua_shared_dict
(如resty.limit.req
模块),性能更高。
七、总结
通过 OpenResty + Lua + Redis,我们实现了一个简单高效的 令牌桶限流机制,可用于 API 网关、登录接口、支付接口等关键场景。相比传统 Nginx 模块,Lua 提供了更高的灵活性,开发者可以根据业务需求随时扩展。