• 技术文章 >后端开发 >PHP问题

    PHP如何实现令牌桶限流

    藏色散人藏色散人2021-11-23 10:08:44原创1117

    PHP实现令牌桶限流的方法:1、设有一个令牌桶,桶内存放令牌;2、每次访问从桶内取走一个令牌;3、根据实际情况,每隔一段时间放入若干个令牌或直接补满令牌桶即可。

    本文操作环境:Windows7系统、PHP7.1、Dell G3电脑。

    PHP如何实现令牌桶限流?

    php 基于redis使用令牌桶算法实现流量控制

    本文介绍php基于redis,使用令牌桶算法,实现访问流量的控制,提供完整算法说明及演示实例,方便大家学习使用。

    每当国内长假期或重要节日时,国内的景区或地铁都会人山人海,导致负载过大,部分则会采用限流措施,限制进入的人数,当区内人数降低到一定值,再允许进入。

    例如:
    区内最大允许人数为 M
    区内当前人数为 N
    每进入一个人,N+1,当N = M时,则不允许进入
    每离开一个人,N-1,当N < M时,可允许进入

    系统在运行过程中,如遇上某些活动,访问的人数会在一瞬间内爆增,导致服务器瞬间压力飙升,使系统超负荷工作。

    当然我们可以增加服务器去分担压力,首先增加服务器也需要一定的时间去配置,而且因为某一个活动而增加服务器,活动结束后这些服务器资源就浪费了。

    因此我们可以根据业务类型,先使用限流的方式去减轻服务器压力。

    与景区限流不同,系统的访问到结束的时间非常短,因此我们只需要知道每个访问持续的平均时间,设定最多同时访问的人数即可。

    令牌桶算法

    1.首先设有一个令牌桶,桶内存放令牌,一开始令牌桶内的令牌是满的(桶内令牌的数量可根据服务器情况设定)。

    2.每次访问从桶内取走一个令牌,当桶内令牌为0,则不允许再访问。

    3.每隔一段时间,再放入令牌,最多使桶内令牌满额。(可以根据实际情况,每隔一段时间放入若干个令牌,或直接补满令牌桶)

    我们可以使用redis的队列作为令牌桶容器使用,使用lPush(入队),rPop(出队),实现令牌加入与消耗的操作。

    TrafficShaper.class.php

    <?php
    /**
     * PHP基于Redis使用令牌桶算法实现流量控制
     * Date:    2018-02-23
     * Author:  fdipzone
     * Version: 1.0
     *
     * Descripton:
     * php基于Redis使用令牌桶算法实现流量控制,使用redis的队列作为令牌桶容器,入队(lPush)出队(rPop)作为令牌的加入与消耗操作。
     *
     * Func:
     * public  add     加入令牌
     * public  get     获取令牌
     * public  reset   重设令牌桶
     * private connect 创建redis连接
     */class TrafficShaper{ // class start
    
        private $_config; // redis设定
        private $_redis;  // redis对象
        private $_queue;  // 令牌桶
        private $_max;    // 最大令牌数
    
        /**
         * 初始化
         * @param Array $config redis连接设定
         */
        public function __construct($config, $queue, $max){
            $this->_config = $config;        $this->_queue = $queue;        $this->_max = $max;        $this->_redis = $this->connect();
        }    /**
         * 加入令牌
         * @param  Int $num 加入的令牌数量
         * @return Int 加入的数量
         */
        public function add($num=0){
    
            // 当前剩余令牌数
            $curnum = intval($this->_redis->lSize($this->_queue));        // 最大令牌数
            $maxnum = intval($this->_max);        // 计算最大可加入的令牌数量,不能超过最大令牌数
            $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;
        }    /**
         * 重设令牌桶,填满令牌
         */
        public function reset(){
            $this->_redis->delete($this->_queue);        $this->add($this->_max);
        }    /**
         * 创建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连接设定
    $config = array(
        'host' => 'localhost',
        'port' => 6379,
        'index' => 0,
        'auth' => '',
        'timeout' => 1,
        'reserved' => NULL,
        'retry_interval' => 100,
    );
    
    // 令牌桶容器
    $queue = 'mycontainer';
    
    // 最大令牌数
    $max = 5;
    
    // 创建TrafficShaper对象
    $oTrafficShaper = new TrafficShaper($config, $queue, $max);
    
    // 重设令牌桶,填满令牌
    $oTrafficShaper->reset();
    
    // 循环获取令牌,令牌桶内只有5个令牌,因此最后3次获取失败
    for($i=0; $i<8; $i++){
        var_dump($oTrafficShaper->get());
    }
    
    // 加入10个令牌,最大令牌为5,因此只能加入5个
    $add_num = $oTrafficShaper->add(10);
    
    var_dump($add_num);
    
    // 循环获取令牌,令牌桶内只有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实现,每分钟调用add方法加入若干令牌。

    crontab最小的执行间隔为1分钟,如果令牌桶内的令牌在前几秒就已经被消耗完,那么剩下的几十秒时间内,都获取不到令牌,导致用户等待时间较长。

    我们可以优化加入令牌的算法,改为一分钟内每若干秒加入若干令牌,这样可以保证一分钟内每段时间都有机会能获取到令牌。

    crontab调用的加入令牌程序如下,每秒自动加入3个令牌。

    <?php
    /**
     * 定时任务加入令牌
     */
    require 'TrafficShaper.class.php';
    
    // redis连接设定
    $config = array(
        'host' => 'localhost',
        'port' => 6379,
        'index' => 0,
        'auth' => '',
        'timeout' => 1,
        'reserved' => NULL,
        'retry_interval' => 100,
    );
    
    // 令牌桶容器
    $queue = 'mycontainer';
    
    // 最大令牌数
    $max = 10;
    
    // 每次时间间隔加入的令牌数
    $token_num = 3;
    
    // 时间间隔,最好是能被60整除的数,保证覆盖每一分钟内所有的时间
    $time_step = 1;
    
    // 执行次数
    $exec_num = (int)(60/$time_step);
    
    // 创建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连接设定
    $config = array(
        'host' => 'localhost',
        'port' => 6379,
        'index' => 0,
        'auth' => '',
        'timeout' => 1,
        'reserved' => NULL,
        'retry_interval' => 100,
    );
    
    // 令牌桶容器
    $queue = 'mycontainer';
    
    // 最大令牌数
    $max = 10;
    
    // 每次时间间隔随机消耗的令牌数量范围
    $consume_token_range = array(2, 8);
    
    // 时间间隔
    $time_step = 1;
    
    // 创建TrafficShaper对象
    $oTrafficShaper = new TrafficShaper($config, $queue, $max);
    
    // 重设令牌桶,填满令牌
    $oTrafficShaper->reset();
    
    // 执行令牌消耗
    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);
    }
    
    ?>

    演示

    设置定时任务,每分钟执行一次

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

    执行模拟消耗

    php consume_demo.php

    执行结果:

    [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

    因令牌桶一开始是满的(最大令牌数10),所以之前的10次都能获取到令牌,10次之后则会根据消耗的令牌大于加入令牌数时,限制访问。

    推荐学习:《PHP视频教程

    以上就是PHP如何实现令牌桶限流的详细内容,更多请关注php中文网其它相关文章!

    声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。
    专题推荐:PHP 令牌桶限流
    上一篇:php怎么实现聊天工具 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • redis可以采用什么方式实现限流• 详解php基于redis的list型数据结构实现ip限流操作• 浅谈Redis限流的3种实现方式• PHP+Redis解决订单限流的实际问题
    1/1

    PHP中文网