輕風解語 發表於 2026-1-11 10:05:58

Redis实现分布式限流的几种方法

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">1. 固定窗口计数算法</a></li><ul class="second_class_ul"><li><a href="#_lab2_0_0">示例代码</a></li></ul><li><a href="#_label1">2. 滑动窗口计数算法</a></li><ul class="second_class_ul"><li><a href="#_lab2_1_1">示例代码</a></li></ul><li><a href="#_label2">3. 令牌桶算法</a></li><ul class="second_class_ul"><li><a href="#_lab2_2_2">示例代码</a></li></ul><li><a href="#_label3">4. 令牌桶算法与Lua脚本</a></li><ul class="second_class_ul"><li><a href="#_lab2_3_3">Lua脚本</a></li><li><a href="#_lab2_3_4">Java代码</a></li></ul></ul></div><p>使用Redis实现分布式限流是一种常见且有效的方法,可以防止系统过载并确保公平的资源分配。Redis的高性能和丰富的数据结构使其成为实现分布式限流的理想选择。常见的限流算法包括固定窗口计数、滑动窗口计数和令牌桶算法。</p>
<p class="maodian"><a name="_label0"></a></p><h2>1. 固定窗口计数算法</h2>
<p>固定窗口计数算法将时间划分为固定长度的窗口,并在每个窗口内计数请求的数量。</p>
<p class="maodian"><a name="_lab2_0_0"></a></p><p class="maodian"><a name="_lab2_1_1"></a></p><p class="maodian"><a name="_lab2_2_2"></a></p><h3>示例代码</h3>
<p>以下示例展示了如何使用Redis实现固定窗口计数算法的分布式限流:</p>
<div class="jb51code"><pre class="brush: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 &lt;= 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 &lt; 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();
    }
}
</pre></div>
<p class="maodian"><a name="_label1"></a></p><h2>2. 滑动窗口计数算法</h2>
<p>滑动窗口计数算法通过记录多个小窗口内的请求数,计算滑动窗口内的总请求数。</p>
<h3>示例代码</h3>
<p>以下示例展示了如何使用Redis实现滑动窗口计数算法的分布式限流:</p>
<div class="jb51code"><pre class="brush: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&lt;Object&gt; results = transaction.exec();
      long requestCount = (long) results.get(2);

      return requestCount &lt;= 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 &lt; 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();
    }
}
</pre></div>
<p class="maodian"><a name="_label2"></a></p><h2>3. 令牌桶算法</h2>
<p>令牌桶算法通过生成令牌来控制请求的速率。每次请求需要消耗一个令牌,如果桶中没有令牌,则请求被拒绝。</p>
<h3>示例代码</h3>
<p>以下示例展示了如何使用Redis实现令牌桶算法的分布式限流:</p>
<div class="jb51code"><pre class="brush: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 &gt; 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 &lt; 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();
    }
}
</pre></div>
<p class="maodian"><a name="_label3"></a></p><h2>4. 令牌桶算法与Lua脚本</h2>
<p>为了确保限流操作的原子性,可以使用Redis的Lua脚本。以下示例展示了如何结合Lua脚本和令牌桶算法来实现分布式限流。</p>
<p class="maodian"><a name="_lab2_3_3"></a></p><h3>Lua脚本</h3>
<p>保存为<code>token_bucket.lua</code>:</p>
<div class="jb51code"><pre class="brush:plain;">local key = KEYS
local maxTokens = tonumber(ARGV)
local refillRate = tonumber(ARGV)
local currentTime = tonumber(ARGV)

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 &gt; 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
</pre></div>
<p class="maodian"><a name="_lab2_3_4"></a></p><h3>Java代码</h3>
<div class="jb51code"><pre class="brush: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;


</pre></div>
頁: [1]
查看完整版本: Redis实现分布式限流的几种方法