您好,登錄后才能下訂單哦!
小編給大家分享一下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è)資訊頻道!
免責(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)容。