例如:
區(qū)內(nèi)最大允許人數(shù)為 M
區(qū)內(nèi)當(dāng)前人數(shù)為 N
每進(jìn)入一個(gè)人,N+1,當(dāng)N = M時(shí),則不允許進(jìn)入
每離開(kāi)一個(gè)人,N-1,當(dāng)N < M時(shí),可允許進(jìn)入
系統(tǒng)在運(yùn)行過(guò)程中,如遇上某些活動(dòng),訪問(wèn)的人數(shù)會(huì)在一瞬間內(nèi)爆增,導(dǎo)致服務(wù)器瞬間壓力飆升,使系統(tǒng)超負(fù)荷工作。
當(dāng)然我們可以增加服務(wù)器去分擔(dān)壓力,首先增加服務(wù)器也需要一定的時(shí)間去配置,而且因?yàn)槟骋粋€(gè)活動(dòng)而增加服務(wù)器,活動(dòng)結(jié)束后這些服務(wù)器資源就浪費(fèi)了。
因此我們可以根據(jù)業(yè)務(wù)類型,先使用限流的方式去減輕服務(wù)器壓力。
與景區(qū)限流不同,系統(tǒng)的訪問(wèn)到結(jié)束的時(shí)間非常短,因此我們只需要知道每個(gè)訪問(wèn)持續(xù)的平均時(shí)間,設(shè)定最多同時(shí)訪問(wèn)的人數(shù)即可。
1.首先設(shè)有一個(gè)令牌桶,桶內(nèi)存放令牌,一開(kāi)始令牌桶內(nèi)的令牌是滿的(桶內(nèi)令牌的數(shù)量可根據(jù)服務(wù)器情況設(shè)定)。
2.每次訪問(wèn)從桶內(nèi)取走一個(gè)令牌,當(dāng)桶內(nèi)令牌為0,則不允許再訪問(wèn)。
3.每隔一段時(shí)間,再放入令牌,最多使桶內(nèi)令牌滿額。(可以根據(jù)實(shí)際情況,每隔一段時(shí)間放入若干個(gè)令牌,或直接補(bǔ)滿令牌桶)
我們可以使用redis的隊(duì)列作為令牌桶容器使用,使用lPush(入隊(duì)),rPop(出隊(duì)),實(shí)現(xiàn)令牌加入與消耗的操作。
TrafficShaper.html' target='_blank'>class.php
<?php/** * PHP基于Redis使用令牌桶算法實(shí)現(xiàn)流量控制 * Date: 2018-02-23 * Author: fdipzone * Version: 1.0 * * Descripton: * php基于Redis使用令牌桶算法實(shí)現(xiàn)流量控制,使用redis的隊(duì)列作為令牌桶容器,入隊(duì)(lPush)出隊(duì)(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對(duì)象 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); // 計(jì)算最大可加入的令牌數(shù)量,不能超過(guò)最大令牌數(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對(duì)象$oTrafficShaper = new TrafficShaper($config, $queue, $max);// 重設(shè)令牌桶,填滿令牌$oTrafficShaper->reset();// 循環(huán)獲取令牌,令牌桶內(nèi)只有5個(gè)令牌,因此最后3次獲取失敗for($i=0; $i<8; $i++){ var_dump($oTrafficShaper->get());}// 加入10個(gè)令牌,最大令牌為5,因此只能加入5個(gè)$add_num = $oTrafficShaper->add(10);var_dump($add_num);// 循環(huán)獲取令牌,令牌桶內(nèi)只有5個(gè)令牌,因此最后1次獲取失敗for($i=0; $i<6; $i++){ var_dump($oTrafficShaper->get());}?>
輸出:
boolean trueboolean trueboolean trueboolean trueboolean trueboolean falseboolean falseboolean falseint 5boolean trueboolean trueboolean trueboolean trueboolean trueboolean false
定期加入令牌,我們可以使用crontab實(shí)現(xiàn),每分鐘調(diào)用add方法加入若干令牌。crontab的使用可以參考:《Linux crontab定時(shí)執(zhí)行任務(wù) 命令格式與詳細(xì)例子》
crontab最小的執(zhí)行間隔為1分鐘,如果令牌桶內(nèi)的令牌在前幾秒就已經(jīng)被消耗完,那么剩下的幾十秒時(shí)間內(nèi),都獲取不到令牌,導(dǎo)致用戶等待時(shí)間較長(zhǎng)。
我們可以優(yōu)化加入令牌的算法,改為一分鐘內(nèi)每若干秒加入若干令牌,這樣可以保證一分鐘內(nèi)每段時(shí)間都有機(jī)會(huì)能獲取到令牌。
crontab調(diào)用的加入令牌程序如下,每秒自動(dòng)加入3個(gè)令牌。
<?php/** * 定時(shí)任務(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í)間間隔加入的令牌數(shù)$token_num = 3;// 時(shí)間間隔,最好是能被60整除的數(shù),保證覆蓋每一分鐘內(nèi)所有的時(shí)間$time_step = 1;// 執(zhí)行次數(shù)$exec_num = (int)(60/$time_step);// 創(chuàng)建TrafficShaper對(duì)象$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個(gè)令牌。
<?php/** * 模擬用戶訪問(wèn)消耗令牌,每段時(shí)間間隔消耗若干令牌 */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í)間間隔隨機(jī)消耗的令牌數(shù)量范圍$consume_token_range = array(2, 8);// 時(shí)間間隔$time_step = 1;// 創(chuàng)建TrafficShaper對(duì)象$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è)置定時(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
因令牌桶一開(kāi)始是滿的(最大令牌數(shù)10),所以之前的10次都能獲取到令牌,10次之后則會(huì)根據(jù)消耗的令牌大于加入令牌數(shù)時(shí),限制訪問(wèn)。
本文講解了php 基于redis使用令牌桶算法實(shí)現(xiàn)流量控制 ,更多相關(guān)內(nèi)容請(qǐng)關(guān)注 。
相關(guān)推薦:
Redis主從同步,讀寫(xiě)分離設(shè)置 的相關(guān)操作
介紹mysql重建表分區(qū)并保留數(shù)據(jù)的方法
PHP生成唯一RequestID類的相關(guān)內(nèi)容
以上就是php 基于redis使用令牌桶算法實(shí)現(xiàn)流量控制的詳細(xì)內(nèi)容,更多請(qǐng)關(guān)注 其它相關(guān)文章!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。
新聞熱點(diǎn)
疑難解答
圖片精選