业务场景为,有一个接口平常很稳定,一个月会有几天访问量特别高,会导致所有服务不可用,我想规避这种情况,具体应该怎么做

使用令牌桶,具体实现代码如下:

<?php
require 'vendor/autoload.php';

use Predis\Client;

// Redis连接
$redis = new Client([
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
]);

/**
* 令牌桶限流
* @param string $key 限流key(如接口名)
* @param int $rate 令牌生成速率(每秒几个)
* @param int $capacity 桶容量
* @return bool 是否允许通过
*/
function allowRequest($redis, $key, $rate = 5, $capacity = 20) {
$now = microtime(true);
$bucket = $redis->hmget($key, ['tokens', 'last_time']);
$tokens = $bucket[0] !== null ? floatval($bucket[0]) : $capacity;
$last_time = $bucket[1] !== null ? floatval($bucket[1]) : $now;

// 计算新令牌数
$delta = $now - $last_time;
$tokens = min($capacity, $tokens + $delta * $rate);

if ($tokens >= 1) {
// 允许请求,扣减令牌
$tokens -= 1;
$redis->hmset($key, ['tokens' => $tokens, 'last_time' => $now]);
$redis->expire($key, 3600); // 1小时自动过期
return true;
} else {
// 拒绝请求
$redis->hmset($key, ['tokens' => $tokens, 'last_time' => $now]);
$redis->expire($key, 3600);
return false;
}
}

/**
* 业务接口
*/
function myApi($redis) {
$limitKey = 'rate_limit:my_api';

if (!allowRequest($redis, $limitKey, 5, 20)) {
// 降级处理:返回友好提示或静态数据
return [
'code' => 429,
'msg' => '当前访问人数过多,请稍后再试!',
'data' => getStaticData()
];
}

// 正常业务逻辑
return [
'code' => 200,
'msg' => 'success',
'data' => getRealData()
];
}

/**
* 获取真实数据
*/
function getRealData() {
// 这里写你的真实业务逻辑
return ['foo' => 'bar'];
}

/**
* 获取静态数据(降级用)
*/
function getStaticData() {
// 返回缓存/静态/默认数据
return ['foo' => 'default'];
}

// ======= 调用接口示例 =======
header('Content-Type: application/json');
echo json_encode(myApi($redis));

 

 

  • allowRequest 实现了令牌桶限流,每秒最多5个请求,桶最大容量20(可根据实际调整)。
  • 超过限流时,接口直接返回静态数据和友好提示,避免服务雪崩。
  • 你可以把 myApi 里的 getRealData() 换成你的实际业务逻辑。
  • 适合高并发场景,Redis 保证分布式限流。

 

总结

  • 桶的容量 (capacity = 20):决定了桶最多能存多少令牌,这影响了你能应对多大的突发流量。如果桶里本来有20个令牌,你可以瞬间处理20个请求。
  • 令牌生成速率 (rate = 5):决定了桶每秒会增加多少令牌,这决定了你的长期平均处理速率。即使桶空了,每秒也能再产生5个令牌供新的请求使用。
  • 当前令牌数:决定了你现在马上还能处理多少个请求。

你可以把令牌桶想象成一个蓄水池:

  • capacity 是蓄水池的最大容量。
  • rate 是源源不断注入蓄水池的水流速度。
  • 请求 是从蓄水池里取水的人。
  • 只有蓄水池里有水(有令牌),取水的人(请求)才能成功。更精确的说法是:
    • 你的接口长期来看的平均处理速率是每秒最多5个请求(由 $rate 决定)。
    • 但是,如果流量是突发的,并且桶里预先积累了令牌(最多可以积累到 $capacity 即 20 个),你可以在短时间内处理超过5个请求,最多可以达到当前令牌数那么多,极限是20个请求(如果桶是满的)。
    • 所以,capacity 提供的是处理突发流量的能力(允许短时间超过 $rate),而 rate 决定了持续的处理速率上限。再用蓄水池的比喻:

    • 水龙头每秒进水5升 (rate)。
    • 蓄水池最大容量20升 (capacity)。
    • 每来一个人取水消耗1升水(请求消耗1个令牌)。
    • 只有蓄水池里有水,人才能取到水。
    • 如果蓄水池是满的(20升),可以瞬间让20个人取到水。之后就只能等新的水流进来(每秒5升)才能继续满足取水需求。

希望这次更加清晰了!

posted @ 2025-05-23 17:47  17601621550  阅读(9)  评论(0)    收藏  举报