Redis的6种限流算法

1. 固定窗口计数算法(Fixed Window Counter)

  • 原理:将时间划分为固定的窗口,如 1 秒、1 分钟等。在每个时间窗口内,计数器记录请求次数。如果请求次数超过设定的阈值,则触发限流。
  • 优点:实现简单,适合对短时间内的流量进行限制。
  • 缺点:在时间窗口边界,可能会出现瞬时流量峰值。例如,一个窗口结束时有大量请求,紧接着下一个窗口开始时也有大量请求,导致短时间内的实际请求数量远超限制。
  • <?php
    
    class FixedWindowRateLimiter
    {
        private $redis;
        private $rateLimitKey;
        private $timeWindow;
        private $maxRequests;
    
        /**
         * 构造函数,初始化Redis连接和限流参数
         * 
         * @param string $host Redis服务器地址
         * @param int $port Redis服务器端口
         * @param int $timeWindow 时间窗口的长度(秒)
         * @param int $maxRequests 时间窗口内的最大请求数
         */
        public function __construct($host = '127.0.0.1', $port = 6379, $timeWindow = 60, $maxRequests = 100)
        {
            $this->redis = new Redis();
            $this->redis->connect($host, $port);
    
            $this->timeWindow = $timeWindow;
            $this->maxRequests = $maxRequests;
            $this->rateLimitKey = 'rate_limit:' . date('Y-m-d H:i') . ':';
        }
    
        /**
         * 检查请求是否允许通过限流
         * 
         * @param string $clientId 客户端唯一标识符
         * @return bool 返回true表示请求被允许,false表示请求被拒绝
         */
        public function isRequestAllowed($clientId)
        {
            $currentWindowKey = $this->rateLimitKey . $clientId;
            
            // 获取当前窗口的请求次数
            $requestCount = $this->redis->get($currentWindowKey);
    
            if ($requestCount === false) {
                // 初始化当前窗口的请求次数为1,并设置过期时间为timeWindow
                $this->redis->set($currentWindowKey, 1, $this->timeWindow);
                return true;
            } else {
                // 如果请求次数超过最大限制,拒绝请求
                if ($requestCount >= $this->maxRequests) {
                    return false;
                }
    
                // 增加当前窗口的请求次数
                $this->redis->incr($currentWindowKey);
                return true;
            }
        }
    }
    
    // 示例用法
    $rateLimiter = new FixedWindowRateLimiter('127.0.0.1', 6379, 60, 10); // 1分钟窗口,最多10次请求
    
    $clientId = 'user_123'; // 这是一个唯一标识符,例如用户ID或IP地址
    
    if ($rateLimiter->isRequestAllowed($clientId)) {
        echo "请求被允许。\n";
        // 执行实际请求的操作
    } else {
        echo "请求被拒绝。您已达到限流上限。\n";
        // 返回错误响应,提示限流
    }
    

    代码解析

  • 构造函数

    • 初始化Redis连接。
    • 设置时间窗口长度(例如60秒)和最大请求次数(例如10次)。
    • 构建限流键(rateLimitKey),用于存储请求计数。
  • isRequestAllowed 方法

    • 获取当前时间窗口的请求计数。
    • 如果计数不存在(首次请求),初始化计数为1,并设置过期时间为时间窗口长度。
    • 如果请求次数未达到最大请求数,增加计数并允许请求。
    • 如果请求次数达到或超过最大请求数,拒绝请求。
  • 2. 滑动窗口计数算法(Sliding Window Counter)

  • 原理:与固定窗口计数不同,滑动窗口算法在时间上是滑动的,而不是固定的。它根据请求的时间戳动态调整窗口的起点和终点,只统计当前窗口内的请求数量。
  • 优点:可以更平滑地统计请求,避免固定窗口边界效应引起的瞬时流量峰值。
  • 缺点:实现相对复杂,需要更多的存储来维护请求的时间戳数据。
  • 滑动窗口算法的实现需要在 Redis 中记录每个请求的时间戳,并通过这些时间戳计算当前窗口内的请求数量。为了实现这个算法,我们将使用 Redis 的 ZSET(有序集合)数据结构来存储请求时间戳。
  • <?php
    
    class SlidingWindowRateLimiter
    {
        private $redis;
        private $rateLimitKey;
        private $timeWindow;
        private $maxRequests;
    
        /**
         * 构造函数,初始化Redis连接和限流参数
         *
         * @param string $host Redis服务器地址
         * @param int $port Redis服务器端口
         * @param int $timeWindow 时间窗口的长度(秒)
         * @param int $maxRequests 时间窗口内的最大请求数
         */
        public function __construct($host = '127.0.0.1', $port = 6379, $timeWindow = 60, $maxRequests = 100)
        {
            $this->redis = new Redis();
            $this->redis->connect($host, $port);
    
            $this->timeWindow = $timeWindow;
            $this->maxRequests = $maxRequests;
            $this->rateLimitKey = 'sliding_window_rate_limit:';
        }
    
        /**
         * 检查请求是否允许通过限流
         *
         * @param string $clientId 客户端唯一标识符
         * @return bool 返回true表示请求被允许,false表示请求被拒绝
         */
        public function isRequestAllowed($clientId)
        {
            $currentTime = microtime(true) * 1000; // 当前时间的毫秒数
            $windowStart = $currentTime - ($this->timeWindow * 1000); // 计算当前滑动窗口的开始时间
            $key = $this->rateLimitKey . $clientId;
    
            // 删除时间窗口之外的请求记录
            $this->redis->zRemRangeByScore($key, 0, $windowStart);
    
            // 获取当前窗口内的请求数量
            $currentRequestCount = $this->redis->zCard($key);
    
            if ($currentRequestCount < $this->maxRequests) {
                // 允许请求,将当前请求的时间戳添加到有序集合中
                $this->redis->zAdd($key, $currentTime, $currentTime);
                // 设置过期时间以避免无用数据长期占用内存
                $this->redis->expire($key, $this->timeWindow);
    
                return true;
            } else {
                // 请求被拒绝,因为请求数量超出限流阈值
                return false;
            }
        }
    }
    
    // 示例用法
    $rateLimiter = new SlidingWindowRateLimiter('127.0.0.1', 6379, 60, 10); // 1分钟窗口,最多10次请求
    
    $clientId = 'user_123'; // 这是一个唯一标识符,例如用户ID或IP地址
    
    if ($rateLimiter->isRequestAllowed($clientId)) {
        echo "请求被允许。\n";
        // 执行实际请求的操作
    } else {
        echo "请求被拒绝。您已达到限流上限。\n";
        // 返回错误响应,提示限流
    }
    

    代码解析

  • 构造函数

    • 初始化 Redis 连接。
    • 设置时间窗口的长度(例如 60 秒)和最大请求数(例如 10 次)。
    • 构建限流键(rateLimitKey),用于存储请求的时间戳。
  • isRequestAllowed 方法

    • 获取当前时间的毫秒数,并计算当前滑动窗口的开始时间。
    • 使用 Redis 的 zRemRangeByScore 方法,删除时间窗口之前的请求记录。
    • 使用 zCard 方法获取当前窗口内的请求数量。
    • 如果请求数量小于最大限制,则允许请求,并将当前时间戳添加到有序集合中,设置过期时间以减少 Redis 内存占用。
    • 如果请求数量超过最大限制,则拒绝请求。
  • 3. 漏桶算法(Leaky Bucket Algorithm)

  • 原理:漏桶算法将请求放入一个固定容量的桶中,请求以固定的速率从桶中流出。如果桶满了,新的请求就会被丢弃。
  • 优点:输出速率稳定,适合控制数据流的平均传输速率。
  • 缺点:突发流量在短时间内会被丢弃,无法适应突发流量。
    <?php
    
    class LeakyBucketRateLimiter
    {
        private $redis;
        private $rateLimitKey;
        private $bucketCapacity;
        private $leakRate; // 单位时间内允许的请求数
        private $lastLeakTime; // 上一次漏水的时间
    
        /**
         * 构造函数,初始化Redis连接和限流参数
         *
         * @param string $host Redis服务器地址
         * @param int $port Redis服务器端口
         * @param int $bucketCapacity 桶的容量(即允许的最大请求数)
         * @param int $leakRate 请求漏出的速率(每秒钟允许的请求数)
         */
        public function __construct($host = '127.0.0.1', $port = 6379, $bucketCapacity = 100, $leakRate = 10)
        {
            $this->redis = new Redis();
            $this->redis->connect($host, $port);
    
            $this->bucketCapacity = $bucketCapacity;
            $this->leakRate = $leakRate;
            $this->rateLimitKey = 'leaky_bucket:';
        }
    
        /**
         * 检查请求是否允许通过限流
         *
         * @param string $clientId 客户端唯一标识符
         * @return bool 返回true表示请求被允许,false表示请求被拒绝
         */
        public function isRequestAllowed($clientId)
        {
            $currentTime = time();
            $key = $this->rateLimitKey . $clientId;
    
            // 获取当前桶中的请求数和上次漏水的时间
            $bucketInfo = $this->redis->hGetAll($key);
            $currentBucketSize = isset($bucketInfo['size']) ? (int)$bucketInfo['size'] : 0;
            $lastLeakTime = isset($bucketInfo['last_leak_time']) ? (int)$bucketInfo['last_leak_time'] : $currentTime;
    
            // 计算漏水量
            $elapsedTime = $currentTime - $lastLeakTime;
            $leakedCount = (int)($elapsedTime * $this->leakRate);
    
            // 更新桶的大小
            $newBucketSize = max(0, $currentBucketSize - $leakedCount);
    
            // 更新漏水时间
            $this->redis->hSet($key, 'last_leak_time', $currentTime);
    
            if ($newBucketSize < $this->bucketCapacity) {
                // 如果桶未满,允许请求
                $this->redis->hSet($key, 'size', $newBucketSize + 1);
                return true;
            } else {
                // 如果桶满了,拒绝请求
                return false;
            }
        }
    }
    
    // 示例用法
    $rateLimiter = new LeakyBucketRateLimiter('127.0.0.1', 6379, 10, 1); // 桶容量为10,每秒1个请求的速率
    
    $clientId = 'user_123'; // 这是一个唯一标识符,例如用户ID或IP地址
    
    if ($rateLimiter->isRequestAllowed($clientId)) {
        echo "请求被允许。\n";
        // 执行实际请求的操作
    } else {
        echo "请求被拒绝。您已达到限流上限。\n";
        // 返回错误响应,提示限流
    }
    

    代码解析

  • 构造函数

    • 初始化 Redis 连接。
    • 设置桶的容量(bucketCapacity)和漏出的速率(leakRate)。
    • 构建限流键(rateLimitKey),用于存储桶的状态。
  • isRequestAllowed 方法

    • 获取当前时间戳。
    • 计算自上次漏水后的时间间隔。
    • 计算漏水量(即在时间间隔内漏出的请求数)。
    • 更新桶的大小,移除已经漏出的请求。
    • 如果桶未满,则允许新的请求进入桶,并增加桶的当前大小。
    • 如果桶满了,则拒绝请求。

      使用场景

      这种实现可以用于需要平稳控制请求速率的场景,比如限制API请求的速率或限速下载。通过控制桶的容量和漏出的速率,可以实现不同的限流策略。

  • 4. 令牌桶算法(Token Bucket Algorithm)

  • 原理:令牌桶算法维持一个令牌桶,系统以固定的速率向桶中添加令牌。每个请求需要消耗一个令牌才能被处理。如果桶中没有足够的令牌,请求就会被限流或等待。
  • 优点:允许一定的突发流量(当桶中有积累的令牌时),在控制请求速率的同时又能适应突发流量。
  • 缺点:突发流量过大时,可能会快速消耗掉所有的令牌,导致后续请求被限流。
    <?php
    
    class TokenBucketRateLimiter
    {
        private $redis;
        private $rateLimitKey;
        private $bucketCapacity; // 令牌桶的容量(最大令牌数)
        private $tokenRate; // 令牌生成速率(每秒生成的令牌数)
    
        /**
         * 构造函数,初始化Redis连接和限流参数
         *
         * @param string $host Redis服务器地址
         * @param int $port Redis服务器端口
         * @param int $bucketCapacity 令牌桶的容量(最大令牌数)
         * @param int $tokenRate 令牌生成速率(每秒生成的令牌数)
         */
        public function __construct($host = '127.0.0.1', $port = 6379, $bucketCapacity = 100, $tokenRate = 10)
        {
            $this->redis = new Redis();
            $this->redis->connect($host, $port);
    
            $this->bucketCapacity = $bucketCapacity;
            $this->tokenRate = $tokenRate;
            $this->rateLimitKey = 'token_bucket:';
        }
    
        /**
         * 检查请求是否允许通过限流
         *
         * @param string $clientId 客户端唯一标识符
         * @return bool 返回true表示请求被允许,false表示请求被拒绝
         */
        public function isRequestAllowed($clientId)
        {
            $currentTime = time();
            $key = $this->rateLimitKey . $clientId;
    
            // 获取当前令牌数和上次令牌更新的时间
            $bucketInfo = $this->redis->hGetAll($key);
            $currentTokenCount = isset($bucketInfo['tokens']) ? (int)$bucketInfo['tokens'] : $this->bucketCapacity;
            $lastTokenTime = isset($bucketInfo['last_token_time']) ? (int)$bucketInfo['last_token_time'] : $currentTime;
    
            // 计算自上次令牌添加后的时间差,计算应添加的令牌数量
            $elapsedTime = $currentTime - $lastTokenTime;
            $newTokens = (int)($elapsedTime * $this->tokenRate);
    
            // 更新令牌数量
            $newTokenCount = min($this->bucketCapacity, $currentTokenCount + $newTokens);
    
            // 更新最后一次添加令牌的时间
            $this->redis->hSet($key, 'last_token_time', $currentTime);
    
            if ($newTokenCount > 0) {
                // 如果桶中有令牌,允许请求,并扣除一个令牌
                $this->redis->hSet($key, 'tokens', $newTokenCount - 1);
                return true;
            } else {
                // 如果没有令牌,拒绝请求
                return false;
            }
        }
    }
    
    // 示例用法
    $rateLimiter = new TokenBucketRateLimiter('127.0.0.1', 6379, 10, 1); // 桶容量为10,每秒1个令牌
    
    $clientId = 'user_123'; // 这是一个唯一标识符,例如用户ID或IP地址
    
    if ($rateLimiter->isRequestAllowed($clientId)) {
        echo "请求被允许。\n";
        // 执行实际请求的操作
    } else {
        echo "请求被拒绝。您已达到限流上限。\n";
        // 返回错误响应,提示限流
    }
    

    代码解析

  • 构造函数

    • 初始化 Redis 连接。
    • 设置令牌桶的容量(bucketCapacity)和令牌生成速率(tokenRate)。
    • 构建限流键(rateLimitKey),用于存储令牌桶的状态。
  • isRequestAllowed 方法

    • 获取当前时间戳。
    • 从 Redis 获取当前令牌数和上次令牌更新的时间。
    • 计算自上次更新后的时间差,并根据令牌生成速率计算新的令牌数。
    • 更新令牌数和上次更新的时间。
    • 如果桶中有令牌,允许请求并扣除一个令牌;否则拒绝请求。

      使用场景

      令牌桶算法适用于需要平稳流量控制但允许突发流量的场景,比如API速率限制、网络流量控制等。通过设置合理的桶容量和令牌生成速率,可以在流量控制和突发流量适应之间找到平衡。

  • 5. 计数器(Counter)

  • 原理:计数器是最简单的限流方式。它通过简单地计数一段时间内的请求数量来实现限流,比如在一分钟内只能允许100个请求。
  • 优点:实现简单,适用于简单的限流需求。
  • 缺点:对时间窗口边界的请求不敏感,容易出现流量突增的问题。
    <?php
    
    class CounterRateLimiter
    {
        private $redis;
        private $rateLimitKey;
        private $timeWindow; // 时间窗口的大小(秒)
        private $maxRequests; // 时间窗口内允许的最大请求数
    
        /**
         * 构造函数,初始化Redis连接和限流参数
         *
         * @param string $host Redis服务器地址
         * @param int $port Redis服务器端口
         * @param int $timeWindow 时间窗口的大小(秒)
         * @param int $maxRequests 时间窗口内允许的最大请求数
         */
        public function __construct($host = '127.0.0.1', $port = 6379, $timeWindow = 60, $maxRequests = 100)
        {
            $this->redis = new Redis();
            $this->redis->connect($host, $port);
    
            $this->timeWindow = $timeWindow;
            $this->maxRequests = $maxRequests;
            $this->rateLimitKey = 'counter_rate_limiter:';
        }
    
        /**
         * 检查请求是否允许通过限流
         *
         * @param string $clientId 客户端唯一标识符
         * @return bool 返回true表示请求被允许,false表示请求被拒绝
         */
        public function isRequestAllowed($clientId)
        {
            $key = $this->rateLimitKey . $clientId;
    
            // 获取当前计数器值
            $currentCount = $this->redis->get($key);
    
            if ($currentCount === false) {
                // 如果计数器不存在,初始化计数器和过期时间
                $this->redis->set($key, 1, $this->timeWindow);
                return true;
            }
    
            if ($currentCount < $this->maxRequests) {
                // 增加计数器值
                $this->redis->incr($key);
                return true;
            } else {
                // 达到请求上限,拒绝请求
                return false;
            }
        }
    }
    
    // 示例用法
    $rateLimiter = new CounterRateLimiter('127.0.0.1', 6379, 60, 100); // 一分钟内最多允许100个请求
    
    $clientId = 'user_123'; // 这是一个唯一标识符,例如用户ID或IP地址
    
    if ($rateLimiter->isRequestAllowed($clientId)) {
        echo "请求被允许。\n";
        // 执行实际请求的操作
    } else {
        echo "请求被拒绝。您已达到限流上限。\n";
        // 返回错误响应,提示限流
    }
    

  • 代码解析

  • 构造函数:

    • 初始化 Redis 连接。
    • 设置时间窗口的大小(timeWindow)和时间窗口内允许的最大请求数(maxRequests)。
    • 构建限流键(rateLimitKey),用于存储计数器的状态。
  • isRequestAllowed 方法:

    • 从 Redis 获取当前计数器值。
    • 如果计数器不存在(即当前时间窗口内没有请求),则初始化计数器为1并设置过期时间(等于时间窗口大小)。
    • 如果当前计数器值小于最大请求数,增加计数器值并允许请求。
    • 如果计数器值达到最大请求数,拒绝请求。

      使用场景

      计数器算法适用于一些简单的限流需求场景,例如防止用户短时间内的频繁操作。它的实现简单,适合不需要平滑流量控制的情况。尽管存在时间窗口边界效应,但在某些情况下可以忽略不计,或通过滑动窗口计数算法等更复杂的限流策略进行优化。

  •   6. 滑动窗口日志算法(Sliding Window Log)
    • 原理:维护一个请求时间戳的日志,每次请求到来时将其时间戳记录在日志中,并根据设定的时间窗口移除过期的时间戳。当前窗口内时间戳的数量即为请求数量。
    • 优点:精确统计时间窗口内的请求数量,避免了固定窗口边界的问题。
    • 缺点:需要存储所有请求的时间戳,内存开销大,不适合高频请求场景。
  •  <?php
    
    class SlidingWindowLogRateLimiter
    {
        private $redis;
        private $rateLimitKey;
        private $timeWindow; // 时间窗口大小(秒)
        private $maxRequests; // 时间窗口内允许的最大请求数
    
        /**
         * 构造函数,初始化Redis连接和限流参数
         *
         * @param string $host Redis服务器地址
         * @param int $port Redis服务器端口
         * @param int $timeWindow 时间窗口大小(秒)
         * @param int $maxRequests 时间窗口内允许的最大请求数
         */
        public function __construct($host = '127.0.0.1', $port = 6379, $timeWindow = 60, $maxRequests = 100)
        {
            $this->redis = new Redis();
            $this->redis->connect($host, $port);
    
            $this->timeWindow = $timeWindow;
            $this->maxRequests = $maxRequests;
            $this->rateLimitKey = 'sliding_window_log:';
        }
    
        /**
         * 检查请求是否允许通过限流
         *
         * @param string $clientId 客户端唯一标识符
         * @return bool 返回true表示请求被允许,false表示请求被拒绝
         */
        public function isRequestAllowed($clientId)
        {
            $key = $this->rateLimitKey . $clientId;
            $now = microtime(true); // 当前时间的时间戳(秒级,带小数)
    
            // 开始 Redis 事务
            $this->redis->multi();
    
            // 移除时间窗口外的过期时间戳
            $this->redis->zRemRangeByScore($key, 0, $now - $this->timeWindow);
    
            // 获取当前窗口内的请求数量
            $requestCount = $this->redis->zCard($key);
    
            if ($requestCount < $this->maxRequests) {
                // 当前请求数未超过限制,允许请求,记录当前请求时间戳
                $this->redis->zAdd($key, $now, $now);
                // 设置key的过期时间为时间窗口大小
                $this->redis->expire($key, $this->timeWindow);
    
                // 执行事务
                $this->redis->exec();
    
                return true;
            } else {
                // 达到请求上限,拒绝请求
                $this->redis->exec();
                return false;
            }
        }
    }
    
    // 示例用法
    $rateLimiter = new SlidingWindowLogRateLimiter('127.0.0.1', 6379, 60, 100); // 一分钟内最多允许100个请求
    
    $clientId = 'user_123'; // 这是一个唯一标识符,例如用户ID或IP地址
    
    if ($rateLimiter->isRequestAllowed($clientId)) {
        echo "请求被允许。\n";
        // 执行实际请求的操作
    } else {
        echo "请求被拒绝。您已达到限流上限。\n";
        // 返回错误响应,提示限流
    }
    

    代码解析

  • 构造函数:

    • 初始化 Redis 连接。
    • 设置时间窗口的大小(timeWindow)和时间窗口内允许的最大请求数(maxRequests)。
    • 构建限流键(rateLimitKey),用于存储请求日志的状态。
  • isRequestAllowed 方法:

    • 获取当前时间的时间戳($now)。
    • 使用 Redis 的事务(multi 和 exec)来确保操作的原子性。
    • 删除时间窗口外的过期时间戳,确保只统计当前窗口内的请求数量。
    • 检查当前窗口内的请求数量是否小于最大请求数。
    • 如果小于最大请求数,允许请求并记录当前请求的时间戳;否则,拒绝请求。
    • 使用场景

      滑动窗口日志算法适用于需要精确控制请求频率的场景,例如API请求限流、金融交易频率控制等。由于它能够精确统计时间窗口内的请求数量,可以有效防止因时间窗口切换导致的流量突增问题。不过,由于需要存储所有请求的时间戳,因此不适合高频请求的场景。在这些场景中,可以考虑使用滑动窗口计数算法或其他更高效的限流算法。

    • 7. Redis Cell(漏桶算法的 Redis 实现)

    • 原理:Redis Cell 是 Redis 提供的一种限流方式,基于漏桶算法实现。它通过 Redis 数据结构来模拟漏桶机制,具有高效性和持久性。
    • 优点:性能高,易于在分布式环境下使用。
    • 缺点:依赖 Redis,增加了外部系统的依赖。
    • 实现原理

    • 桶容量: 维护一个 Redis 键作为桶,记录当前桶中的水量(请求数)。
    • 漏水速率: 使用 Redis 键的过期时间来模拟桶的漏水速率。
    • 请求处理: 每当有请求到来时,检查桶是否有足够的容量。如果有足够的容量,则允许请求,并减少桶中的水量;如果没有足够的容量,则拒绝请求。
      <?php
      
      class LeakyBucketRateLimiter
      {
          private $redis;
          private $bucketKey;
          private $bucketCapacity; // 桶的容量(请求数)
          private $leakRate; // 漏水速率(请求数/秒)
          
          /**
           * 构造函数,初始化Redis连接和限流参数
           *
           * @param string $host Redis服务器地址
           * @param int $port Redis服务器端口
           * @param int $bucketCapacity 桶的容量(请求数)
           * @param float $leakRate 漏水速率(请求数/秒)
           */
          public function __construct($host = '127.0.0.1', $port = 6379, $bucketCapacity = 100, $leakRate = 1)
          {
              $this->redis = new Redis();
              $this->redis->connect($host, $port);
      
              $this->bucketCapacity = $bucketCapacity;
              $this->leakRate = $leakRate;
              $this->bucketKey = 'leaky_bucket:';
          }
      
          /**
           * 检查请求是否允许通过限流
           *
           * @param string $clientId 客户端唯一标识符
           * @return bool 返回true表示请求被允许,false表示请求被拒绝
           */
          public function isRequestAllowed($clientId)
          {
              $key = $this->bucketKey . $clientId;
              $currentTime = microtime(true);
      
              // 开始 Redis 事务
              $this->redis->multi();
      
              // 获取当前桶的状态
              $bucketData = $this->redis->hMGet($key, ['timestamp', 'water']);
      
              $lastTimestamp = isset($bucketData['timestamp']) ? $bucketData['timestamp'] : $currentTime;
              $currentWater = isset($bucketData['water']) ? $bucketData['water'] : 0;
      
              // 计算桶漏水后剩余的水量
              $elapsedTime = $currentTime - $lastTimestamp;
              $leakedWater = $elapsedTime * $this->leakRate;
              $newWater = max(0, $currentWater - $leakedWater);
      
              if ($newWater < $this->bucketCapacity) {
                  // 桶中有足够空间,允许请求
                  $newWater += 1; // 添加请求
                  $this->redis->hMSet($key, ['timestamp' => $currentTime, 'water' => $newWater]);
                  $this->redis->expire($key, ceil($this->bucketCapacity / $this->leakRate));
                  $this->redis->exec();
                  return true;
              } else {
                  // 桶满,拒绝请求
                  $this->redis->exec();
                  return false;
              }
          }
      }
      
      // 示例用法
      $rateLimiter = new LeakyBucketRateLimiter('127.0.0.1', 6379, 100, 1); // 桶容量为100,漏水速率为1请求/秒
      
      $clientId = 'user_123'; // 这是一个唯一标识符,例如用户ID或IP地址
      
      if ($rateLimiter->isRequestAllowed($clientId)) {
          echo "请求被允许。\n";
          // 执行实际请求的操作
      } else {
          echo "请求被拒绝。桶已满。\n";
          // 返回错误响应,提示限流
      }
      

      代码解析

    • 构造函数:

      • 初始化 Redis 连接。
      • 设置桶的容量(bucketCapacity)和漏水速率(leakRate)。
    • isRequestAllowed 方法:

      • 获取当前时间($currentTime)。
      • 从 Redis 中获取桶的当前状态(时间戳和水量)。
      • 计算自上次请求以来经过的时间,并根据漏水速率计算漏掉的水量。
      • 更新桶中的水量,并检查是否有足够的容量来接受新请求。
      • 如果桶中有足够的空间,允许请求并更新桶的状态;否则,拒绝请求。

        使用场景

        漏桶算法适用于需要控制请求的流量速率的场景,例如 API 请求限流、流量控制、带宽管理等。该实现可以有效控制请求的速率,适合需要高性能和简单实现的场景。然而,在高并发请求或极端负载下,Redis 的内存开销和性能开销可能成为瓶颈。

  • 13
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值