基于 lua_shared_dict 的本地内存限流实现

OpenResty 实战:基于 lua_shared_dict 的本地内存限流实现

在高并发环境下,限流是保护系统的关键手段。前面我们用 Redis 实现了分布式令牌桶,但对于单机限流场景,还可以使用 OpenResty 自带的 lua_shared_dict

这种方案的优点是:

  • 高性能:直接存储在 Nginx 共享内存中,读写速度远高于 Redis。
  • 低延迟:不依赖外部存储,不存在网络开销。
  • 简单易用:配置简洁,部署方便。

本文将介绍如何用 lua_shared_dict + Lua 脚本 在 OpenResty 中实现一个高效的本地限流器。


一、原理说明

lua_shared_dict 是 OpenResty 提供的共享内存字典,所有 worker 进程可读写。我们可以在里面保存限流计数或令牌桶状态。

常见限流算法:

  1. 固定窗口:统计某个时间段内的请求数,超出则拒绝。
  2. 滑动窗口:更细粒度,适合平滑限流。
  3. 令牌桶(推荐):系统按速率往桶里放令牌,请求消耗一个令牌才能通过。

本文用 令牌桶 实现。


二、Nginx 配置

编辑 nginx.conf

nginx 复制代码
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

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 then
    last_time = now
    tokens = 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 then
    tokens = tokens - 1
    limit_store:set(key .. ":tokens", tokens)
    ngx.say("请求成功,剩余令牌:", tokens)
else
    limit_store:set(key .. ":tokens", tokens)
    ngx.status = 429
    ngx.say("请求过多,请稍后再试!")
end

四、测试效果

  1. 启动 OpenResty:

    bash 复制代码
    openresty -p `pwd`/ -c conf/nginx.conf
  2. ab 模拟请求:

    bash 复制代码
    ab -n 20 -c 5 http://127.0.0.1:8080/api/local_limit
  3. 结果:

    • 前 10 次(桶满 + 新令牌)会返回:

      复制代码
      请求成功,剩余令牌:X
    • 超过速率时返回:

      复制代码
      429 Too Many Requests
      请求过多,请稍后再试!

五、优化方向

  1. 针对用户或 IP 限流

    lua 复制代码
    local client_ip = ngx.var.remote_addr
    local key = "limit:" .. client_ip
  2. 封装为模块

    可以写成 rate_limit.lua 工具库,供多个接口复用。

  3. 使用 OpenResty 自带模块

    OpenResty 提供了 resty.limit.req 库(基于 lua_shared_dict),功能更完善,还支持平滑限速。

    使用示例:

    lua 复制代码
    local limit_req = require "resty.limit.req"
    local lim, err = limit_req.new("limit_store", 5, 10)
    local key = ngx.var.remote_addr
    
    local delay, err = lim:incoming(key, true)
    if not delay then
        ngx.exit(429)
    end
    ngx.say("请求成功")

六、总结

通过 lua_shared_dict,我们实现了一个本地高性能的 令牌桶限流器

  • 优点:速度快、延迟低、实现简单。
  • 缺点:仅限单机,无法跨节点共享限流状态。

如果是 单机 API 限流 ,推荐 lua_shared_dict

如果是 集群分布式限流,则建议用 Redis 或 etcd。


相关推荐
代码游侠3 分钟前
学习笔记——设备树基础
linux·运维·开发语言·单片机·算法
devmoon12 分钟前
运行时(Runtime)是什么?为什么 Polkadot 的 Runtime 可以被“像搭积木一样”定制
开发语言·区块链·智能合约·polkadot·runtmie
时艰.13 分钟前
Java 并发编程 — 并发容器 + CPU 缓存 + Disruptor
java·开发语言·缓存
忆~遂愿26 分钟前
GE 引擎进阶:依赖图的原子性管理与异构算子协作调度
java·开发语言·人工智能
沐知全栈开发31 分钟前
API 类别 - 交互
开发语言
人道领域1 小时前
SSM框架从入门到入土(AOP面向切面编程)
java·开发语言
铅笔侠_小龙虾1 小时前
Flutter 实战: 计算器
开发语言·javascript·flutter
2的n次方_1 小时前
Runtime 执行提交机制:NPU 硬件队列的管理与任务原子化下发
c语言·开发语言
2501_944711431 小时前
JS 对象遍历全解析
开发语言·前端·javascript
凡人叶枫2 小时前
C++中智能指针详解(Linux实战版)| 彻底解决内存泄漏,新手也能吃透
java·linux·c语言·开发语言·c++·嵌入式开发