Featured image of post 漏桶算法与令牌桶算法

漏桶算法与令牌桶算法

本文阅读量

漏桶算法与令牌桶算法

https://www.cnblogs.com/xuwc/p/9123078.html

https://www.cnblogs.com/xuwc/p/9123078.html

http://www.seedblog.cn/article_details/488

漏桶算法

概念

漏桶算法,又称leaky bucket。为了理解漏桶算法,我们看一下对于该算法的示意图:

从图中我们可以看到,整个算法其实十分简单。首先,我们有一个固定容量的桶(请求数控制),有水流进来(应用中的请求),也有水流出去(处理请求)。对于流进来的水来说,我们无法预计一共有多少水会流进来,也无法预计水流的速度。但是对于流出去的水来说,这个桶可以固定水流出的速率。而且,当桶满了之后,多余的水将会溢出(多余请求不处理)。

这样在并发多的情况下,会丢弃很多请求。

实现

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
/**
 * [leaky php实现漏桶算法]
 * @Author   [NiuShao   <camel_niu@163.com> <qq:370574131>]
 * @DateTime 2020-06-26
 * @param    [type]     $contain            [int 桶的总容量]
 * @param    [type]     $addNum             [int 每次注入桶中的水量]
 * @param    [type]     $leakRate           [int 桶中漏水的速率,秒为单位。例如2/s,3/s]
 * @param    integer    &$water             [int 当前水量]
 * @param    integer    &$preTime           [int 时间戳,记录的上次漏水时间]
 * @return   [type]                         [bool,返回可否继续注入true/false]
 */
function leaky($contain,$addNum,$leakRate,&$water=0,&$preTime=0)
{
    //参数赋值
    //首次进入默认当前水量为0
    $water = empty($water) ? 0 : $water;
    //首次进入默认上次漏水时间为当前时间
    $preTime = empty($water) ? time() : $preTime;
    $curTime = time();
    //上次结束到本次开始,流出去的水
    $leakWater = ($curTime-$preTime)*$leakRate;
    //上次结束时候的水量减去流出去的水,也就是本次初始水量
    $water = $water-$leakWater;
    //水量不可能为负,漏出大于进入则水量为0
    $water = ( $water>=0 ) ? $water : 0 ;
    //更新本次漏完水的时间
    $preTime = $curTime;
    //水小于总容量则可注入,否则不可注入
    if( ($water+$addNum) <= $contain ){
        $water += $addNum;
        return true;
    }else{
        return false;
    }

}

/**
 * 测试
 * @var integer
 */
for($i=0;$i<500;$i++){
    $res = leaky(50,1,5,$water,$timeStamp);
    var_dump($res);
    usleep(50000);
}






#############实现2#######################

//速度 桶大小 / 时间段
$rate = $maxRequests / $period;

$t_key = $keyTime($id); //最后一次获取令牌时间
$a_key = $keyAllow($id); //已有令牌数

//判断是否有最后一次获取令牌记录
if ($cache->exists($t_key)) {
    $c_time = time();
    //计算上一次获取令牌到现在过去的时间
    $time_passed = $c_time - $cache->get($t_key);
    $cache->set($t_key, $c_time, $ttl);

    //获取桶中令牌数
    $allow = $cache->get($a_key);
    $allow += $time_passed * $rate; //加上最后一次消费令牌到现在期间增长的令牌数

    //令牌数不能超过最大数
    if ($allow > $maxRequests) {
        $allow = $maxRequests;
    }

    //使用的令牌数不能超过最大限制
    if ($allow < $use) {
        $cache->set($a_key, $allow, $ttl);
        return 0;
    } else {
        //消费令牌
        $cache->set($a_key, $allow - $use, $ttl);
        return (int) ceil($allow);
    }
} else {
    //记录当前时间为最后一次处理时间,用于下次使用
    $cache->set($t_key, time(), $ttl);
    //没有令牌时按照最大令牌数处理
    $cache->set($a_key, $maxRequests - $use, $ttl);
    return $maxRequests;
}

令牌桶算法

概念

为了更形象了解该算法,我们看一下该算法的示意图:

\

令牌桶算法的原理是系统以恒定的速率产生令牌,然后把令牌放到令牌桶中,令牌桶有一个容量,当令牌桶满了的时候,再向其中放令牌,那么多余的令牌会被丢弃;当想要处理一个请求的时候,需要从令牌桶中取出一个令牌,如果此时令牌桶中没有令牌,那么则拒绝该请求。

