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;
相关推荐
optimistic_chen8 分钟前
【Redis 系列】常用数据结构---String类型
数据结构·数据库·redis·缓存·string
程序员良辰8 分钟前
【面试读心术】一场Redis项目面试的AB面
redis·面试·职场和发展
大猫子的技术日记12 分钟前
Redis 快速上手实战教程:从零搭建高性能缓存系统
数据库·redis·缓存
莳花微语12 分钟前
记录一次生产中mysql主备延迟问题处理
数据库·mysql
Hello.Reader20 分钟前
Flink JDBC Driver把 Flink SQL Gateway 变成“数据库”,让 BI / 工具 / 应用直接用 JDBC 跑 Flink SQL
数据库·sql·flink
李宥小哥20 分钟前
SQLite02-安装
数据库
一 乐23 分钟前
景区管理|基于springboot + vue景区管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·学习
JIngJaneIL26 分钟前
基于java + vue连锁门店管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot
阿拉伯柠檬27 分钟前
MySQL内置函数(二)
linux·数据库·mysql·面试
杜子不疼.29 分钟前
从 0 到 1:基于 Spring Boot 4 + Redis + MySQL 构建高可用电商后端系统
spring boot·redis·mysql