Redis(170)如何使用Redis实现分布式限流?

使用Redis实现分布式限流是一种常见且有效的方法,可以防止系统过载并确保公平的资源分配。Redis的高性能和丰富的数据结构使其成为实现分布式限流的理想选择。常见的限流算法包括固定窗口计数、滑动窗口计数和令牌桶算法。

1. 固定窗口计数算法

固定窗口计数算法将时间划分为固定长度的窗口,并在每个窗口内计数请求的数量。

示例代码

以下示例展示了如何使用Redis实现固定窗口计数算法的分布式限流:

java 复制代码
import redis.clients.jedis.Jedis;

public class FixedWindowRateLimiter {

    private Jedis jedis;
    private int maxRequests;
    private int windowSize; // 窗口大小,单位为秒

    public FixedWindowRateLimiter(String host, int port, int maxRequests, int windowSize) {
        this.jedis = new Jedis(host, port);
        this.maxRequests = maxRequests;
        this.windowSize = windowSize;
    }

    public boolean isAllowed(String clientId) {
        String key = "rate_limiter:" + clientId;
        long currentWindow = System.currentTimeMillis() / 1000 / windowSize;
        String windowKey = key + ":" + currentWindow;

        long requestCount = jedis.incr(windowKey);
        if (requestCount == 1) {
            jedis.expire(windowKey, windowSize);
        }

        return requestCount <= maxRequests;
    }

    public void close() {
        jedis.close();
    }

