Skip to content

Node.js 限流阀(Redis + Lua)

结合 Redis + Lua + Express 实现限流功能(如抽奖按钮防连点)。

原理

30 秒内最多操作 5 次,超过则提示"请稍后重试"。

代码实现

index.lua — 限流核心逻辑

lua
local key = KEYS[1]
local limit = tonumber(ARGV[1])      -- 限制次数
local interval = tonumber(ARGV[2])   -- 时间窗口(秒)
local count = tonumber(redis.call("get", key) or "0")

if count > limit then
  return 0   -- 超限
else
  redis.call("incr", key)           -- 计数 +1
  redis.call("expire", key, interval)  -- 设置过期时间
  return 1   -- 放行
end

index.js — Express 接口

js
import express from 'express'
import Redis from 'ioredis'
import fs from 'node:fs'

const lua = fs.readFileSync('./index.lua', 'utf8')
const redis = new Redis()
const app = express()

const TIME = 30      // 30秒窗口
const CHANGE = 5     // 最多5次
const KEY = 'lottery'

app.use('*', (req, res, next) => {     // CORS
  res.header('Access-Control-Allow-Origin', '*')
  next()
})

app.get('/lottery', (req, res) => {
  redis.eval(lua, 1, KEY, CHANGE, TIME, (err, result) => {
    if (err) return console.log(err)
    result === 1 ? res.send('抽奖成功') : res.send('请稍后重试!')
  })
})

app.listen(3000)

执行流程

用户点击抽奖
  → GET /lottery
    → redis.eval(lua, 1, 'lottery', 5, 30)
      → Lua 原子执行:
        1. GET lottery → 获取当前计数
        2. 如果 count > 5 → 返回 0(限流)
        3. 否则 INCR +1, EXPIRE 30秒 → 返回 1(放行)
    → result === 1 ? '抽奖成功' : '请稍后重试'

redis.eval 参数说明

js
redis.eval(luaScript, numKeys, key1, arg1, arg2, callback)
//          ^script   ^key数量  ^KEYS  ^ARGV  ^ARGV  ^回调(err, result)

Lua 中 KEYS[1] 对应 key1,ARGV[1] 对应 arg1,ARGV[2] 对应 arg2