PHP项目调用外部接口如何超时处理

wen PHP项目 25

本文目录导读:

PHP项目调用外部接口如何超时处理

  1. cURL 方式(最常用)
  2. file_get_contents 方式
  3. Guzzle HTTP 客户端(推荐)
  4. 异步超时处理(Swoole/ReactPHP)
  5. 完整封装示例
  6. 最佳实践建议

在PHP项目中调用外部接口时,超时处理至关重要,能防止接口卡死导致整个请求阻塞,以下是几种常见方式的超时处理方案:

cURL 方式(最常用)

基础超时设置

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.example.com/endpoint");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// 设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, 10);        // 总超时时间(秒)
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);  // 连接超时时间(秒)
$response = curl_exec($ch);
if (curl_errno($ch)) {
    $error = curl_error($ch);
    $errno = curl_errno($ch);
    // 超时错误处理
    if ($errno == CURLE_OPERATION_TIMEDOUT) {
        echo "请求超时,请稍后重试";
        // 记录日志
        error_log("API 超时: " . $error);
    }
}
curl_close($ch);

更精细的超时控制

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.example.com/endpoint");
// 毫秒级超时设置
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 10000);        // 10秒总超时(毫秒)
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 5000);  // 5秒连接超时(毫秒)
// 设置重试次数
$maxRetries = 3;
$attempt = 0;
$response = false;
while ($attempt < $maxRetries && !$response) {
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    if (curl_errno($ch) != 0) {
        $attempt++;
        if ($attempt < $maxRetries) {
            usleep(500000); // 等待0.5秒后重试
        }
    } else {
        break;
    }
}

file_get_contents 方式

$options = [
    'http' => [
        'method' => 'GET',
        'timeout' => 10, // 超时时间(秒)
        'ignore_errors' => true
    ],
    'ssl' => [
        'verify_peer' => false,
        'verify_peer_name' => false
    ]
];
$context = stream_context_create($options);
try {
    $response = file_get_contents("https://api.example.com/endpoint", false, $context);
    if ($response === false) {
        throw new Exception("请求失败");
    }
} catch (Exception $e) {
    echo "接口调用超时或失败: " . $e->getMessage();
    // 记录错误日志
}

Guzzle HTTP 客户端(推荐)

安装

composer require guzzlehttp/guzzle

使用示例

use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ClientException;
$client = new Client([
    'timeout' => 10,              // 总超时
    'connect_timeout' => 5,       // 连接超时
    'read_timeout' => 10,         // 读取超时
    'retry' => 3                  // 重试次数
]);
try {
    $response = $client->request('GET', 'https://api.example.com/endpoint', [
        'headers' => [
            'Authorization' => 'Bearer your_token'
        ]
    ]);
    $body = $response->getBody();
    $data = json_decode($body, true);
} catch (ConnectException $e) {
    // 连接超时
    echo "连接超时: " . $e->getMessage();
} catch (RequestException $e) {
    // 请求超时或其他错误
    if ($e->hasResponse()) {
        $statusCode = $e->getResponse()->getStatusCode();
        echo "请求失败,状态码: " . $statusCode;
    } else {
        echo "请求异常: " . $e->getMessage();
    }
}

异步超时处理(Swoole/ReactPHP)

Swoole 协程方式

use Swoole\Coroutine\Http\Client;
use function Swoole\Coroutine\run;
run(function () {
    $cli = new Client('api.example.com', 443, true);
    $cli->set(['timeout' => 10]);
    $cli->get('/endpoint');
    if ($cli->statusCode === -1) {
        echo "请求超时";
    } else {
        echo $cli->body;
    }
    $cli->close();
});

完整封装示例

class ApiClient
{
    private $timeout = 10;
    private $connectTimeout = 5;
    private $maxRetries = 3;
    public function request($method, $url, $data = [])
    {
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => $this->timeout,
            CURLOPT_CONNECTTIMEOUT => $this->connectTimeout,
            CURLOPT_CUSTOMREQUEST => strtoupper($method),
        ]);
        if (!empty($data)) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
            curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
        }
        $attempt = 0;
        $response = null;
        while ($attempt < $this->maxRetries) {
            $response = curl_exec($ch);
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $error = curl_error($ch);
            $errno = curl_errno($ch);
            // 成功,跳出循环
            if ($httpCode >= 200 && $httpCode < 300) {
                break;
            }
            // 连接超时或请求超时,重试
            if ($errno == CURLE_OPERATION_TIMEDOUT || 
                $errno == CURLE_COULDNT_CONNECT) {
                $attempt++;
                if ($attempt < $this->maxRetries) {
                    $waitTime = pow(2, $attempt); // 指数退避
                    sleep($waitTime);
                    continue;
                }
            }
            break;
        }
        curl_close($ch);
        if ($response === false) {
            throw new \Exception("API 请求失败: " . ($error ?? "未知错误"));
        }
        return json_decode($response, true);
    }
}
// 使用示例
try {
    $client = new ApiClient();
    $result = $client->request('GET', 'https://api.example.com/data');
} catch (\Exception $e) {
    echo "请求失败: " . $e->getMessage();
    // 执行降级逻辑
}

最佳实践建议

超时时间设置

  • 连接超时:3-5秒
  • 读取超时:10-30秒(根据业务需求)
  • 总超时:不超过30秒

错误处理策略

// 降级处理
function handleApiFailure($error)
{
    // 1. 使用缓存数据
    if ($cacheData = getFromCache('api_data')) {
        return $cacheData;
    }
    // 2. 返回默认值
    return ['status' => 'error', 'message' => '服务暂时不可用'];
    // 3. 记录告警
    sendAlert("API 服务异常: " . $error);
}

日志记录

function logApiCall($url, $response, $duration, $error = null)
{
    $logData = [
        'url' => $url,
        'duration' => $duration,
        'response_code' => $response['code'] ?? null,
        'error' => $error,
        'timestamp' => date('Y-m-d H:i:s')
    ];
    // 写入日志文件
    file_put_contents(
        '/var/log/api_calls.log',
        json_encode($logData) . PHP_EOL,
        FILE_APPEND
    );
}

监控告警

  • 设置超时率告警阈值(如超过5%)
  • 监控响应时间趋势
  • 设置失败重试机制
  • 实现熔断器模式

选择合适的超时方案取决于项目的具体情况,对于重要的生产环境,推荐使用 Guzzle 并配合完善的错误处理和重试机制。

抱歉,评论功能暂时关闭!