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

介绍

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;
        };
    }
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 4年前 自动加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 10

哈哈,学到了。 现在都是靠堆代码重试,看了楼主的这个,感觉发现新大陆了..

4年前 评论
黑将军

GET,楼主 :+1:

4年前 评论

感谢 我之前都是用while来进行

4年前 评论

谢谢大佬分享

4年前 评论

大佬 这个是使用阻塞延迟还是用队列延时的 具体延时是怎么实现的

4年前 评论

@最后一缕阳光 阻塞的,Retry中间件在下次请求之前添加了 delay 参数,然后Guzzle根据 delay 参数,使用 usleep 函数来延迟

4年前 评论

@linkjian 那时间设置太长是不是会造成连接超时

4年前 评论
linkjian (楼主) 4年前
最后一缕阳光 (作者) 4年前
/**
     * 返回一个匿名函数,该匿名函数返回下次重试的时间(毫秒)
     * @return Closure
     */
    protected function retryDelay()
    {
        return function ($numberOfRetries) {
            return 1000 * $numberOfRetries;
        };
    }

想请问一下 这里不设置 $numberOfRetries 最后 该怎么调用这个参数了?

/**
     * 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);
        };
    }

这里提示 需要传入 callback 所以我也就不能传入 int 我该怎么才能调整这个重试延迟时间呢?

3年前 评论
烟囱土著 2年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!