PHP项目网络请求超时处理

wen PHP项目 4

本文目录导读:

PHP项目网络请求超时处理

  1. 常见网络请求方式的超时设置
  2. 异步请求处理(适合长时间任务)
  3. 全局超时配置方案
  4. 最佳实践建议
  5. 监控和日志记录

在PHP项目中处理网络请求超时是一个常见且重要的问题,下面我为你整理一份全面的超时处理方案。

常见网络请求方式的超时设置

cURL 请求超时处理

<?php
/**
 * cURL请求超时处理
 */
function curlRequest($url, $options = []) {
    $ch = curl_init();
    // 默认超时设置
    $defaultOptions = [
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT => 5,      // 连接超时时间(秒)
        CURLOPT_TIMEOUT => 10,            // 总超时时间(秒)
        CURLOPT_SSL_VERIFYPEER => false, // 跳过SSL验证(生产环境建议开启)
        CURLOPT_SSL_VERIFYHOST => false,
    ];
    // 合并自定义选项
    if (!empty($options)) {
        $defaultOptions = $options + $defaultOptions;
    }
    curl_setopt_array($ch, $defaultOptions);
    $response = curl_exec($ch);
    // 检查错误
    if (curl_errno($ch)) {
        $error = curl_error($ch);
        curl_close($ch);
        throw new Exception("cURL错误: {$error}");
    }
    // 获取HTTP状态码
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    return [
        'code' => $httpCode,
        'data' => $response
    ];
}
// 使用示例
try {
    $result = curlRequest('https://api.example.com/data', [
        CURLOPT_CONNECTTIMEOUT => 3,
        CURLOPT_TIMEOUT => 8,
    ]);
    echo "请求成功";
} catch (Exception $e) {
    echo "请求失败: " . $e->getMessage();
}

file_get_contents 超时处理

<?php
/**
 * file_get_contents 超时处理
 */
function fileGetContentsWithTimeout($url, $timeout = 5) {
    $context = stream_context_create([
        'http' => [
            'timeout' => $timeout, // 超时时间(秒)
            'method' => 'GET',
            'header' => "User-Agent: PHP\r\n"
        ],
        'ssl' => [
            'verify_peer' => true,
            'verify_peer_name' => true,
        ]
    ]);
    $result = @file_get_contents($url, false, $context);
    if ($result === false) {
        // 检查错误
        $error = error_get_last();
        throw new Exception("file_get_contents错误: " . $error['message']);
    }
    return $result;
}
// 使用示例
try {
    $data = fileGetContentsWithTimeout('https://api.example.com/data', 3);
    echo "请求成功";
} catch (Exception $e) {
    echo "请求失败: " . $e->getMessage();
}

Guzzle HTTP 客户端(推荐)

<?php
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\TimeoutException;
/**
 * Guzzle 请求超时处理
 */
class GuzzleClient {
    private $client;
    public function __construct() {
        $this->client = new Client([
            'timeout' => 10,      // 请求超时时间(秒)
            'connect_timeout' => 5, // 连接超时时间(秒)
            'read_timeout' => 5,    // 读取超时时间(秒)
            'verify' => true,       // SSL验证
        ]);
    }
    public function request($method, $url, $options = []) {
        try {
            $response = $this->client->request($method, $url, $options);
            return $response->getBody()->getContents();
        } catch (TimeoutException $e) {
            // 请求超时
            throw new Exception("请求超时: " . $e->getMessage());
        } catch (ConnectException $e) {
            // 连接失败
            throw new Exception("连接失败: " . $e->getMessage());
        } catch (RequestException $e) {
            // 其他请求错误
            throw new Exception("请求错误: " . $e->getMessage());
        }
    }
}
// 使用示例
$client = new GuzzleClient();
try {
    $data = $client->request('GET', 'https://api.example.com/data', [
        'timeout' => 3,
        'connect_timeout' => 2,
    ]);
    echo "请求成功";
} catch (Exception $e) {
    echo "请求失败: " . $e->getMessage();
}

异步请求处理(适合长时间任务)

<?php
/**
 * 使用 ReactPHP 进行异步请求
 */
use React\EventLoop\Factory;
use React\HttpClient\Client;
require 'vendor/autoload.php';
function asyncRequest($url, $callback) {
    $loop = Factory::create();
    $client = new Client($loop);
    $request = $client->request('GET', $url);
    $timeoutTimer = $loop->addTimer(10, function() use ($request) {
        $request->close();
        echo "请求超时!\n";
    });
    $request->on('response', function($response) use ($timeoutTimer, $callback) {
        $response->on('data', function($chunk) use ($callback) {
            echo "收到数据: " . $chunk;
            $callback($chunk);
        });
        $response->on('end', function() use ($timeoutTimer) {
            $loop->cancelTimer($timeoutTimer);
            echo "请求完成\n";
        });
    });
    $request->on('error', function(\Exception $e) use ($timeoutTimer) {
        $loop->cancelTimer($timeoutTimer);
        echo "请求错误: " . $e->getMessage() . "\n";
    });
    $request->end();
    $loop->run();
}
// 使用示例
asyncRequest('https://api.example.com/data', function($data) {
    // 处理数据
});

