OpenResty 中实现限流(Rate Limiting)的实战案例

发布于:2025-09-11 ⋅ 阅读:(21) ⋅ 点赞:(0)

OpenResty 实战:使用 Lua 脚本实现限流(Rate Limiting)

在高并发系统中,限流是保障服务稳定性的重要手段。常见的限流场景包括:

  • API 网关防止恶意请求突发
  • 防止单个用户/接口过载影响整体服务
  • 与熔断、降级机制结合,提升系统鲁棒性

OpenResty 作为高性能 Web 平台,可以借助 Lua 脚本在 Nginx 层实现灵活的限流策略,例如 令牌桶(Token Bucket)漏桶(Leaky Bucket) 等。本文将通过一个实战案例,演示如何在 OpenResty 中实现 基于 Redis 的令牌桶限流


一、限流算法简介

常见的限流算法有两种:

  1. 固定窗口计数:在一个时间窗口内只允许 N 次请求,超出则拒绝。实现简单,但存在“突刺流量”问题。
  2. 令牌桶算法:系统按照固定速率往桶里放令牌,请求需要消耗令牌才能通过;如果桶空了,就拒绝请求。相比更平滑。

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

-- 释放 Redis 连接
red:set_keepalive(10000, 100)

五、测试效果

  1. 在 Redis 中初始化限流桶(可选):

    redis-cli hset limit:api last_time 0 tokens 10
    
  2. 使用 ab 压测工具模拟请求:

    ab -n 20 -c 5 http://127.0.0.1:8080/api/limit
    
  3. 结果:

    • 前几次请求返回 请求成功,剩余令牌:X
    • 超过令牌桶容量后,返回 429 Too Many Requests

这样就实现了 基于令牌桶的限流


六、优化方向

  1. 不同用户限流:将 key 动态拼接用户 ID 或 IP,实现精细化控制。

    local client_ip = ngx.var.remote_addr
    local key = "limit:" .. client_ip
    
  2. Lua 脚本原子化:为了避免并发条件竞争,可以将逻辑写成 Redis Lua 脚本,用 EVAL 保证原子性。

  3. 本地共享内存限流:如果不需要分布式,可以用 lua_shared_dict(如 resty.limit.req 模块),性能更高。


七、总结

通过 OpenResty + Lua + Redis,我们实现了一个简单高效的 令牌桶限流机制,可用于 API 网关、登录接口、支付接口等关键场景。相比传统 Nginx 模块,Lua 提供了更高的灵活性,开发者可以根据业务需求随时扩展。


网站公告

今日签到

点亮在社区的每一天
去签到