guzzle使用中间件进行优雅的请求重试

2,434 阅读1分钟

使用

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 接收两个参数:

  • $decider重试决策,类型是 callable,中间件根据该回调函数的返回值来决定是否进行重试。回调函数接收四个参数,分别是:当前重试次数当前请求 (GuzzleHttp\Psr7\Request),当前响应 (GuzzleHttp\Psr7\Response), 当前发生的异常 (GuzzleHttp\Exception\RequestException),回调函数返回 true/false,表示继续重试 / 停止重试

  • $delay重试延迟时间,类型也是 callable,中间件根据该回调函数返回的值来延迟下次请求的时间,回调函数接收一个参数,是当前重试的次数,该回调函数返回下次重试的时间间隔

所以根据 Retry 中间件的定义,我们有如下的代码:

<?php

namespace App\Services\Dida;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Handler\CurlHandler;
//use GuzzleHttp\Handler\CurlMultiHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\RequestOptions;

class BaseService
{
    /**
     * 最大重试次数
     */
    const MAX_RETRIES = 10;

    protected $cache_tags;

    public function __construct()
    {
        $this->cache_tags = config("api_token.dida.cache.tags");
    }

    /**
     * 抓取数据接口
     * @param string $uri 接口地址
     * @param array $data 请求参数
     * @return mixed
     */
    protected function fetch($uri, array $data)
    {
        // 创建 Handler
        $handlerStack = HandlerStack::create(new CurlHandler());
        // 如果是一次发出多个请求的话,需要改为CurlMultiHandler
        //$handlerStack = HandlerStack::create(new CurlMultiHandler());
        // 创建重试中间件,指定决策者为 $this->retryDecider(),指定重试延迟为 $this->retryDelay()
        $handlerStack->push(Middleware::retry($this->retryDecider(), $this->retryDelay()));
        //创建含base_uri及handler的guzzle#Client#
        $client = new Client([
            'base_uri'  => config('api_token.dida.test.BaseUrl'),
            'handler'   => $handlerStack,
        ]);
        //本次请求的post的body参数数据
        $header = [
            'Header'           => [
                'ClientID'      => config('api_token.dida.test.ClientID'),
                'LicenseKey'    => config('api_token.dida.test.LicenseKey'),
            ],
        ];
        $body   = array_merge($header, $data);
        // guzzle#Client# 发起post请求
        // 不能用form_params 只能用json
        // 设置5s超时
        $req = $client->post($uri, [RequestOptions::JSON => $body, 'timeout' => 5])
            ->getBody()
            ->getContents();

        return \GuzzleHttp\json_decode($req, true);
    }

    /**
     * 返回一个匿名函数, 匿名函数若返回false 表示不重试,反之则表示继续重试
     * @return \Closure
     */
    private 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
     */
    private function retryDelay()
    {
        return function ($numberOfRetries) {
            return 1000 * $numberOfRetries;
        };
    }
}

参考