全局超时配置方案

自定义超时管理类

<?php
/**
 * 统一的超时管理类
 */
class TimeoutManager {
    private static $instance = null;
    private $timeoutConfig = [];
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    /**
     * 设置超时配置
     */
    public function configure(array $config) {
        $this->timeoutConfig = array_merge($this->timeoutConfig, [
            'default_timeout' => 10,      // 默认线程超时(秒)
            'connect_timeout' => 5,       // 默认连接超时(秒)
            'max_retries' => 2,           // 最大重试次数
            'retry_delay' => 3,           // 重试延迟(秒)
        ], $config);
    }
    /**
     * 带超时控制的请求
     */
    public function requestWithTimeout($url, $callback, $options = []) {
        $timeout = $options['timeout'] ?? $this->timeoutConfig['default_timeout'];
        $connectTimeout = $options['connect_timeout'] ?? $this->timeoutConfig['connect_timeout'];
        $attempts = 0;
        $maxRetries = $options['max_retries'] ?? $this->timeoutConfig['max_retries'];
        while ($attempts <= $maxRetries) {
            try {
                $startTime = microtime(true);
                // 设置 PHP 执行时间限制(防止脚本超时)
                set_time_limit($timeout + 10);
                // 执行请求
                $result = $callback([
                    'timeout' => $timeout,
                    'connect_timeout' => $connectTimeout
                ]);
                $elapsedTime = microtime(true) - $startTime;
                // 记录日志
                $this->logRequest($url, $elapsedTime, true);
                return $result;
            } catch (\Exception $e) {
                $attempts++;
                // 记录错误日志
                $this->logRequest($url, 0, false, $e->getMessage());
                if ($attempts <= $maxRetries) {
                    sleep($this->timeoutConfig['retry_delay']);
                } else {
                    throw $e;
                }
            }
        }
        throw new Exception("请求失败,重试次数已用完");
    }
    /**
     * 日志记录
     */
    private function logRequest($url, $elapsedTime, $success, $error = '') {
        $logData = [
            'timestamp' => date('Y-m-d H:i:s'),
            'url' => $url,
            'elapsed_time' => round($elapsedTime, 3),
            'success' => $success,
            'error' => $error
        ];
        // 写入日志文件或日志系统
        error_log(json_encode($logData) . PHP_EOL, 3, '/var/log/php_requests.log');
    }
    /**
     * 压测时的超时控制
     */
    public function benchmark($url, $callback, $concurrent = 5) {
        $results = [];
        $startTime = microtime(true);
        // 并发请求(使用多进程或异步方式)
        $pids = [];
        for ($i = 0; $i < $concurrent; $i++) {
            $pid = pcntl_fork();
            if ($pid == -1) {
                throw new Exception("无法创建子进程");
            } elseif ($pid) {
                $pids[] = $pid;
            } else {
                // 子进程执行
                try {
                    $result = $callback();
                    exit(0);
                } catch (Exception $e) {
                    exit(1);
                }
            }
        }
        // 等待所有子进程完成
        foreach ($pids as $pid) {
            pcntl_waitpid($pid, $status);
        }
        $elapsedTime = microtime(true) - $startTime;
        return [
            'total_time' => $elapsedTime,
            'concurrent' => $concurrent,
            'success' => array_sum($results)
        ];
    }
}
// 使用示例
$manager = TimeoutManager::getInstance();
$manager->configure([
    'default_timeout' => 8,
    'connect_timeout' => 3,
    'max_retries' => 3,
]);
try {
    $result = $manager->requestWithTimeout(
        'https://api.example.com/data',
        function($timeout) {
            // 使用 cURL 或其他方式请求
            return curlRequest('https://api.example.com/data', [
                CURLOPT_TIMEOUT => $timeout['timeout'],
                CURLOPT_CONNECTTIMEOUT => $timeout['connect_timeout'],
            ]);
        }
    );
    var_dump($result);
} catch (Exception $e) {
    echo "请求最终失败: " . $e->getMessage();
}

Laravel 中的超时处理

<?php
// Laravel 中使用 HTTP 客户端
use Illuminate\Support\Facades\Http;
try {
    $response = Http::timeout(10)           // 总超时
        ->connectTimeout(5)                 // 连接超时
        ->retry(3, 100)                    // 重试3次,间隔100ms
        ->get('https://api.example.com/data');
    if ($response->successful()) {
        $data = $response->json();
    }
} catch (\Illuminate\Http\Client\ConnectionException $e) {
    echo "连接失败: " . $e->getMessage();
} catch (\Illuminate\Http\Client\RequestException $e) {
    echo "请求错误: " . $e->getMessage();
}

最佳实践建议

超时时间设置建议

场景 连接超时 请求超时
内部服务 2秒 5秒
外部API 5秒 10秒
大数据处理 10秒 30秒
长时间任务 15秒 60秒

错误处理策略

