流量并发控制常用的方法
浏览量:320
在最近几天的面试中,老被面试问到,对于大流量高并发的情况下,怎么做到并发流量控制
1.计数器
<?php
/**
* 计数器来限流
* @param $uid 用户的uid
* @param $actionKey 任务名的key
* @param int $limitTime 多长时间
* @param int $maxCount 访问的最大次数
*/
function countingFlow($uid,$actionKey,$limitTime=60,$maxCount=100){
if(empty($uid) || empty($actionKey)){
return false;
}
$redis = new Redis();
$redis->connect("127.0.0.1",6379);
$redis->auth("foobared");
$redisKey = sprintf("hist:%s:%s",$uid,$actionKey);
$count = $redis->incr($redisKey);
if($count == 1){
$redis->expire($redisKey,$limitTime);
}
if($count > $maxCount){
return false;
}
return true;
}注意:
临界问题: 在第一次0-58秒之内有1个请求,在59秒内突然来了15个请求,这个时候已经到了1分钟计数器会重置。 第二次的1秒内(1分0秒)又有了15个请求,这个时候是不是就在2秒内有30个请求被放行了呢?(59秒,1分0秒),如果某个服务器的访问只能是20次请求,那么这种限流方式已经导致服务器挂掉了。
2.滑动窗口计数器
<?php
/**
* 滑动窗口计数器来限流
* @param $uid 用户的uid
* @param $actionKey 任务名的key
* @param int $period 时间 毫秒
* @param int $maxCount 最大次数
*/
function isActionAllowed($uid,$actionKey,$period,$maxCount=100){
if(empty($uid) || empty($actionKey)){
return false;
}
$redis = new Redis();
$redis->connect("127.0.0.1",6379);
$redis->auth('foobared'); //密码验证
$now = msectime();//毫秒时间戳
$maxCount = 1;
//拼接key
$redisKey = sprintf('miaosha:%s:%s',$uid,$actionKey);
//事务开始客户端请求 客户端缓存
$pipe = $redis->multi(Redis::PIPELINE);
//value 和 score 都用毫秒时间戳
$pipe->zAdd($redisKey,$now,$now);
//移除时间窗口之前的行为记录,剩下的都是时间窗口
$pipe->zRemRangeByScore($redisKey,0,$now - $period);
$pipe->zCard($redisKey);
//获取窗口内的行为数量
//多加1秒的过期时间
$pipe->expire($redisKey,$period+1);
$res = $pipe->exec();
return $res[2] < $maxCount;
}3.令牌桶限流
所有的请求之前都需要那个令牌
根据限流的大小按照一定的速率往桶中放令牌
桶需要限制一个最大的令牌数,当桶满的的时候,新增的令牌就得被丢弃或者拒绝
请求先拿着令牌才能进行自己的业务逻辑然后处理完删除
令牌有最低限额,当桶中的限额达到最低令牌时,请求处理完也不会删除令牌,以达到最终限流
<?php
<?php
class Token
{
private $_max;//最大的令牌
private $_queue;
private $_redis;
public function __construct()
{
try {
$this->_redis = new Redis();
$this->_redis->connect("127.0.0.1", 6379);
$this->_redis->auth("foobared");
$this->_queue = "token";
$this->_max = 10;
} catch (RedisException $exception) {
throw new Exception($exception->__toString());
return false;
}
}
/**
* 令牌初始化
*/
public function resertKey()
{
$this->_redis->del($this->_queue);
$this->_add($this->_max);
}
/**
* 添加令牌
* @param int $number
*/
public function add($number = 1)
{
$maxNum = $this->_max;
$currentNumber = $this->_redis->lLen($this->_queue);
if ($maxNum > $currentNumber + $number) {
$number = $number;
} else {
$number = $maxNum - $currentNumber;
}
if ($number > 0) {
$tokens = array_fill(0, $number, 1);
foreach ($tokens as $token) {
$this->_redis->lPush($this->_queue, $token);
}
}
}
/**
* 获取令牌
* @return bool
*/
public function get()
{
return $this->_redis->rPop($this->_queue) ? true : false;
}
}消费类:模拟用户请求
<?php require 'token.php'; $token = new Token(); var_dump($token->get());
投递类:后端向令牌桶投递
<?php require 'token.php'; $token = new Token(); $token->add(1);

神回复
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。