Redis令牌桶算法(全网最全,后续可以接入lua做原子性操作)

Redis令牌桶算法(全网最全,后续可以接入lua做原子性操作)
最新回答
浮华沧桑

2026-10-23 14:21:07

Redis令牌桶算法是一种基于Redis实现的限流算法,用于控制接口的访问频率,防止接口滥用,保障服务器资源的高可用性。

一、令牌桶算法原理
  • 基本概念:

    令牌桶算法以恒定的速率向桶中添加令牌,新请求到达时需要从桶中获取令牌。

    如果桶中有足够的令牌,请求被允许;否则,请求被拒绝或阻塞。

    桶有一定的容量,可以应对短时间的流量突发。

  • 关键参数:

    速率(Rate):单位时间内向桶中添加的令牌数量。

    容量(Capacity):桶中可以存储的最大令牌数量。

二、基于Redis的令牌桶算法实现1. 数据结构设计
  • Redis键:使用一个键来存储令牌桶的状态,包括剩余令牌数和上次更新时间。
  • 数据格式:使用JSON格式存储剩余令牌数(num)和上次更新时间(time)。
2. 算法流程
  • 初始化:

    第一次访问时,初始化令牌桶,设置剩余令牌数为初始值(initNum),并记录当前时间戳。

  • 获取令牌:

    获取当前剩余令牌数和上次更新时间。

    计算自上次更新以来应该添加的令牌数:新增令牌数 = (当前时间 - 上次更新时间) * 速率其中速率为 initNum / 桶的过期时间(秒)。

    更新剩余令牌数:剩余令牌数 = min(初始令牌数, 剩余令牌数 - 1 + 新增令牌数)。

    如果剩余令牌数大于0,允许访问;否则,拒绝访问。

  • 并发控制:

    使用Redis的WATCH命令监视令牌桶的键,确保在事务执行期间键未被其他客户端修改。

    使用MULTI和EXEC命令保证更新操作的原子性。

3. PHP代码示例<?phpnamespace ApiLib;class RateLimit { private $minNum = 60; // 单个用户每分钟访问数 private $dayNum = 10000; // 单个用户每天总的访问量 public function minLimit($uid) { $minNumKey = $uid . '_minNum'; $dayNumKey = $uid . '_dayNum'; $resMin = $this->getRedis($minNumKey, $this->minNum, 60); $resDay = $this->getRedis($dayNumKey, $this->dayNum, 86400); if (!$resMin['status'] || !$resDay['status']) { exit($resMin['msg'] . $resDay['msg']); } } public function getRedis($key, $initNum, $expire) { $nowtime = time(); $result = ['status' => true, 'msg' => '']; $redisObj = $this->di->get('redis'); $redis = $redisObj; $redis->watch($key); $limitVal = $redis->get($key); if ($limitVal) { $limitVal = json_decode($limitVal, true); $newNum = min($initNum, ($limitVal['num'] - 1) + (($initNum / $expire) * ($nowtime - $limitVal['time']))); if ($newNum > 0) { $redisVal = json_encode(['num' => $newNum, 'time' => time()]); } else { return ['status' => false, 'msg' => '当前时刻令牌消耗完!']; } } else { $redisVal = json_encode(['num' => $initNum, 'time' => time()]); } $redis->multi(); $redis->set($key, $redisVal); $rob_result = $redis->exec(); if (!$rob_result) { $result = ['status' => false, 'msg' => '访问频次过多!']; } return $result; }}三、Redis乐观锁在令牌桶算法中的应用
  • 乐观锁机制:

    Redis通过WATCH命令提供乐观锁功能,监视一个或多个键。

    如果被监视的键在事务执行前被其他客户端修改,事务将失败,EXEC命令返回nil。

  • 在令牌桶算法中的作用:

    确保在并发环境下,令牌的获取和更新操作是原子的。

    防止多个客户端同时获取和更新令牌数,导致数据不一致。

四、接入Lua脚本实现原子性操作
  • Lua脚本的优势:

    Redis支持Lua脚本,可以在服务器端原子性地执行多个命令。

    避免网络传输和客户端处理的开销,提高性能。

  • Lua脚本示例:

    以下是一个简单的Lua脚本,用于实现令牌桶算法的令牌获取和更新:

-- KEYS[1]: 令牌桶的Redis键-- ARGV[1]: 初始令牌数-- ARGV[2]: 桶的过期时间(秒)local key = KEYS[1]local initNum = tonumber(ARGV[1])local expire = tonumber(ARGV[2])local now = redis.call('TIME')[1] -- 获取当前时间戳(秒)local limitVal = redis.call('GET', key)if limitVal then limitVal = cjson.decode(limitVal) local rate = initNum / expire local newNum = math.min(initNum, (limitVal.num - 1) + (rate * (now - limitVal.time))) if newNum > 0 then limitVal = {num = newNum, time = now} redis.call('SET', key, cjson.encode(limitVal)) return 1 -- 允许访问 else return 0 -- 令牌不足,拒绝访问 endelse local limitVal = {num = initNum, time = now} redis.call('SET', key, cjson.encode(limitVal)) return 1 -- 允许访问end
  • 说明:

    使用redis.call调用Redis命令。

    使用cjson库处理JSON编码和解码(需确保Redis服务器已安装cjson模块)。

    脚本返回1表示允许访问,0表示拒绝访问。

五、总结
  • 令牌桶算法是一种有效的限流算法,适用于控制接口的访问频率。
  • Redis提供了高性能的键值存储和乐观锁机制,适合实现令牌桶算法。
  • Lua脚本可以进一步提升性能,确保操作的原子性。
  • 通过合理设置速率和容量,可以平衡系统的可用性和流量突发处理能力。