PHP接口调用重试机制方案

Php   2025-11-12 08:09   64   0  

调用接口时,如果没有返回数据,我们就增加下一次调用的间隔时间。我们可以使用一个简单的退避策略,例如每次没有数据返回时,等待时间翻倍,直到达到一个最大等待时间。同时,如果有数据返回,我们就重置等待时间。

方案一:基础重试机制

<?php
function callApiWithRetry($url, $maxRetries = 5, $initialDelay = 1000) {
    $retries = 0;
    $delay = $initialDelay;
    
    while ($retries < $maxRetries) {
        // 调用接口
        $response = file_get_contents($url);
        $data = json_decode($response, true);
        
        // 检查是否有数据
        if (!empty($data)) {
            return $data;
        }
        
        // 无数据,增加等待时间
        $retries++;
        echo "第 {$retries} 次重试,等待 {$delay} 毫秒\n";
        
        // 等待指定时间(毫秒)
        usleep($delay * 1000);
        
        // 增加延迟时间(指数退避)
        $delay *= 2;
    }
    
    throw new Exception("达到最大重试次数,未获取到数据");
}

// 使用示例
try {
    $result = callApiWithRetry('https://api.example.com/data', 5, 1000);
    print_r($result);
} catch (Exception $e) {
    echo "错误: " . $e->getMessage();
}
?>

方案二:使用cURL的进阶版本

<?php
class ApiCaller {
    private $maxRetries;
    private $initialDelay;
    private $maxDelay;
    
    public function __construct($maxRetries = 5, $initialDelay = 1000, $maxDelay = 30000) {
        $this->maxRetries = $maxRetries;
        $this->initialDelay = $initialDelay;
        $this->maxDelay = $maxDelay;
    }
    
    public function callWithRetry($url, $options = []) {
        $retries = 0;
        $delay = $this->initialDelay;
        
        while ($retries < $this->maxRetries) {
            $response = $this->callApi($url, $options);
            
            // 检查响应是否有效
            if ($this->isValidResponse($response)) {
                return $response;
            }
            
            $retries++;
            echo "无数据返回,第 {$retries} 次重试,等待 " . ($delay/1000) . " 秒\n";
            
            // 等待
            usleep($delay * 1000);
            
            // 指数退避,但不超过最大延迟
            $delay = min($delay * 2, $this->maxDelay);
        }
        
        throw new Exception("接口调用失败,达到最大重试次数");
    }
    
    private function callApi($url, $options) {
        $ch = curl_init();
        
        $defaultOptions = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/json',
            ]
        ];
        
        curl_setopt_array($ch, array_merge($defaultOptions, $options));
        
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        
        if (curl_error($ch)) {
            curl_close($ch);
            throw new Exception('cURL错误: ' . curl_error($ch));
        }
        
        curl_close($ch);
        
        return [
            'http_code' => $httpCode,
            'data' => $response
        ];
    }
    
    private function isValidResponse($response) {
        // 检查HTTP状态码
        if ($response['http_code'] !== 200) {
            return false;
        }
        
        // 解析JSON数据
        $data = json_decode($response['data'], true);
        
        // 检查数据是否为空
        if (empty($data)) {
            return false;
        }
        
        // 可以根据具体API返回结构自定义检查逻辑
        // 例如:检查特定字段是否存在数据
        if (isset($data['data']) && empty($data['data'])) {
            return false;
        }
        
        return true;
    }
}

// 使用示例
$apiCaller = new ApiCaller(5, 1000, 30000); // 最多重试5次,初始延迟1秒,最大延迟30秒

try {
    $result = $apiCaller->callWithRetry('https://api.example.com/data');
    print_r($result);
} catch (Exception $e) {
    echo "API调用失败: " . $e->getMessage();
}
?>

方案三:带有随机抖动的重试策略

<?php
class SmartApiCaller {
    private $maxRetries;
    private $baseDelay;
    private $maxDelay;
    
    public function __construct($maxRetries = 5, $baseDelay = 1000, $maxDelay = 60000) {
        $this->maxRetries = $maxRetries;
        $this->baseDelay = $baseDelay;
        $this->maxDelay = $maxDelay;
    }
    