    public static void main(String[] args) {
        FixedWindowRateLimiter rateLimiter = new FixedWindowRateLimiter("localhost", 6379, 5, 60);

        for (int i = 0; i < 10; i++) {
            boolean allowed = rateLimiter.isAllowed("client1");
            System.out.println("Request " + (i + 1) + " allowed: " + allowed);
            try {
                Thread.sleep(500); // 模拟请求间隔
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        rateLimiter.close();
    }
}

2. 滑动窗口计数算法

滑动窗口计数算法通过记录多个小窗口内的请求数,计算滑动窗口内的总请求数。

示例代码

以下示例展示了如何使用Redis实现滑动窗口计数算法的分布式限流:

java 复制代码
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

import java.util.List;

public class SlidingWindowRateLimiter {

    private Jedis jedis;
    private int maxRequests;
    private int windowSize; // 窗口大小,单位为秒
    private int interval; // 时间间隔,单位为秒

    public SlidingWindowRateLimiter(String host, int port, int maxRequests, int windowSize, int interval) {
        this.jedis = new Jedis(host, port);
        this.maxRequests = maxRequests;
        this.windowSize = windowSize;
        this.interval = interval;
    }

    public boolean isAllowed(String clientId) {
        String key = "rate_limiter:" + clientId;
        long currentTime = System.currentTimeMillis() / 1000;
        long windowStart = currentTime - windowSize;

        Transaction transaction = jedis.multi();
        transaction.zadd(key, currentTime, String.valueOf(currentTime));
        transaction.zremrangeByScore(key, 0, windowStart);
        transaction.zcard(key);
        transaction.expire(key, windowSize + interval);

        List<Object> results = transaction.exec();
        long requestCount = (long) results.get(2);

        return requestCount <= maxRequests;
    }

    public void close() {
        jedis.close();
    }

    public static void main(String[] args) {
        SlidingWindowRateLimiter rateLimiter = new SlidingWindowRateLimiter("localhost", 6379, 5, 60, 1);

        for (int i = 0; i < 10; i++) {
            boolean allowed = rateLimiter.isAllowed("client1");
            System.out.println("Request " + (i + 1) + " allowed: " + allowed);
            try {
                Thread.sleep(500); // 模拟请求间隔
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        rateLimiter.close();
    }
}

3. 令牌桶算法

令牌桶算法通过生成令牌来控制请求的速率。每次请求需要消耗一个令牌,如果桶中没有令牌,则请求被拒绝。

示例代码

以下示例展示了如何使用Redis实现令牌桶算法的分布式限流:

java 复制代码
import redis.clients.jedis.Jedis;

public class TokenBucketRateLimiter {

    private Jedis jedis;
    private int maxTokens;
    private int refillRate; // 令牌生成速率,单位为令牌/秒

    public TokenBucketRateLimiter(String host, int port, int maxTokens, int refillRate) {
        this.jedis = new Jedis(host, port);
        this.maxTokens = maxTokens;
        this.refillRate = refillRate;
    }

    public boolean isAllowed(String clientId) {
        String key = "rate_limiter:" + clientId;
        long currentTime = System.currentTimeMillis() / 1000;
        long lastRefillTime = jedis.hget(key, "lastRefillTime") == null ?
                0 : Long.parseLong(jedis.hget(key, "lastRefillTime"));
        int tokens = jedis.hget(key, "tokens") == null ?
                maxTokens : Integer.parseInt(jedis.hget(key, "tokens"));

        long tokensToAdd = (currentTime - lastRefillTime) * refillRate;
        tokens = Math.min(maxTokens, tokens + (int) tokensToAdd);
        lastRefillTime = currentTime;

        if (tokens > 0) {
            jedis.hset(key, "tokens", String.valueOf(tokens - 1));
            jedis.hset(key, "lastRefillTime", String.valueOf(lastRefillTime));
            return true;
        } else {
            jedis.hset(key, "tokens", String.valueOf(tokens));
            jedis.hset(key, "lastRefillTime", String.valueOf(lastRefillTime));
            return false;
        }
    }

    public void close() {
        jedis.close();
    }

    public static void main(String[] args) {
        TokenBucketRateLimiter rateLimiter = new TokenBucketRateLimiter("localhost", 6379, 5, 1);

        for (int i = 0; i < 10; i++) {
            boolean allowed = rateLimiter.isAllowed("client1");
            System.out.println("Request " + (i + 1) + " allowed: " + allowed);
            try {
                Thread.sleep(500); // 模拟请求间隔
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        rateLimiter.close();
    }
}

4. 令牌桶算法与Lua脚本

为了确保限流操作的原子性,可以使用Redis的Lua脚本。以下示例展示了如何结合Lua脚本和令牌桶算法来实现分布式限流。

Lua脚本

保存为token_bucket.lua

lua 复制代码
local key = KEYS[1]
local maxTokens = tonumber(ARGV[1])
local refillRate = tonumber(ARGV[2])
local currentTime = tonumber(ARGV[3])

local tokens = tonumber(redis.call("hget", key, "tokens") or maxTokens)
local lastRefillTime = tonumber(redis.call("hget", key, "lastRefillTime") or 0)

local tokensToAdd = math.floor((currentTime - lastRefillTime) * refillRate)
tokens = math.min(maxTokens, tokens + tokensToAdd)
lastRefillTime = currentTime

if tokens > 0 then
    redis.call("hset", key, "tokens", tokens - 1)
    redis.call("hset", key, "lastRefillTime", lastRefillTime)
    return 1
else
    redis.call("hset", key, "tokens", tokens)
    redis.call("hset", key, "lastRefillTime", lastRefillTime)
    return 0
end
Java代码
java 复制代码
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class TokenBucketRateLimiterWithLua {

    private JedisPool jedisPool;
    private String luaScript;
    private String scriptSha;

    public TokenBucketRateLimiterWithLua(String host, int port, String scriptPath) throws IOException {
        this.jedisPool = new JedisPool(host, port);
        this.luaScript = new String(Files.readAllBytes(Paths.get(scriptPath)));
        try (Jedis jedis = jedisPool.getResource()) {
            this.scriptSha = jedis.scriptLoad(luaScript);
        }
    }

    public boolean isAllowed(String clientId, int maxTokens, int refillRate) {
        String key = "rate_limiter:" + clientId;
        long currentTime = System.currentTimeMillis() / 1000;
相关推荐
李广坤19 小时前
MySQL 大表字段变更实践(改名 + 改类型 + 改长度)
数据库
初次攀爬者2 天前
ZooKeeper 实现分布式锁的两种方式
分布式·后端·zookeeper
爱可生开源社区2 天前
2026 年,优秀的 DBA 需要具备哪些素质?
数据库·人工智能·dba
随逸1772 天前
《从零搭建NestJS项目》
数据库·typescript
加号33 天前
windows系统下mysql多源数据库同步部署
数据库·windows·mysql
シ風箏3 天前
MySQL【部署 04】Docker部署 MySQL8.0.32 版本(网盘镜像及启动命令分享)
数据库·mysql·docker
李慕婉学姐3 天前
Springboot智慧社区系统设计与开发6n99s526(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
百锦再3 天前
Django实现接口token检测的实现方案
数据库·python·django·sqlite·flask·fastapi·pip
tryCbest3 天前
数据库SQL学习
数据库·sql
jnrjian3 天前
ORA-01017 查找机器名 用户名 以及library cache lock 参数含义
数据库·oracle