溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

PHP怎么實現(xiàn)令牌桶限流

發(fā)布時間:2021-11-23 10:33:03 來源:億速云 閱讀:162 作者:小新 欄目:編程語言

小編給大家分享一下PHP怎么實現(xiàn)令牌桶限流,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

PHP實現(xiàn)令牌桶限流的方法:1、設(shè)有一個令牌桶,桶內(nèi)存放令牌;2、每次訪問從桶內(nèi)取走一個令牌;3、根據(jù)實際情況,每隔一段時間放入若干個令牌或直接補滿令牌桶即可。

本文操作環(huán)境:Windows7系統(tǒng)、PHP7.1、Dell G3電腦。

php 基于redis使用令牌桶算法實現(xiàn)流量控制

每當(dāng)國內(nèi)長假期或重要節(jié)日時,國內(nèi)的景區(qū)或地鐵都會人山人海,導(dǎo)致負(fù)載過大,部分則會采用限流措施,限制進入的人數(shù),當(dāng)區(qū)內(nèi)人數(shù)降低到一定值,再允許進入。

例如:
區(qū)內(nèi)最大允許人數(shù)為 M
區(qū)內(nèi)當(dāng)前人數(shù)為 N
每進入一個人,N+1,當(dāng)N = M時,則不允許進入
每離開一個人,N-1,當(dāng)N < M時,可允許進入

系統(tǒng)在運行過程中,如遇上某些活動,訪問的人數(shù)會在一瞬間內(nèi)爆增,導(dǎo)致服務(wù)器瞬間壓力飆升,使系統(tǒng)超負(fù)荷工作。

當(dāng)然我們可以增加服務(wù)器去分擔(dān)壓力,首先增加服務(wù)器也需要一定的時間去配置,而且因為某一個活動而增加服務(wù)器,活動結(jié)束后這些服務(wù)器資源就浪費了。

因此我們可以根據(jù)業(yè)務(wù)類型,先使用限流的方式去減輕服務(wù)器壓力。

與景區(qū)限流不同,系統(tǒng)的訪問到結(jié)束的時間非常短,因此我們只需要知道每個訪問持續(xù)的平均時間,設(shè)定最多同時訪問的人數(shù)即可。

令牌桶算法

1.首先設(shè)有一個令牌桶,桶內(nèi)存放令牌,一開始令牌桶內(nèi)的令牌是滿的(桶內(nèi)令牌的數(shù)量可根據(jù)服務(wù)器情況設(shè)定)。

2.每次訪問從桶內(nèi)取走一個令牌,當(dāng)桶內(nèi)令牌為0,則不允許再訪問。

3.每隔一段時間,再放入令牌,最多使桶內(nèi)令牌滿額。(可以根據(jù)實際情況,每隔一段時間放入若干個令牌,或直接補滿令牌桶)

我們可以使用redis的隊列作為令牌桶容器使用,使用lPush(入隊),rPop(出隊),實現(xiàn)令牌加入與消耗的操作。
 
TrafficShaper.class.php

