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

PHP如何實現(xiàn)令牌桶限流?
php 基于redis使用令牌桶算法實現(xiàn)流量控制
本文介紹php基于redis,使用令牌桶算法,實現(xiàn)訪問流量的控制,提供完整算法說明及演示實例,方便大家學(xué)習(xí)使用。
每當國內(nèi)長假期或重要節(jié)日時,國內(nèi)的景區(qū)或地鐵都會人山人海,導(dǎo)致負載過大,部分則會采用限流措施,限制進入的人數(shù),當區(qū)內(nèi)人數(shù)降低到一定值,再允許進入。
例如:
區(qū)內(nèi)最大允許人數(shù)為 M
區(qū)內(nèi)當前人數(shù)為 N
每進入一個人,N+1,當N = M時,則不允許進入
每離開一個人,N-1,當N < M時,可允許進入
系統(tǒng)在運行過程中,如遇上某些活動,訪問的人數(shù)會在一瞬間內(nèi)爆增,導(dǎo)致服務(wù)器瞬間壓力飆升,使系統(tǒng)超負荷工作。
當然我們可以增加服務(wù)器去分擔壓力,首先增加服務(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)取走一個令牌,當桶內(nèi)令牌為0,則不允許再訪問。
3.每隔一段時間,再放入令牌,最多使桶內(nèi)令牌滿額。(可以根據(jù)實際情況,每隔一段時間放入若干個令牌,或直接補滿令牌桶)
我們可以使用redis的隊列作為令牌桶容器使用,使用lPush(入隊),rPop(出隊),實現(xiàn)令牌加入與消耗的操作。
TrafficShaper.class.php
_config = $config; $this->_queue = $queue; $this->_max = $max; $this->_redis = $this->connect();
} /**
* 加入令牌
* @param Int $num 加入的令牌數(shù)量
* @return Int 加入的數(shù)量
*/
public function add($num=0){
// 當前剩余令牌數(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:
'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個令牌。
'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個令牌。
'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)令牌桶限流
文章出自:http://fisionsoft.com.cn/article/djpedjg.html


咨詢
建站咨詢
