Redis + Lua 实现限流(令牌桶算法)

Redis + Lua 可以高效实现分布式限流,确保原子性,避免多个请求同时修改计数器造成不准确的问题。


1️⃣ 基于令牌桶算法的 Redis 限流

基本思路

  • 设定一个 桶(bucket),桶内可以存放一定数量的令牌
  • 每个请求先从桶中取一个令牌,如果有令牌,则允许请求,否则拒绝请求
  • 令牌按照一定速率 自动添加 到桶中(但不能超过桶的最大容量)。

2️⃣ 具体实现

实现方式

  • Redis 存储令牌数量
  • Lua 以原子性操作检查 & 更新令牌数量

🚀 Lua 脚本

local key = KEYS[1]  -- Redis Key(存储令牌数)
local rate = tonumber(ARGV[1])  -- 令牌添加速率(每秒增加多少个)
local capacity = tonumber(ARGV[2])  -- 令牌桶的最大容量
local now = tonumber(ARGV[3])  -- 当前时间戳(秒)
local requested = tonumber(ARGV[4])  -- 请求的令牌数

-- 获取上次请求时的令牌信息
local last_tokens = tonumber(redis.call("GET", key)) or capacity  -- 令牌数量(默认满桶)
local last_time = tonumber(redis.call("GET", key .. ":time")) or now  -- 上次刷新时间

-- 计算经过的时间
local elapsed = now - last_time

-- 计算新令牌数量
local new_tokens = math.min(capacity, last_tokens + (elapsed * rate))

-- 判断是否允许请求
if new_tokens >= requested then
    -- 允许请求,扣除令牌
    redis.call("SET", key, new_tokens - requested)
    redis.call("SET", key .. ":time", now)
    return 1  -- 允许访问
else
    -- 令牌不足,拒绝请求
    return 0
end
 


3️⃣ Java 代码调用 Lua

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.util.Pool;
import java.time.Instant;
import java.util.Collections;

public class RedisRateLimiter {
    private static final String LUA_SCRIPT =
            "local key = KEYS[1] " +
            "local rate = tonumber(ARGV[1]) " +
            "local capacity = tonumber(ARGV[2]) " +
            "local now = tonumber(ARGV[3]) " +
            "local requested = tonumber(ARGV[4]) " +
            "local last_tokens = tonumber(redis.call('GET', key)) or capacity " +
            "local last_time = tonumber(redis.call('GET', key .. ':time')) or now " +
            "local elapsed = now - last_time " +
            "local new_tokens = math.min(capacity, last_tokens + (elapsed * rate)) " +
            "if new_tokens >= requested then " +
            "    redis.call('SET', key, new_tokens - requested) " +
            "    redis.call('SET', key .. ':time', now) " +
            "    return 1 " +
            "else " +
            "    return 0 " +
            "end";

    private final Pool<Jedis> jedisPool;

    public RedisRateLimiter(Pool<Jedis> jedisPool) {
        this.jedisPool = jedisPool;
    }

    public boolean tryAcquire(String key, int rate, int capacity, int requested) {
        try (Jedis jedis = jedisPool.getResource()) {
            long now = Instant.now().getEpochSecond();
            Object result = jedis.eval(LUA_SCRIPT, 
                                       Collections.singletonList(key), 
                                       Arrays.asList(String.valueOf(rate),
                                                     String.valueOf(capacity),
                                                     String.valueOf(now),
                                                     String.valueOf(requested)));
            return Integer.parseInt(result.toString()) == 1;
        }
    }

    public static void main(String[] args) {
        JedisPool pool = new JedisPool("localhost", 6379);
        RedisRateLimiter limiter = new RedisRateLimiter(pool);

        // 测试限流:每秒最多 5 个请求,最大容量 10,当前请求 1 个
        for (int i = 0; i < 20; i++) {
            boolean allowed = limiter.tryAcquire("rate_limit", 5, 10, 1);
            System.out.println("请求 " + (i + 1) + (allowed ? " ✅ 通过" : " ❌ 拒绝"));
            try {
                Thread.sleep(200); // 200ms 发起一个请求
            } catch (InterruptedException ignored) {}
        }

        pool.close();
    }
}


4️⃣ 结果

请求 1 ✅ 通过
请求 2 ✅ 通过
请求 3 ✅ 通过
...
请求 11 ❌ 拒绝
请求 12 ❌ 拒绝
..


5️⃣ 优势

高效 & 并发安全:Lua 原子性执行,避免并发问题
可动态调整速率:可动态修改令牌生成速率
轻量级:不需要额外的分布式组件


6️⃣ 总结

  • Redis + Lua 实现令牌桶限流
  • Lua 原子操作,避免并发问题
  • 适用于高并发 API 限流
  • Java 代码调用 Lua 脚本

💡 这个方案高效、轻量、可扩展,适合分布式系统限流 🚀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值