<?php
class NetworkTimeoutHandler {
    /**
     * 分级重试策略
     */
    public function retryWithBackoff($callback, $options = []) {
        $maxRetries = $options['max_retries'] ?? 3;
        $baseDelay = $options['base_delay'] ?? 1;
        $useExponentialBackoff = $options['exponential'] ?? true;
        for ($attempt = 0; $attempt < $maxRetries; $attempt++) {
            try {
                return $callback();
            } catch (Exception $e) {
                if ($attempt < $maxRetries - 1) {
                    // 计算延迟时间
                    if ($useExponentialBackoff) {
                        $delay = $baseDelay * pow(2, $attempt);
                    } else {
                        $delay = $baseDelay;
                    }
                    // 添加随机抖动,防止同步重试
                    $delay = $delay * (0.5 + lcg_value() * 0.5);
                    sleep($delay);
                }
            }
        }
        throw new Exception("请求失败,重试次数已用完");
    }
    /**
     * 请求降级处理
     */
    public function withFallback($primaryCallback, $fallbackCallback) {
        try {
            return $primaryCallback();
        } catch (TimeoutException $e) {
            // 主请求超时,启用备用方案
            return $fallbackCallback();
        }
    }
    /**
     * 批量请求超时处理
     */
    public function batchRequestWithTimeout($urls, $timeout = 5) {
        $results = [];
        $multiHandle = curl_multi_init();
        $handles = [];
        foreach ($urls as $key => $url) {
            $handle = curl_init($url);
            curl_setopt_array($handle, [
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_TIMEOUT => $timeout,
                CURLOPT_CONNECTTIMEOUT => $timeout/2,
            ]);
            $handles[$key] = $handle;
            curl_multi_add_handle($multiHandle, $handle);
        }
        $active = null;
        do {
            $mrc = curl_multi_exec($multiHandle, $active);
        } while ($mrc == CURLM_CALL_MULTI_PERFORM);
        while ($active && $mrc == CURLM_OK) {
            if (curl_multi_select($multiHandle) == -1) {
                usleep(100);
            }
            do {
                $mrc = curl_multi_exec($multiHandle, $active);
            } while ($mrc == CURLM_CALL_MULTI_PERFORM);
        }
        foreach ($handles as $key => $handle) {
            $results[$key] = curl_multi_getcontent($handle);
            curl_multi_remove_handle($multiHandle, $handle);
            curl_close($handle);
        }
        curl_multi_close($multiHandle);
        return $results;
    }
}
// 使用示例
$handler = new NetworkTimeoutHandler();
try {
    $result = $handler->retryWithBackoff(function() {
        $client = new GuzzleClient(['timeout' => 5]);
        return $client->get('https://api.example.com');
    }, [
        'max_retries' => 3,
        'base_delay' => 1,
        'exponential' => true
    ]);
} catch (Exception $e) {
    // 降级处理
    $fallbackData = $handler->withFallback(
        function() { return fetchFromCache(); },
        function() { return fetchFromDatabase(); }
    );
}

监控和日志记录

<?php
/**
 * 超时监控
 */
class TimeoutMonitor {
    private $redis;
    public function __construct() {
        $this->redis = new Redis();
        $this->redis->connect('127.0.0.1', 6379);
    }
    /**
     * 记录超时事件
     */
    public function recordTimeout($url, $timeout) {
        $this->redis->lpush('timeout_log', json_encode([
            'url' => $url,
            'timeout' => $timeout,
            'timestamp' => date('Y-m-d H:i:s')
        ]));
        // 记录到监控系统
        $this->sendToMonitor($url, $timeout);
    }
    /**
     * 获取超时统计
     */
    public function getTimeoutStats() {
        $logs = $this->redis->lrange('timeout_log', 0, 99);
        $stats = ['total' => 0, 'by_url' => []];
        foreach ($logs as $log) {
            $logData = json_decode($log, true);
            $stats['total']++;
            if (!isset($stats['by_url'][$logData['url']])) {
                $stats['by_url'][$logData['url']] = 0;
            }
            $stats['by_url'][$logData['url']]++;
        }
        return $stats;
    }
    /**
     * 发送到监控系统
     */
    private function sendToMonitor($url, $timeout) {
        // 集成 Sentry、Elastic APM 等
        $data = [
            'type' => 'timeout',
            'url' => $url,
            'timeout' => $timeout,
            'timestamp' => time()
        ];
        // PS:实际项目中应使用专业监控框架
        file_put_contents('/var/log/timeout_monitor.log', 
            json_encode($data) . PHP_EOL, FILE_APPEND);
    }
}
  1. 选择合适的超时时间:根据业务场景动态调整
  2. 实现重试机制:使用指数退避算法,避免雪崩
  3. 做好降级处理:准备备用方案
  4. 完善监控告警:及时发现并解决问题
  5. 合理设计架构:考虑使用消息队列、异步处理等

这些方案可以根据你的项目需求进行选择和调整,核心是:合理设置超时时间 + 实现优雅的错误处理 + 做好日志记录

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