枯痕个人博客
立即登录 马上注册
当前位置: 首页 > PHP基于Redis使用令牌桶算法实现接口限流

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


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

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


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

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

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

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

令牌桶算法

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

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

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

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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
&lt;?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&#91;'host'],$this->_config&#91;'port'],$this->_config&#91;'timeout'],$this->_config&#91;'reserved'],$this->_config&#91;'retry_interval']);
            if(empty($this->_config&#91;'auth'])){
                $redis->auth($this->_config&#91;'auth']);
            }
            $redis->select($this->_config&#91;'index']);
        }catch(RedisException $e){
            throw new Exception($e->getMessage());
            return false;
        }
        return $redis;
    }


} // class end
?>

demo:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
&lt;?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&lt;8; $i++){
    var_dump($oTrafficShaper->get());
}

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

var_dump($add_num);

// 循环获取令牌,令牌桶内只有5个令牌,因此最后1次获取失败
for($i=0; $i&lt;6; $i++){
    var_dump($oTrafficShaper->get());
}

?>

输出:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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的使用可以参考:《Linux crontab定时执行任务 命令格式与详细例子》

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

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

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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
&lt;?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&lt;$exec_num; $i++){
    $add_num = $oTrafficShaper->add($token_num);
    echo '&#91;'.date('Y-m-d H:i:s').'] add token num:'.$add_num.PHP_EOL;
    sleep($time_step);
}

?>

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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
&lt;?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&#91;0], $consume_token_range&#91;1]);
    for($i=0; $i&lt;$consume_num; $i++){
        $status = $oTrafficShaper->get();
        echo '&#91;'.date('Y-m-d H:i:s').'] consume token:'.($status? 'true' : 'false').PHP_EOL;
    }
    sleep($time_step);
}

?>

演示

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


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

执行模拟消耗


1
php consume_demo.php

执行结果:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
&#91;2018-02-23 11:42:57] consume token:true
&#91;2018-02-23 11:42:57] consume token:true
&#91;2018-02-23 11:42:57] consume token:true
&#91;2018-02-23 11:42:57] consume token:true
&#91;2018-02-23 11:42:57] consume token:true
&#91;2018-02-23 11:42:57] consume token:true
&#91;2018-02-23 11:42:57] consume token:true
&#91;2018-02-23 11:42:58] consume token:true
&#91;2018-02-23 11:42:58] consume token:true
&#91;2018-02-23 11:42:58] consume token:true
&#91;2018-02-23 11:42:58] consume token:true
&#91;2018-02-23 11:42:58] consume token:true
&#91;2018-02-23 11:42:58] consume token:true
&#91;2018-02-23 11:42:58] consume token:false
&#91;2018-02-23 11:42:59] consume token:true
&#91;2018-02-23 11:42:59] consume token:true
&#91;2018-02-23 11:42:59] consume token:true
&#91;2018-02-23 11:42:59] consume token:false
&#91;2018-02-23 11:42:59] consume token:false
&#91;2018-02-23 11:42:59] consume token:false
&#91;2018-02-23 11:42:59] consume token:false
&#91;2018-02-23 11:43:00] consume token:true
&#91;2018-02-23 11:43:00] consume token:true
&#91;2018-02-23 11:43:00] consume token:true
&#91;2018-02-23 11:43:00] consume token:false
&#91;2018-02-23 11:43:00] consume token:false

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

「梦想一旦被付诸行动,就会变得神圣,如果觉得我的文章对您有用,请帮助本站成长」

赞(1) 打赏
欢迎打赏您的小可爱~~~~

支付宝扫一扫打赏

微信扫一扫打赏

标签:

上一篇:

下一篇:

相关推荐

0 条评论关于"PHP基于Redis使用令牌桶算法实现接口限流"

最新评论

    暂无留言哦~~

博客简介

枯痕个人博客: xiaole.biz,一个关注Web后端开发技术、关注用户体验、坚持更多原创实战教程的个人网站,愿景:成为宇宙中最具有代表性的后端博客,期待您的参与。

精彩评论

友情链接

他们同样是一群网虫,却不是每天泡在网上游走在淘宝和网游之间、刷着本来就快要透支的信用卡。他们或许没有踏出国门一步,但同学却不局限在一国一校,而是遍及全球!申请交换友链

站点统计

  • 文章总数: 106 篇
  • 草稿数目: 1 篇
  • 分类数目: 26 个
  • 独立页面: 4 个
  • 评论总数: 11 条
  • 链接总数: 5 个
  • 标签总数: 177 个
  • 注册用户: 10 人
  • 访问总量: 8,769,767 次
  • 最近更新: 2021年11月23日
服务热线:
 156144**650

 QQ在线交流

 旺旺在线