本文目录导读:

在PHP项目中实现指数退避算法(Exponential Backoff)的重试间隔,通常用于处理网络请求、数据库连接失败等临时性错误,以下是几种常见实现方式:
基础指数退避实现
<?php
class ExponentialBackoff {
private $maxRetries;
private $baseDelay;
private $maxDelay;
public function __construct(
int $maxRetries = 3,
int $baseDelay = 100, // 毫秒
int $maxDelay = 30000 // 30秒
) {
$this->maxRetries = $maxRetries;
$this->baseDelay = $baseDelay;
$this->maxDelay = $maxDelay;
}
public function getDelay(int $attempt): int {
// 计算延迟:baseDelay * 2^attempt
$delay = $this->baseDelay * pow(2, $attempt);
// 添加随机抖动(jitter),避免惊群效应
$delay = mt_rand($delay, (int)($delay * 1.5));
// 限制最大延迟
return min($delay, $this->maxDelay);
}
public function execute(callable $operation) {
$lastException = null;
for ($attempt = 0; $attempt < $this->maxRetries; $attempt++) {
try {
return $operation();
} catch (\Throwable $e) {
$lastException = $e;
if ($attempt < $this->maxRetries - 1) {
$delayMs = $this->getDelay($attempt);
usleep($delayMs * 1000); // 转换为微秒
}
}
}
throw $lastException ?? new \RuntimeException('Operation failed');
}
}
// 使用示例
$backoff = new ExponentialBackoff(5, 200, 10000);
$result = $backoff->execute(function() {
// 模拟可能失败的操作
$response = file_get_contents('https://api.example.com/data');
return json_decode($response, true);
});
带完整功能的改良版
<?php
class SmartExponentialBackoff {
private int $maxRetries;
private int $baseDelay;
private int $maxDelay;
private bool $jitter;
private array $retryableExceptions = [];
private $onRetryCallback;
public function __construct(
int $maxRetries = 3,
int $baseDelayMs = 100,
int $maxDelayMs = 30000,
bool $jitter = true
) {
$this->maxRetries = max(1, $maxRetries);
$this->baseDelay = max(1, $baseDelayMs);
$this->maxDelay = max($this->baseDelay, $maxDelayMs);
$this->jitter = $jitter;
}
/**
* 设置可重试的异常类型
*/
public function setRetryableExceptions(array $exceptions): self {
$this->retryableExceptions = $exceptions;
return $this;
}
/**
* 设置重试回调
*/
public function onRetry(callable $callback): self {
$this->onRetryCallback = $callback;
return $this;
}
/**
* 计算延迟时间(毫秒)
*/
public function getDelayMs(int $attempt): int {
// 指数计算:baseDelay * 2^attempt
$delay = min(
$this->baseDelay * pow(2, $attempt),
$this->maxDelay
);
// 添加随机抖动
if ($this->jitter && $delay > 0) {
$delay = mt_rand(
(int)($delay * 0.5),
(int)($delay * 1.5)
);
}
return max(0, $delay);
}
/**
* 执行操作并自动重试
*/
public function execute(callable $operation) {
$lastException = null;
$attempt = 0;
while ($attempt < $this->maxRetries) {
try {
return $operation();
} catch (\Throwable $e) {
$lastException = $e;
// 检查是否应该重试
if (!$this->shouldRetry($e, $attempt)) {
throw $e;
}
$attempt++;
if ($attempt < $this->maxRetries) {
$delayMs = $this->getDelayMs($attempt - 1);
// 触发重试回调
if ($this->onRetryCallback) {
call_user_func($this->onRetryCallback, $attempt, $delayMs, $e);
}
// 等待延迟时间
usleep($delayMs * 1000);
}
}
}
throw $lastException;
}
/**
* 判断是否应该重试
*/
private function shouldRetry(\Throwable $e, int $attempt): bool {
if ($attempt >= $this->maxRetries) {
return false;
}
if (empty($this->retryableExceptions)) {
return true; // 默认所有异常都重试
}
foreach ($this->retryableExceptions as $exceptionClass) {
if ($e instanceof $exceptionClass) {
return true;
}
}
return false;
}
}
// 使用示例
$backoff = new SmartExponentialBackoff(
maxRetries: 5,
baseDelayMs: 200,
maxDelayMs: 15000,
jitter: true
)
->setRetryableExceptions([
\RuntimeException::class,
\GuzzleHttp\Exception\ServerException::class
])
->onRetry(function(int $attempt, int $delayMs, \Throwable $e) {
echo "重试 #{$attempt},等待 {$delayMs}ms,异常: " . $e->getMessage() . "\n";
});
try {
$result = $backoff->execute(function() {
// API调用
$client = new \GuzzleHttp\Client();
$response = $client->get('https://api.example.com/data', [
'timeout' => 5,
'connect_timeout' => 3
]);
return json_decode($response->getBody(), true);
});
} catch (\Throwable $e) {
echo "最终失败: " . $e->getMessage() . "\n";
}
轻量级函数实现
<?php
function retryWithExponentialBackoff(callable $operation, int $maxRetries = 3, int $baseDelayMs = 100) {
$attempt = 0;
while ($attempt < $maxRetries) {
try {
return $operation();
} catch (\Throwable $e) {
$attempt++;
if ($attempt >= $maxRetries) {
throw $e;
}
// 计算延迟:baseDelay * 2^(attempt-1) + random(0, 1000)
$delayMs = $baseDelayMs * pow(2, $attempt - 1) + mt_rand(0, 1000);
$delayMs = min($delayMs, 30000); // 上限30秒
usleep($delayMs * 1000);
}
}
}
// 使用
try {
$data = retryWithExponentialBackoff(function() {
return file_get_contents('https://api.example.com/data');
}, 5, 500);
} catch (\Throwable $e) {
echo "请求失败: " . $e->getMessage();
}
使用现有库(推荐)
// composer require guzzlehttp/retry-subscriber
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\Client;
$handlerStack = HandlerStack::create();
// 添加重试中间件
$retryMiddleware = Middleware::retry(
function ($retries, $request, $response, $exception) {
// 最多重试3次
if ($retries >= 3) {
return false;
}
// 只在服务器错误时重试
if ($response && $response->getStatusCode() >= 500) {
return true;
}
return $exception instanceof \GuzzleHttp\Exception\ConnectException;
},
function ($retries) {
// 指数退避:100ms, 200ms, 400ms
return 100 * pow(2, $retries);
}
);
$handlerStack->push($retryMiddleware);
$client = new Client(['handler' => $handlerStack]);
最佳实践建议
- 总是添加随机抖动:避免多个客户端同时重试导致服务器过载
- 设置最大延迟上限:通常30秒-60秒
- 区分可重试错误:不要对所有错误都重试(如400 Bad Request不需要重试)
- 记录重试日志:便于调试和监控
- 考虑使用库:生产环境推荐使用成熟的库(如Guzzle的重试中间件)
选择哪种实现方式取决于你的具体需求:
- 简单场景使用函数实现
- 复杂场景使用面向对象版本
- HTTP请求使用Guzzle中间件
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播