流量并发控制常用的方法
浏览量:311
在最近几天的面试中,老被面试问到,对于大流量高并发的情况下,怎么做到并发流量控制
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);
神回复
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。