PHP+Redis令牌桶限流

一 、业务场景php


  在作项目系统接口服务的时候,为了防止客户端对于接口的滥用、保护服务器的资源, 一般来讲咱们会对于服务器上的各类接口进行请求次数的限制。好比对于某个用户,他在一个时间段内,好比 1 0秒,请求服务器接口的次数不可以大于一个上限,好比说5次。若是用户调用接口的次数超过上限的话,就直接拒绝用户的请求,返回错误信息。redis

  服务接口的流量控制策略:限流、分流、降级、熔断等。本文讨论下限流策略,虽然下降了服务接口的访问频率和并发量,却换取服务接口和业务应用系统的高可用。算法


2、经常使用的限流算法json

  

  一、漏桶算法服务器

  漏桶(Leaky Bucket)算法思路比较简单,(请求)先进入到漏桶里,漏桶以必定的速度出水(接口有响应速率),当水流进速度过大会直接致使桶溢出(访问频率超过接口响应速率),而后就拒绝请求,这里咱们能够得出结论:漏桶算法能强行限制数据的传输速率。网络

  漏桶算法的话通常有两个变量,一个是桶的大小,也就是你的承载量,好比流量突发增多时能够存多少的水,另外一个就是你可以去正常处理的请求,水桶漏洞的大小。并发

由于漏桶的漏出速率是固定的,因此即便网络中不存在资源冲突(没有发生拥塞),漏桶算法也不能使流量突发到端口。所以,漏桶算法对于存在突发特性的流量来讲缺少效率。分布式


  二、令牌桶算法ide

  令牌桶算法(Token Bucket)和Leaky Bucket 效果同样但方向相反的算法,随着时间流逝,系统会按你的并发量,好比恒定1/QPS时间间隔(若是QPS=100,则间隔是10ms)往桶里加入令牌,桶能够承载必定的令牌数,具体一个桶有多少令牌,这里是能够本身去控制的。当请求进来时,每一个请求都会拿走一个Token,若是没有Token拿了就阻塞等待或者拒绝服务。ui

图片


  Redis令牌桶算法还有一个优势就是能够方便的改变速度若是你须要提升速率,则按需提升放入桶中的令牌的速率通常会定时(好比10毫秒)往桶中增长必定数量的令牌有些算法则实时的计算应该增长的令牌的数量。


3、观摩PHP+Redis实现令牌桶算法
















































<?php namespace Api\Li; //令牌桶限流算法   class Rate{    private $minSecond = 20; //单个用户每分钟访问数    private $daySecond = 5000; //单个用户天天总的访问量     //    public function minLi($uid){        $minSecondKey = $uid . '_minSecond';        $daySecondKey = $uid . '_daySecond';        $resMin    = $this->getRedisLi($minSecondKey, $this->minSecond, 60);        $resDay    = $this->getRedisLi($minSecondKey, $this->minSecond, 86400);        if (!$resMin['status'] || !$resDay['status']) {            exit($resMin['msg'] . $resDay['msg']);        }    }     public function getRedisLi($key, $initNum, $expire){        $nowtime  = time();        $result   = ['status' => true, 'msg' => ''];        $redisObj = $this->di->get('redis');        $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;    }}

代码要点:


(1)定义规则

单个用户每分钟访问次数($minSecond),单个用户天天总的访问次数($daySecond)。

(2)计算速率

该代码示例以秒为最小的时间单位,速率=访问次数/时间($initNum / $expire

(3)每次消耗令牌后须要补充的令牌个数--计算方式

获取上次请求的时间即上次存入令牌的时间,计算当前时刻与上次请求的时间差乘以速率就是这次须要补充的令牌个数,切记!补充令牌后的总令牌数不能大于初始化令牌数,以补充数和初始化数的最小值为标准。

(4)程序流程

第一次请求时初始化令牌个数($minSecond),存入Redis同时将当前的时间戳存入以便计算下次须要补充的令牌个数。第二次访问时获取剩余的令牌个数,并添加本次应该补充的令牌个数,补充后如何令牌数>0则当前访问是有效的能够访问,不然令牌使用完毕不可访问。先补充令牌再判断令牌是否>0的缘由是因为还有速率这个概念即若是上次剩余的令牌为0可是本次应该补充的令牌>1那么本次依然能够访问。

(5)针对并发的处理

  • 5.1使用乐观锁+事务解决。

  • 5.2redis无需集合+list队列

  • 5.3redis+lua分布式锁

相关文章
相关标签/搜索