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;
相关推荐
2601_957084843 小时前
2026年生产型服装供应链SCM系统丨水洗参数、产能与验收节点架构
数据库·scm·服装供应链系统·服装scm·服装供应链
IvorySQL8 小时前
PostgreSQL 日报|恢复期 COPY TO 未日志表修复(10 月 21 日)
数据库·postgresql·区块链
贝猫说python9 小时前
大模型比赛2.3智能体开发(工具调用智能问答助手)
数据库·oracle
许彰午10 小时前
28-ORA-01110数据文件读取错误
数据库·oracle
凯凯姜10 小时前
MySQL 联合索引设计与索引失效
数据库·mysql
猫咪很可爱11 小时前
Oracle TEMP 表空间不足(ORA-01652)问题分析与优化实践
数据库·oracle
九零HTTP12 小时前
10月21号-CSDN配菜-IP池的5个坑
服务器·数据库·tcp/ip
Data-Miner13 小时前
需要做财务数据分析,有什么好用的工具?从准备到出报告的实操指南
大数据·数据库·数据分析
袋鼠云数栈13 小时前
让 SQL 解析不再重复造轮子:SqlParserX 正式开源
数据库·人工智能·sql·开源·数据治理
这个DBA有点耶14 小时前
期货 CTP 系统 Oracle RAC 迁移国产库实战:读写分离架构设计与性能对标
数据库·架构·dba