乐趣区

关于php:redis实际应用限流

为什么要做限流

首先让咱们先看一看零碎架构设计中,为什么要做“限流”。

旅游景点通常都会有最大的接待量,不可能无限度的放游客进入,比方故宫每天只卖八万张票,超过八万的游客,无奈买票进入,因为如果超过八万人,景点的工作人员可能就忙不过来,过于拥挤的景点也会影响游客的体验和情绪,并且还会有安全隐患;只卖 N 张票,这就是一种限流的伎俩

软件架构中的服务限流也是相似,也是当系统资源不够的时候,曾经不足以应答大量的申请,为了保障服务还可能失常运行,那么依照规定,零碎会把多余的申请间接回绝掉,以达到限流的成果

不晓得大家留神过没有,比方双 11,刚过 12 点有些顾客的网页或 APP 会显示下单失败的提醒,有些就是被限流掉了。

常见的限流算法

计数法

顾名思义就是来一个,记录一个,比方我 1 分钟只能解决 1000 个申请,那么咱们就能够设置一个计数器,来一个申请就 incr+1,当 1 分钟之内的数量大于等于 1000 之后不解决了即可,伪代码如下

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$rate_limit = 1000;  // 限度个数
$rate_seconds = 60;  // 限度工夫
$redis_key = "redis_limit";
$count = $redis->get($redis_key);
if ($count >= $rate_limit){  // 判断 60 秒内申请个数是否曾经达到下限
    // 间接返回,不解决申请
    return
}
$redis->incr($redis_key, 1);// 申请计数
$redis->expire($redis, $rate_seconds); // 设置过期工夫 60s
//to do  业务逻辑解决.......

这种计数形式比较简单快捷,然而有很大的毛病,因为申请的拜访不肯定是很安稳的,如果 0:59 过去了 1000 个申请,1:01 曾经是下一个窗口,又过去了 1000 个申请,但实际上三秒内来了 2000 个申请,曾经超过咱们的限流下限了。所以这种办法是不举荐的。

滑动窗口算法

还拿下面的例子,一分钟分 6 份,每份 10 秒;每过 10 秒钟,咱们的工夫窗口就会往右滑动一格,每个格子都有独立的计数器,咱们每次都计算工夫窗口内的数量,能够解决计数器法中的问题,而且当滑动窗口的格子越多,那么限流的统计就会越准确。具体能够参考下图,看图比拟清晰

伪代码实现如下

function api_limit($scene,  $period, $maxCount){$redis = new Redis();
    $redis->connect('127.0.0.1', 6379);
    $key = sprintf('hist:%s', $scene); // 限流场景惟一标识
    $now = msectime();   // 毫秒工夫戳, 这样更准确
    $pipe=$redis->multi(Redis::PIPELINE); // 应用管道晋升性能
    $pipe->zadd($key, $now, $now); //value 和 score 都应用毫秒工夫戳
    $pipe->zremrangebyscore($key, 0, $now - $period); // 移除工夫窗口之前的行为记录,剩下的都是工夫窗口内的
    $pipe->zcard($key);  // 获取窗口内的行为数量
    $pipe->expire($key, $period/1000 + 1);  // 多加一秒过期工夫
    $replies = $pipe->exec();
    return $replies[2] <= $maxCount;  //$replies[2]为 zcard 返回的个数  如果 zcard 后果大于 maxCount,则不处理结果
}

for ($i=0; $i<20; $i++){  // 测试限流是否实现代码
    var_dump(isActionAllowed("uniq_scene", 60*1000, 5)); // 执行能够发现只有前 5 次是通过的
}

// 返回以后的毫秒工夫戳
function msectime() {list($msec, $sec) = explode(' ', microtime());
    $msectime = (float)sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000);
    return $msectime;
 }

这段代码还是略显简单,须要读者花肯定的工夫好好啃。它的整体思路就是:每一个行为到来时,都保护一次工夫窗口。将工夫窗口外的记录全副清理掉,只保留窗口内的记录。

因为这几个间断的 Redis 操作都是针对同一个 key 的,应用 pipeline 能够显著晋升 Redis 存取效率。但这种计划也有毛病,因为它要记录时间窗口内所有的行为记录,如果这个量很大,比方限定 60s 内操作不得超过 100w 次这样的参数,它是不适宜做这样的限流的,因为会耗费大量的存储空间

前面还有漏桶算法和令牌桶算法,因为各自的实现比较复杂,所以筹备各自新开一篇文章独自形容

退出移动版