限流策略之令牌桶和漏桶

     为了避免恶意行为导致的过载,通常采用令牌桶和漏桶进行限流。下面简单介绍一下令牌桶和漏桶的实现。

     对于每个独立的访客,redis 会为他建立两个 key,一个 key 保存了剩余令牌的数量,另外一个 key 保存了最近一次访问的时间戳。其中,最近一次访问时间戳在新访问到来时候用于计算时间间隔,从而计算在此时间间隔内应该向令牌桶中添加或减少多少令牌,进而获得当前令牌桶的剩余令牌数。

1. 令牌桶
    (如果有足够的令牌,则发放(返回true),如果不够,
      计算需要等待时间,
      得出的等待时间大于系统设置的最长等待时间,则不发放(返回false)
      得出的等待时间小于系统设置的最长等待时间,则sleep后,继续计算,
      有足够令牌返回true,否则返回false


 2. 漏桶
    先执行漏掉令牌,计算剩余可用令牌数量,
    如果当前需要的令牌数量+可用的数量,之和不大于桶的容量,则请求可以被处理返回true
    否则请求丢弃返回false

class BucketLimiter
{
    protected $maxTokens;
    protected $availableTokens = 100;
    protected $startMilli;
    protected $rate;
    protected $maxWaitTime = 100;
    protected $intervalTokenMilli;
    protected $timeout = 60;

    public function __construct($maxTokens, $rate)
    {
        $this->maxTokens          = $maxTokens;
        $this->rate               = $rate;
        $this->intervalTokenMilli = 1 / $rate;
        if ($this->di == null) {
            $this->di = \Phalcon\DI::getDefault();
        }
    }

    /**
     * 令牌桶获取令牌
     * @DateTime 2017-10-17T09:37:01+0800
     * @param    [type]                   $requierdToken [description]
     * @return   [type]                                  [description]
     */
    public function getTBToken($requierdToken, $id)
    {
        $now       = $this->getNowTime();
        $num       = $this->di['redis']->get('tbkt:num:' . $id);
        $startTime = $this->di['redis']->get('tbkt:start:' . $id);
        if ($startTime) {
            $this->startMilli      = $startTime;
            $this->availableTokens = $num;
            $lock                  = $this->di['redis']->lock();
            if ($lock) {
                // 先添加令牌
                $this->availableTokens = intval(min($this->maxTokens, $this->availableTokens + ($now - $this->startMilli) * $this->rate));
                $this->di['redis']->unlock();
            }
        }

        if ($requierdToken <= $this->availableTokens) {
            $this->availableTokens -= $requierdToken;
            $this->di['redis']->set('tbkt:num:' . $id, $this->availableTokens, '', $this->timeout);
            $this->di['redis']->set('tbkt:start:' . $id, $now, '', $this->timeout);
            return true;
        }

        $tokenPermitted = $this->availableTokens;
        $needNewToken   = $requierdToken - $tokenPermitted;

        $waitTime = $needNewToken * $this->intervalTokenMilli;

        if ($waitTime < $this->maxWaitTime) {
            usleep($waitTime * 1000);
            $this->startMilli      = $now;
            $now                   = $this->getNowTime();
            $this->availableTokens = min($this->maxTokens, $this->availableTokens + ($now - $this->startMilli) * $this->rate);
            if ($requierdToken <= $this->availableTokens) {
                $this->availableTokens -= $requierdToken;
                $this->di['redis']->set('tbkt:num:' . $id, $this->availableTokens, '', $this->timeout);
                $this->di['redis']->set('tbkt:start:' . $id, $now, '', $this->timeout);
                return true;
            }
        }

        return false;

    }

    /**
     * 漏桶
     * @DateTime 2017-10-17T09:37:36+0800
     * @param    [type]                   $requierdToken [description]
     * @return   [type]                                  [description]
     */
    public function getLBToken($requierdToken, $id)
    {
        $now       = getNowTime();
        $num       = $this->di['redis']->get('lbkt:num:' . $id);
        $startTime = $this->di['redis']->get('lbkt:start:' . $id);
        if ($startTime) {
            $this->availableTokens = $num;
            $this->startMilli      = $startTime;
            // 先执行漏令牌,计算剩余令牌量
            $this->availableTokens = max(0, $this->availableTokens - ($now - $this->startMilli) * $this->rate);
        }

        if (($this->availableTokens + $requierdToken) < $this->maxTokens) {
            // 尝试加令牌,并且令牌还未满
            $this->availableTokens += $requierdToken;
            $this->di['redis']->set('lbkt:num:' . $id, $this->availableTokens, '', $this->timeout);
            $this->di['redis']->set('lbkt:start:' . $id, $now, '', $this->timeout);
            return true;
        } else {
            // 桶满,拒绝加令牌
            return false;
        }
    }

    private function getNowTime()
    {
        list($s1, $s2) = explode(' ', microtime());
        return (float) sprintf('%.0f', (floatval($s1) + floatval($s2)) * 1000);
    }
}




 
 




   

 


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值