实现

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
100
101
102
103
104
105
106
107
108
109
110
111
112
class TrafficShaperController extends Controller
{
 
    /**
     * 令牌桶总数量
     * @var int
     */
    private $totleNum = 25;
 
    /**
     * 令牌标识(可以根据需要加上关键ID,uid、orderid...)
     * @var string
     */
    private $quekueName ="TrafficShaper_queue";
 
    /**
     * redis缓存类
     * @var object
     */
    private $redis;
 
    /**
     * 初始化方法
     *
     * @author heyw<1051834593@qq.com>
     * @since  2020/12/10
     */
    public function _initialize()
    {
        $this->redis = Redis::getInstance();
    }
 
    /**
     * 模拟用户消耗令牌
     *
     * @param int $num
     * @author heyw<1051834593@qq.com>
     * @since  2020/12/10
     */
    public function run($num = 1)
    {
        // 初始化
        $this->reset();
 
        // 模拟1s请求10次
        while (1) {
            $this->getKey();
            sleep(0.1);
        }
    }
 
    /**
     *  获取令牌
     *
     * @return bool
     * @author heyw<1051834593@qq.com>
     * @since  2020/12/11
     */
    protected function getKey()
    {
        // 初始化
        $redis = $this->redis;
        $queueName = $this->quekueName;
 
        // 获取一个令牌,如果没有直接返回
        $res = $redis->rPop($queueName);
 
        // 获得令牌,处理业务
        var_dump($res ?'get it' : 'empty');
 
        return true;
    }
 
    /**
     * 重置
     *
     * @author heyw<1051834593@qq.com>
     * @since  2020/12/11
     */
    protected function reset()
    {
        $this->redis->delete($this->quekueName);
        $this->add(25);
    }
 
    /**
     * 定时加入令牌桶,1s执行1次
     *
     * @author heyw<1051834593@qq.com>
     * @since  2020/12/10
     */
    public function add($stepNum = 5)
    {
        // 初始化
        $redis      = $this->redis;
        $queueName  = $this->quekueName;
 
        // 当前令牌书
        $currNum    = $redis->lSize($queueName) ?: 0;
        $maxNum     = $this->totleNum;
        $addNum     = $maxNum >= $currNum + $stepNum ? $stepNum : $maxNum - $currNum;
        if ($addNum == 0) {
            return true;
        }
 
        // 加入令牌
        $token = array_fill(0, $addNum, 1);
        $redis->lPush($queueName, $token);
 
        return true;
    }
}

go实现

 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
package main

import (
    "fmt"
    "time"
)

var (
    lastRequestTime int64 = time.Now().Unix()
    tokenSurplus    int64 = 0
    qps             int64 = 5
)

func getMin(a, b int64) int64 {
    if a > b {
        return b
    }
    return a
}

func getToken() bool {
    now := time.Now().Unix()
    temp := (now-lastRequestTime)*qps + tokenSurplus
    tokenNow := getMin(temp, qps)
    if tokenNow > 0 {
        lastRequestTime = now
        tokenSurplus--
        return true
    }
    return false
}

func main() {
    fmt.Println(time.Now().Unix())
}

https://blog.csdn.net/HapplyFox/article/details/115458867

对比

令牌桶和漏桶对比:

  • 令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求;
  • 漏桶则是按照常量固定速率流出请求,流入请求速率任意,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝;
  • 令牌桶限制的是平均流入速率(允许突发请求,只要有令牌就可以处理,支持一次拿3个令牌,4个令牌),并允许一定程度突发流量;
  • 漏桶限制的是常量流出速率(即流出速率是一个固定常量值,比如都是1的速率流出,而不能一次是1,下次又是2),从而平滑突发流入速率;
  • 令牌桶允许一定程度的突发,而漏桶主要目的是平滑流入速率;
  • 两个算法实现可以一样,但是方向是相反的,对于相同的参数得到的限流效果是一样的。

概述

在大量并发的环境下,为了防止由于请求暴涨,导致系统崩溃从而引起雪崩,一般会对流量做一定的限制操作。比如等待、排队、降级、拒绝服务、限流等

说明

主要目的是控制数据注入到网络的速率,平滑网络上的突发流量。漏桶算法提供了一种机制,通过它,突发流量可以被整形以便为网络提供一个稳定的流量

使用 Hugo 构建
主题 StackJimmy 设计