企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
### 漏桶 漏桶(Leaky Bucket)算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率.示意图如下:![](https://mmbiz.qpic.cn/mmbiz_jpg/niaWWW4jIlibxibYHyPDX8KLjdEicAxgVdzvkGoIEX3icR4t6fiaJQ9pb6T5rvu0u5k6qhNqHyzibAJ6TcjHEhClIiadSQ/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1) 具体代码实现如下 ~~~ <?php class Funnel { private $capacity; private $leakingRate; private $leftQuote; private $leakingTs; public function __construct($capacity, $leakingRate) { $this->capacity = $capacity; //漏斗容量 $this->leakingRate = $leakingRate;//漏斗流水速率 $this->leftQuote = $capacity; //漏斗剩余空间 $this->leakingTs = time(); //上一次漏水时间 } public function makeSpace() { $now = time(); $deltaTs = $now-$this->leakingTs; //距离上一次漏水过去了多久 $deltaQuota = $deltaTs * $this->leakingRate; //可腾出的空间 if($deltaQuota < 1) { return; } $this->leftQuote += $deltaQuota; //增加剩余空间 $this->leakingTs = time(); //记录漏水时间 if($this->leftQuota > $this->capacaty){ $this->leftQuote - $this->capacity; } } public function watering($quota) { $this->makeSpace(); //漏水操作 if($this->leftQuote >= $quota) { $this->leftQuote -= $quota; return true; } return false; } } $funnels = []; global $funnel; function isActionAllowed($userId, $action, $capacity, $leakingRate) { $key = sprintf("%s:%s", $userId, $action); $funnel = $GLOBALS['funnel'][$key] ?? ''; if (!$funnel) { $funnel = new Funnel($capacity, $leakingRate); $GLOBALS['funnel'][$key] = $funnel; } return $funnel->watering(1); } for ($i=0; $i<20; $i++){ var_dump(isActionAllowed("110", "reply", 15, 0.5)); //执行可以发现只有前15次是通过的 } ~~~ 核心逻辑就是makeSpace,在每次灌水前调用以触发漏水,给漏斗腾出空间。funnels我们可以利用Redis中的hash结构来存储对应字段,灌水时将字段取出进行逻辑运算后再存入hash结构中即可完成一次行为频度的检测。但这有个问题就是整个过程的原子性无法保证,意味着要用锁来控制,但如果加锁失败,就要重试或者放弃,这回导致性能下降和影响用户体验,同时代码复杂度也升高了,此时Redis提供了一个插件,Redis-Cell出现了。 ##### Redis-Cell Redis 4.0提供了一个限流Redis模块,名称为redis-cell,该模块提供漏斗算法,并提供原子的限流指令。 该模块只有一条指令cl.throttle,其参数和返回值比较复杂。 ~~~ > cl.throttle tom:reply 14 30 60 1 1) (integer) 0 # 0表示允许,1表示拒绝 2) (integer) 15 # 漏斗容量capacity 3) (integer) 14 # 漏斗剩余空间left_quota 4) (integer) -1 # 如果拒绝了,需要多长时间后再重试,单位秒 5) (integer) 2 # 多长时间后,漏斗完全空出来,单位秒 ~~~ 该指令意思为,允许用户tom的reply行为的频率为每60s最多30次,漏斗初始容量为15(因为是从0开始计数,到14为15个),默认每个行为占据的空间为1(可选参数)。如果被拒绝,取返回数组的第四个值进行sleep即可作为重试时间,也可以异步定时任务来重试。