場景:會員免費下載限速 100KB/s,VIP 不限速;防止單用戶佔滿帶寬;實現公平隊列。
<?php
function downloadThrottled(string $file, float $kbps = 100): void {
if (!is_file($file)) { http_response_code(404); exit; }
$fp = fopen($file, 'rb');
$chunk = 1024 * round($kbps); // 每秒塊大小
header('Content-Type: application/octet-stream');
header('Content-Length: ' . filesize($file));
header('Content-Disposition: attachment; filename="' . basename($file) . '"');
while (!feof($fp)) {
echo fread($fp, $chunk);
@ob_flush(); @flush();
sleep(1); // 硬性睡眠 1 秒
}
fclose($fp);
}
缺陷:精度差(網絡延遲不計入)、阻塞進程、無法應對併發、CPU 空轉。
<?php
class TokenBucket {
public function __construct(
private float $rate, // tokens per second (bytes/s)
private float $burst // max bucket size (bytes)
) {
$this->tokens = $burst;
$this->last = microtime(true);
}
public function consume(int $bytes): float {
$now = microtime(true);
$since = $now - $this->last;
$this->tokens = min($this->burst, $this->tokens + $since * $this->rate);
$this->last = $now;
if ($this->tokens >= $bytes) {
$this->tokens -= $bytes;
return 0; // 即時發送
}
$wait = ($bytes - $this->tokens) / $this->rate;
$this->tokens = 0;
return $wait; // 需等待秒數
}
}
function downloadSmooth(string $file, float $kbps = 100): void {
if (!is_file($file)) { http_response_code(404); exit; }
$bucket = new TokenBucket($kbps * 1024, $kbps * 1024 * 2); // 允許 2 秒突發
$fp = fopen($file, 'rb');
$chunkSize = 16 * 1024; // 16KB 塊
header('Content-Type: application/octet-stream');
header('Content-Length: ' . filesize($file));
header('Content-Disposition: attachment; filename="' . basename($file) . '"');
while (!feof($fp)) {
$data = fread($fp, $chunkSize);
$wait = $bucket->consume(strlen($data));
if ($wait > 0) usleep($wait * 1_000_000);
echo $data;
@ob_flush(); @flush();
}
fclose($fp);
}
# nginx.conf
location /download/ {
# 根據用戶等級設置變量 $limit_rate
# 免費用戶 100k,VIP 0(不限速)
set $limit_rate $limit_rate_var;
limit_rate_after 1m; # 前 1MB 不限速,體驗更好
internal; # 僅內部重定向訪問
alias /var/www/files/;
}
# PHP 僅驗證權限並簽發簽名 URL
header('X-Accel-Redirect: /download/secret.zip');
header('X-Accel-Limit-Rate: 102400'); # 100KB/s
exit;
| 方案 | 精度 | 併發性能 | 實現複雜度 | 推薦場景 |
|---|---|---|---|---|
| PHP sleep | 低 | 差(阻塞進程) | 極簡 | 極低併發、臨時腳本 |
| PHP Token Bucket | 高(ms級) | 中(佔用 PHP 進程) | 中 | 中等併發、需邏輯控制(會員等級) |
| Nginx limit_rate | 高 | 極佳(內核態) | 低(配置) | 高併發、生產環境首選 |