使用 Guzzle 中介軟體進行優雅的請求重試

linkjian發表於2019-07-31

介紹

Guzzle是 PHP 的一款HTTP客戶端包,這裡介紹如何使用Guzzle提供的中介軟體來進行請求失敗後的重試,

關於中介軟體的介紹可以檢視文件:Handlers and Middleware

使用

先來看看Guzzle原始碼中 retry 中介軟體的定義:

/**
 * Middleware that retries requests based on the boolean result of
 * invoking the provided "decider" function.
 *
 * If no delay function is provided, a simple implementation of exponential
 * backoff will be utilized.
 *
 * @param callable $decider Function that accepts the number of retries,
 *                          a request, [response], and [exception] and
 *                          returns true if the request is to be retried.
 * @param callable $delay   Function that accepts the number of retries and
 *                          returns the number of milliseconds to delay.
 *
 * @return callable Returns a function that accepts the next handler.
 */
 public static function retry(callable $decider, callable $delay = null)
 {
     return function (callable $handler) use ($decider, $delay) {
        return new RetryMiddleware($decider, $handler, $delay);
     };
 }

retry 接收兩個引數:

  1. $decider:重試決策,型別是callable,中介軟體根據該回撥函式的返回值來決定是否進行重試。回撥函式接收四個引數,分別是:當前重試次數,當前請求(GuzzleHttp\Psr7\Request),當前響應(GuzzleHttp\Psr7\Response), 當前發生的異常 (GuzzleHttp\Exception\RequestException),回撥函式返回true/false,表示繼續重試/停止重試
  2. $delay:重試延遲時間,型別也是callable,中介軟體根據該回撥函式返回的值來延遲下次請求的時間,回撥函式接收一個引數,是當前重試的次數,該回撥函式返回下次重試的時間間隔

所以根據Retry中介軟體的定義,我們有如下的程式碼:


<?php
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Handler\CurlHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;

class TestGuzzleRetry
{
    /**
     * 最大重試次數
     */
    const MAX_RETRIES = 5;

    /**
     * @var Client
     */
    protected $client;

    /**
     * GuzzleRetry constructor.
     */
    public function __construct()
    {
        // 建立 Handler
        $handlerStack = HandlerStack::create(new CurlHandler());
        // 建立重試中介軟體,指定決策者為 $this->retryDecider(),指定重試延遲為 $this->retryDelay()
        $handlerStack->push(Middleware::retry($this->retryDecider(), $this->retryDelay()));
        // 指定 handler
        $this->client = new Client(['handler' => $handlerStack]);
    }
    /**
     * retryDecider
     * 返回一個匿名函式, 匿名函式若返回false 表示不重試,反之則表示繼續重試
     * @return Closure
     */
    protected function retryDecider()
    {
        return function (
            $retries,
            Request $request,
            Response $response = null,
            RequestException $exception = null
        ) {
            // 超過最大重試次數,不再重試
            if ($retries >= self::MAX_RETRIES) {
                return false;
            }

            // 請求失敗,繼續重試
            if ($exception instanceof ConnectException) {
                return true;
            }

            if ($response) {
                // 如果請求有響應,但是狀態碼大於等於500,繼續重試(這裡根據自己的業務而定)
                if ($response->getStatusCode() >= 500) {
                    return true;
                }
            }

            return false;
        };
    }

    /**
     * 返回一個匿名函式,該匿名函式返回下次重試的時間(毫秒)
     * @return Closure
     */
    protected function retryDelay()
    {
        return function ($numberOfRetries) {
            return 1000 * $numberOfRetries;
        };
    }
}

相關文章