基于 lua_shared_dict 的本地内存限流实现
OpenResty 实战:基于 lua_shared_dict 的本地内存限流实现
在高并发环境下,限流是保护系统的关键手段。前面我们用 Redis 实现了分布式令牌桶,但对于单机限流场景,还可以使用 OpenResty 自带的 lua_shared_dict
。
这种方案的优点是:
- 高性能:直接存储在 Nginx 共享内存中,读写速度远高于 Redis。
- 低延迟:不依赖外部存储,不存在网络开销。
- 简单易用:配置简洁,部署方便。
本文将介绍如何用 lua_shared_dict
+ Lua 脚本 在 OpenResty 中实现一个高效的本地限流器。
一、原理说明
lua_shared_dict
是 OpenResty 提供的共享内存字典,所有 worker 进程可读写。我们可以在里面保存限流计数或令牌桶状态。
常见限流算法:
- 固定窗口:统计某个时间段内的请求数,超出则拒绝。
- 滑动窗口:更细粒度,适合平滑限流。
- 令牌桶(推荐):系统按速率往桶里放令牌,请求消耗一个令牌才能通过。
本文用 令牌桶 实现。
二、Nginx 配置
编辑 nginx.conf
:
worker_processes 1;events {worker_connections 1024;
}http {lua_shared_dict limit_store 10m; # 共享内存 10MB,用于存放令牌桶server {listen 8080;location /api/local_limit {content_by_lua_file conf/lua/local_rate_limit.lua;}}
}
三、Lua 脚本实现
conf/lua/local_rate_limit.lua
:
-- 获取共享字典
local limit_store = ngx.shared.limit_store-- 限流参数
local key = "api:local:limit"
local rate = 5 -- 每秒生成令牌数
local capacity = 10 -- 桶容量
local now = ngx.now()-- 获取上次访问时间和令牌数
local last_time = limit_store:get(key .. ":last_time")
local tokens = limit_store:get(key .. ":tokens")if not last_time thenlast_time = nowtokens = capacity
end-- 补充令牌
local delta = now - last_time
local add_tokens = math.floor(delta * rate)
tokens = math.min(capacity, tokens + add_tokens)-- 更新最后访问时间
limit_store:set(key .. ":last_time", now)-- 判断是否还有令牌
if tokens > 0 thentokens = tokens - 1limit_store:set(key .. ":tokens", tokens)ngx.say("请求成功,剩余令牌:", tokens)
elselimit_store:set(key .. ":tokens", tokens)ngx.status = 429ngx.say("请求过多,请稍后再试!")
end
四、测试效果
-
启动 OpenResty:
openresty -p `pwd`/ -c conf/nginx.conf
-
用
ab
模拟请求:ab -n 20 -c 5 http://127.0.0.1:8080/api/local_limit
-
结果:
-
前 10 次(桶满 + 新令牌)会返回:
请求成功,剩余令牌:X
-
超过速率时返回:
429 Too Many Requests 请求过多,请稍后再试!
-
五、优化方向
-
针对用户或 IP 限流
local client_ip = ngx.var.remote_addr local key = "limit:" .. client_ip
-
封装为模块
可以写成rate_limit.lua
工具库,供多个接口复用。 -
使用 OpenResty 自带模块
OpenResty 提供了resty.limit.req
库(基于 lua_shared_dict),功能更完善,还支持平滑限速。使用示例:
local limit_req = require "resty.limit.req" local lim, err = limit_req.new("limit_store", 5, 10) local key = ngx.var.remote_addrlocal delay, err = lim:incoming(key, true) if not delay thenngx.exit(429) end ngx.say("请求成功")
六、总结
通过 lua_shared_dict
,我们实现了一个本地高性能的 令牌桶限流器。
- 优点:速度快、延迟低、实现简单。
- 缺点:仅限单机,无法跨节点共享限流状态。
如果是 单机 API 限流,推荐 lua_shared_dict
;
如果是 集群分布式限流,则建议用 Redis 或 etcd。