<?php
/**
 * PHP基于Redis使用令牌桶算法實現(xiàn)流量控制
 * Date:    2018-02-23
 * Author:  fdipzone
 * Version: 1.0
 *
 * Descripton:
 * php基于Redis使用令牌桶算法實現(xiàn)流量控制,使用redis的隊列作為令牌桶容器,入隊(lPush)出隊(rPop)作為令牌的加入與消耗操作。
 *
 * Func:
 * public  add     加入令牌
 * public  get     獲取令牌
 * public  reset   重設(shè)令牌桶
 * private connect 創(chuàng)建redis連接
 */class TrafficShaper{ // class start

    private $_config; // redis設(shè)定
    private $_redis;  // redis對象
    private $_queue;  // 令牌桶
    private $_max;    // 最大令牌數(shù)

    /**
     * 初始化
     * @param Array $config redis連接設(shè)定
     */
    public function __construct($config, $queue, $max){
        $this->_config = $config;        $this->_queue = $queue;        $this->_max = $max;        $this->_redis = $this->connect();
    }    /**
     * 加入令牌
     * @param  Int $num 加入的令牌數(shù)量
     * @return Int 加入的數(shù)量
     */
    public function add($num=0){

        // 當(dāng)前剩余令牌數(shù)
        $curnum = intval($this->_redis->lSize($this->_queue));        // 最大令牌數(shù)
        $maxnum = intval($this->_max);        // 計算最大可加入的令牌數(shù)量,不能超過最大令牌數(shù)
        $num = $maxnum>=$curnum+$num? $num : $maxnum-$curnum;        // 加入令牌
        if($num>0){            $token = array_fill(0, $num, 1);            $this->_redis->lPush($this->_queue, ...$token);            return $num;
        }        return 0;

    }    /**
     * 獲取令牌
     * @return Boolean
     */
    public function get(){
        return $this->_redis->rPop($this->_queue)? true : false;
    }    /**
     * 重設(shè)令牌桶,填滿令牌
     */
    public function reset(){
        $this->_redis->delete($this->_queue);        $this->add($this->_max);
    }    /**
     * 創(chuàng)建redis連接
     * @return Link
     */
    private function connect(){
        try{            $redis = new Redis();            $redis->connect($this->_config['host'],$this->_config['port'],$this->_config['timeout'],$this->_config['reserved'],$this->_config['retry_interval']);            if(empty($this->_config['auth'])){                $redis->auth($this->_config['auth']);
            }            $redis->select($this->_config['index']);
        }catch(RedisException $e){            throw new Exception($e->getMessage());            return false;
        }        return $redis;
    }


} // class end?>

demo:

<?php
/**
 * 演示令牌加入與消耗
 */
require 'TrafficShaper.class.php';

// redis連接設(shè)定
$config = array(
    'host' => 'localhost',
    'port' => 6379,
    'index' => 0,
    'auth' => '',
    'timeout' => 1,
    'reserved' => NULL,
    'retry_interval' => 100,
);

// 令牌桶容器
$queue = 'mycontainer';

// 最大令牌數(shù)
$max = 5;

// 創(chuàng)建TrafficShaper對象
$oTrafficShaper = new TrafficShaper($config, $queue, $max);

// 重設(shè)令牌桶,填滿令牌
$oTrafficShaper->reset();

// 循環(huán)獲取令牌,令牌桶內(nèi)只有5個令牌,因此最后3次獲取失敗
for($i=0; $i<8; $i++){
    var_dump($oTrafficShaper->get());
}

// 加入10個令牌,最大令牌為5,因此只能加入5個
$add_num = $oTrafficShaper->add(10);

var_dump($add_num);

// 循環(huán)獲取令牌,令牌桶內(nèi)只有5個令牌,因此最后1次獲取失敗
for($i=0; $i<6; $i++){
    var_dump($oTrafficShaper->get());
}

?>

輸出:

boolean true
boolean true
boolean true
boolean true
boolean true
boolean false
boolean false
boolean false
int 5
boolean true
boolean true
boolean true
boolean true
boolean true
boolean false

定期加入令牌算法

定期加入令牌,我們可以使用crontab實現(xiàn),每分鐘調(diào)用add方法加入若干令牌。

crontab最小的執(zhí)行間隔為1分鐘,如果令牌桶內(nèi)的令牌在前幾秒就已經(jīng)被消耗完,那么剩下的幾十秒時間內(nèi),都獲取不到令牌,導(dǎo)致用戶等待時間較長。

我們可以優(yōu)化加入令牌的算法,改為一分鐘內(nèi)每若干秒加入若干令牌,這樣可以保證一分鐘內(nèi)每段時間都有機會能獲取到令牌。
 
crontab調(diào)用的加入令牌程序如下,每秒自動加入3個令牌。

<?php
/**
 * 定時任務(wù)加入令牌
 */
require 'TrafficShaper.class.php';

// redis連接設(shè)定
$config = array(
    'host' => 'localhost',
    'port' => 6379,
    'index' => 0,
    'auth' => '',
    'timeout' => 1,
    'reserved' => NULL,
    'retry_interval' => 100,
);

