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

php实现 令牌桶,php 基于redis使用令牌桶算法实现流量控制本文介绍 php 基于 redis 使用令牌桶算法 实现访问流量的控制 提供完整算法说明及演示实例 方便大家学习使用 每当国内长假期或重要节日时 国内的景区或地铁都会人山人海 导致负载过大 部分则会采用限流措施 限制进入的人数 当区内人数降低到一定值 再允许进入 例如 区内最大允许人数为 M 区内当前人数为 N 每进入一个人 N 1 当 N M 时 则不允许进入每离开一个人 N 1 当 N

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

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

例如:

区内最大允许人数为 M

区内当前人数为 N

每进入一个人,N+1,当N = M时,则不允许进入

每离开一个人,N-1,当N < M时,可允许进入

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

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

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

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

令牌桶算法

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

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

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

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

TrafficShaper.class.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:

/ * 演示令牌加入与消耗 */

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的使用可以参考:《Linux crontab定时执行任务 命令格式与详细例子》

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

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

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

/ * 定时任务加入令牌 */

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

$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个令牌。

/ * 模拟用户访问消耗令牌,每段时间间隔消耗若干令牌 */

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

$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次之后则会根据消耗的令牌大于加入令牌数时,限制访问。

源码下载地址:点击下载

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/178639.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月26日 下午4:49
下一篇 2026年3月26日 下午4:49


相关推荐

  • 40个Unity游戏开发小阴招

    40个Unity游戏开发小阴招Unity游戏开发小技巧集结,不用谢我,我只是大自然的搬运工。借助Prefab暂存数据。可以直接在脚本的Inspector上填写,然后把整个GameObject拖成Prefab暂存。等要用的时候再拖出来。 DontDestroy模式,在跨场景的时候保持某些对象一直存在,不必要每次都重新加载,譬如显示广告的模块。 内置的Awake,Start,Update,FixedUpdate,LateUpdate函数,令开发者直接往游戏循环里加内容,非常方便快捷。 Input.MouseD.

    2025年8月6日
    2
  • 机器学习-算法-半监督学习:半监督学习(Semi-supervised Learning)算法

    机器学习-算法-半监督学习:半监督学习(Semi-supervised Learning)算法一 半监督学习算法提出的背景 1 监督学习监督学习 训练样本集不仅包含样本 还包含这些样本对应的标签 即样本和样本标签成对出现 监督学习的目标是从训练样本中学习一个从样本到标签的有效映射 使其能够预测未知样本的标签 监督学习是机器学习中最成熟的学习方法 代表性的算法包括神经网络 支持向量机 SVM 等 2 无监督学习无监督学习 只能利用训练样本的数据分布或样本间的关系将样本划分到不同的聚类簇或给出样本对应的低维结构 因此 无监督学习常被用于对样本进行聚类或降维 典型的算法包括尺均值聚类和主成

    2026年3月2日
    1
  • java messagedigest,在C#中的Java MessageDigest类[通俗易懂]

    java messagedigest,在C#中的Java MessageDigest类[通俗易懂]IrequireacertainpieceofencrytionlogicdoneinJavatobeconvertedinC#thejavacodesnippetisasfollows.WhatwouldbetheC#equivalentfortheupdate(),Digestandresetfunctions?解决方案In…

    2022年7月16日
    24
  • 验证码识别(Tess4J初体验)

    验证码识别(Tess4J初体验)遇到一道机试题当时就懵逼了0.0查了好多资料,大体知道了基本的步骤:1.预处理2.灰度化3.二值化4.去噪5.分割6.识别还好题目要求不严格,可以使用开源程序。机智的我还真找到一个:Tesseract下面开始正文:Tess4J官方描述:AJavaJNAwrapperforTesseractOCRAPI.1.先去官网下载:http://

    2022年4月29日
    88
  • c中怎么将string转换成int

    c中怎么将string转换成int通过 int Parse string 类型变量名 实现 但是使用 int Parse 方法实现转换 通常需要额外捕获并处理转换时发生的异常 2 通过 int TryParse strings outintresult 实现 使用 int TryParse 方法实现转换 不需要处理额外处理转换时发生的异常 3 通过 Convert ToInt32 string 类型变量名 实现 补充一下 Convert ToInt32 int Parse 的区别 1 这两个方法的最大不同是

    2026年3月19日
    3
  • 剑指Offer算法题

    剑指Offer算法题

    2022年3月13日
    38

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号