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;
相关推荐
Victor3561 小时前
Redis(171)如何使用Redis实现分布式事务?
后端
Tsonglew1 小时前
Python 自由线程实现原理深度解析
后端·python
锋行天下9 小时前
公司内网部署大模型的探索之路
前端·人工智能·后端
码事漫谈10 小时前
C++异常安全保证:从理论到实践
后端
码事漫谈10 小时前
C++对象生命周期与析构顺序深度解析
后端
张较瘦_12 小时前
SpringBoot3 | SpringBoot中Entity、DTO、VO的通俗理解与实战
java·spring boot·后端
LucianaiB14 小时前
从 0 到 1 玩转 N8N——初识 N8N(入门必看)
后端
ChinaRainbowSea14 小时前
github 仓库主页美化定制
java·后端·github
程序猿小蒜14 小时前
基于springboot的医院资源管理系统开发与设计
java·前端·spring boot·后端·spring