// 令牌桶容器
$queue = 'mycontainer';

// 最大令牌數(shù)
$max = 10;

// 每次時間間隔加入的令牌數(shù)
$token_num = 3;

// 時間間隔,最好是能被60整除的數(shù),保證覆蓋每一分鐘內(nèi)所有的時間
$time_step = 1;

// 執(zhí)行次數(shù)
$exec_num = (int)(60/$time_step);

// 創(chuàng)建TrafficShaper對象
$oTrafficShaper = new TrafficShaper($config, $queue, $max);

for($i=0; $i<$exec_num; $i++){
    $add_num = $oTrafficShaper->add($token_num);
    echo '['.date('Y-m-d H:i:s').'] add token num:'.$add_num.PHP_EOL;
    sleep($time_step);
}

?>

模擬消耗程序如下,每秒消耗2-8個令牌。

<?php
/**
 * 模擬用戶訪問消耗令牌,每段時間間隔消耗若干令牌
 */
require 'TrafficShaper.class.php';

// redis連接設(shè)定
$config = array(
    'host' => 'localhost',
    'port' => 6379,
    'index' => 0,
    'auth' => '',
    'timeout' => 1,
    'reserved' => NULL,
    'retry_interval' => 100,
);

// 令牌桶容器
$queue = 'mycontainer';

// 最大令牌數(shù)
$max = 10;

// 每次時間間隔隨機消耗的令牌數(shù)量范圍
$consume_token_range = array(2, 8);

// 時間間隔
$time_step = 1;

// 創(chuàng)建TrafficShaper對象
$oTrafficShaper = new TrafficShaper($config, $queue, $max);

// 重設(shè)令牌桶,填滿令牌
$oTrafficShaper->reset();

// 執(zhí)行令牌消耗
while(true){
    $consume_num = mt_rand($consume_token_range[0], $consume_token_range[1]);
    for($i=0; $i<$consume_num; $i++){
        $status = $oTrafficShaper->get();
        echo '['.date('Y-m-d H:i:s').'] consume token:'.($status? 'true' : 'false').PHP_EOL;
    }
    sleep($time_step);
}

?>

演示

設(shè)置定時任務(wù),每分鐘執(zhí)行一次

* * * * * php /程序的路徑/cron_add.php >> /tmp/cron_add.log

執(zhí)行模擬消耗

php consume_demo.php

執(zhí)行結(jié)果:

[2018-02-23 11:42:57] consume token:true
[2018-02-23 11:42:57] consume token:true
[2018-02-23 11:42:57] consume token:true
[2018-02-23 11:42:57] consume token:true
[2018-02-23 11:42:57] consume token:true
[2018-02-23 11:42:57] consume token:true
[2018-02-23 11:42:57] consume token:true
[2018-02-23 11:42:58] consume token:true
[2018-02-23 11:42:58] consume token:true
[2018-02-23 11:42:58] consume token:true
[2018-02-23 11:42:58] consume token:true
[2018-02-23 11:42:58] consume token:true
[2018-02-23 11:42:58] consume token:true
[2018-02-23 11:42:58] consume token:false
[2018-02-23 11:42:59] consume token:true
[2018-02-23 11:42:59] consume token:true
[2018-02-23 11:42:59] consume token:true
[2018-02-23 11:42:59] consume token:false
[2018-02-23 11:42:59] consume token:false
[2018-02-23 11:42:59] consume token:false
[2018-02-23 11:42:59] consume token:false
[2018-02-23 11:43:00] consume token:true
[2018-02-23 11:43:00] consume token:true
[2018-02-23 11:43:00] consume token:true
[2018-02-23 11:43:00] consume token:false
[2018-02-23 11:43:00] consume token:false

因令牌桶一開始是滿的(最大令牌數(shù)10),所以之前的10次都能獲取到令牌,10次之后則會根據(jù)消耗的令牌大于加入令牌數(shù)時,限制訪問。

以上是“PHP怎么實現(xiàn)令牌桶限流”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

php
AI