    public function callWithExponentialBackoff($url, $callback = null) {
        $retries = 0;
        
        while ($retries <= $this->maxRetries) {
            try {
                $response = $this->makeRequest($url);
                
                // 使用回调函数检查响应
                if ($callback) {
                    $isValid = call_user_func($callback, $response);
                } else {
                    $isValid = $this->defaultValidation($response);
                }
                
                if ($isValid) {
                    return $response;
                }
                
            } catch (Exception $e) {
                echo "请求异常: " . $e->getMessage() . "\n";
            }
            
            if ($retries < $this->maxRetries) {
                $delay = $this->calculateDelay($retries);
                echo "等待 {$delay} 毫秒后重试...\n";
                usleep($delay * 1000);
            }
            
            $retries++;
        }
        
        throw new Exception("所有重试尝试均失败");
    }
    
    private function makeRequest($url) {
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_SSL_VERIFYPEER => false,
        ]);
        
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        
        if (curl_error($ch)) {
            throw new Exception(curl_error($ch));
        }
        
        curl_close($ch);
        
        return [
            'http_code' => $httpCode,
            'body' => $response,
            'json' => json_decode($response, true)
        ];
    }
    
    private function defaultValidation($response) {
        return $response['http_code'] === 200 && 
               !empty($response['body']) && 
               !empty($response['json']);
    }
    
    private function calculateDelay($retryCount) {
        // 指数退避 + 随机抖动
        $exponentialDelay = $this->baseDelay * pow(2, $retryCount);
        $jitter = rand(0, 500); // 添加0-500ms的随机抖动
        
        return min($exponentialDelay + $jitter, $this->maxDelay);
    }
}

// 使用示例
$caller = new SmartApiCaller();

// 自定义验证回调
$validationCallback = function($response) {
    // 检查特定的业务逻辑
    if ($response['http_code'] !== 200) {
        return false;
    }
    
    $data = $response['json'];
    return isset($data['success']) && $data['success'] && !empty($data['result']);
};

try {
    $result = $caller->callWithExponentialBackoff(
        'https://api.example.com/data', 
        $validationCallback
    );
    print_r($result);
} catch (Exception $e) {
    echo "最终失败: " . $e->getMessage();
}
?>

方案四:使用Guzzle HTTP客户端

如果你使用Composer,可以安装Guzzle来简化HTTP请求:

通过Composer可快速集成Guzzle到项目中:

composer require guzzlehttp/guzzle
<?php
require 'vendor/autoload.php';

use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;

class GuzzleApiCaller {
    private $client;
    private $maxRetries;
    
    public function __construct($maxRetries = 5) {
        $this->client = new Client(['timeout' => 30]);
        $this->maxRetries = $maxRetries;
    }
    
    public function callWithRetry($url) {
        $retries = 0;
        $delay = 1000; // 1秒
        
        while ($retries < $this->maxRetries) {
            try {
                $response = $this->client->get($url);
                $data = json_decode($response->getBody(), true);
                
                if (!empty($data)) {
                    return $data;
                }
                
                echo "无数据,准备重试...\n";
                
            } catch (RequestException $e) {
                echo "请求失败: " . $e->getMessage() . "\n";
            }
            
            $retries++;
            if ($retries < $this->maxRetries) {
                echo "等待 " . ($delay/1000) . " 秒后重试\n";
                usleep($delay * 1000);
                $delay *= 2; // 指数退避
            }
        }
        
        throw new Exception("达到最大重试次数");
    }
}

// 使用示例
$caller = new GuzzleApiCaller();
try {
    $result = $caller->callWithRetry('https://api.example.com/data');
    print_r($result);
} catch (Exception $e) {
    echo "错误: " . $e->getMessage();
}
?>


说明

  1. 指数退避: 每次重试的等待时间翻倍

  2. 最大重试次数: 防止无限循环

  3. 随机抖动: 避免多个客户端同时重试

  4. 灵活验证: 支持自定义响应验证逻辑

  5. 异常处理: 完善的错误处理机制