prismamedia/php-endeavor

PHP 工具,用于重试任何操作,采用各种策略

v1.0.0 2022-02-25 12:05 UTC

This package is auto-updated.

Last update: 2024-08-25 17:20:57 UTC


README

Endeavor 是一个 PHP 工具,用于重试任何操作,采用各种策略。

要求

  • PHP 7.4+

安装

使用 Composer

composer require prismamedia/php-endeavor

用法

简单地将您想要重试的代码封装在 Closure 中,然后在 run() 方法中调用它

use PrismaMedia\Endeavor\Endeavor;
use PrismaMedia\Endeavor\Strategy\ConstantStrategy;

$endeavor = new Endeavor(new ConstantStrategy(500));
$endeavor->run(function () {
    // Code which can throw any \Throwable
});

默认情况下,Endeavor 将尝试运行给定的代码 5 次。如果第一次尝试成功,Endeavor 将停止。但如果代码每次尝试都失败,Endeavor 将在最后一次尝试时抛出异常。

可以使用构造函数的第二个参数指定最大尝试次数

use PrismaMedia\Endeavor\Endeavor;
use PrismaMedia\Endeavor\Strategy\ConstantStrategy;

$endeavor = new Endeavor(new ConstantStrategy(500), 3);
$endeavor->run(function () {
    // Code which can throw any \Throwable
});

在这个例子中,它将运行代码 3 次,如果在第三次尝试时仍然失败,则抛出异常。

请注意,最大尝试次数包括第一次。

可以使用构造函数的第三个参数指定最大延迟,以创建一个上限

use PrismaMedia\Endeavor\Endeavor;
use PrismaMedia\Endeavor\Strategy\ExponentialStrategy;

$endeavor = new Endeavor(new ExponentialStrategy(1000), 5, 5000);
$endeavor->run(function () {
    // Code which can throw any \Throwable
});

在这个例子中,代码将以指数策略执行 5 次,每次尝试的延迟翻倍,最大延迟为 5 秒

策略

根据预期的重试间隔,Endeavor 可以使用各种策略进行实例化。

每个策略都需要一个以毫秒为单位的 delay,并根据策略的性质计算下一次尝试的延迟。

ConstantStrategy

这是最简单的策略。它接受一个固定的延迟,并将尝试之间的间隔设置为该数值。

use PrismaMedia\Endeavor\Endeavor;
use PrismaMedia\Endeavor\Strategy\ConstantStrategy;

$endeavor = new Endeavor(new ConstantStrategy(100));
$endeavor->run(function () {
    throw new \RuntimeException('Failing');
});

// 1st attempt: immediate
// 2nd attempt: 100ms
// 3rd attempt: 100ms
// 4th attempt: 100ms
// 5th attempt: 100ms

LinearStrategy

此策略接受一个初始延迟,并在每次尝试中将其相加。

use PrismaMedia\Endeavor\Endeavor;
use PrismaMedia\Endeavor\Strategy\LinearStrategy;

$endeavor = new Endeavor(new LinearStrategy(100));
$endeavor->run(function () {
    throw new \RuntimeException('Failing');
});

// 1st attempt: immediate
// 2nd attempt: 100ms
// 3rd attempt: 200ms
// 4th attempt: 300ms
// 5th attempt: 400ms

ExponentialStrategy

此策略接受一个初始延迟,并在每次尝试中将它翻倍。

use PrismaMedia\Endeavor\Endeavor;
use PrismaMedia\Endeavor\Strategy\ExponentialStrategy;

$endeavor = new Endeavor(new ExponentialStrategy(100));
$endeavor->run(function () {
    throw new \RuntimeException('Failing');
});

// 1st attempt: immediate
// 2nd attempt: 100ms
// 3rd attempt: 200ms
// 4th attempt: 400ms
// 5th attempt: 800ms

MultiplicativeStrategy

此策略接受一个初始延迟和一个乘数,然后在每次尝试中将延迟乘以该乘数。

use PrismaMedia\Endeavor\Endeavor;
use PrismaMedia\Endeavor\Strategy\MultiplicativeStrategy;

$endeavor = new Endeavor(new MultiplicativeStrategy(100, 3));
$endeavor->run(function () {
    throw new \RuntimeException('Failing');
});

// 1st attempt: immediate
// 2nd attempt: 100ms
// 3rd attempt: 300ms
// 4th attempt: 900ms
// 5th attempt: 2700ms

错误处理

默认情况下,Endeavor 在抛出异常时会简单地重试代码,然后在达到最大尝试次数时抛出最后一次遇到的异常。

可以使用一个 Closure 指定错误处理程序,该程序将在每次不成功的尝试后执行。

它可以用于记录目的

use PrismaMedia\Endeavor\Endeavor;
use PrismaMedia\Endeavor\Strategy\LinearStrategy;

$endeavor = new Endeavor(new LinearStrategy(500));
$endeavor->setErrorHandler(function (Endeavor $endeavor, \Throwable $e, int $attempt) {
    // $endeavor is the current instance
    // $e is the thrown Exception during this attempt
    // $attempt is the current attempt number
    $this->logger->error(
        'Something went wrong on the attempt #{attempt}: {error}',
        [
            'attempt' => $attempt,
            'error' => $e->getMessage(),
        ]
    );
});
$endeavor->run(function () {
    throw new \RuntimeException('Failing');
});

或者抛出另一个异常并停止 Endeavor,如果错误无法恢复

$endeavor->setErrorHandler(function (Endeavor $endeavor, \Throwable $e, int $attempt) {
    if ($e instanceof OneSpecificException) {
        throw $e
    }
});

还可以用来更改当前策略

$endeavor->setErrorHandler(function (Endeavor $endeavor, \Throwable $e, int $attempt) {
    if ($e instanceof UnreachableDatabaseException) {
        $endeavor->setStrategy(new ConstantStrategy(5000));
    }
});

测试

测试使用 Endeavor 的类可能会显著减慢测试的执行速度。

Symfony 环境

在 Symfony 项目中,可以使用 symfony/phpunit-bridge 包和包含的 ClockMock 来解决这个问题。

请参阅 文档 了解如何设置桥接和使用 @group time-sensitive 注解。

最后,在 tests/bootstrap.php文档),注册 Endeavor

# tests/boostrap.php
<?php

use PrismaMedia\Endeavor\Endeavor;
use Symfony\Bridge\PhpUnit\ClockMock;
use Symfony\Component\Dotenv\Dotenv;

require dirname(__DIR__).'/vendor/autoload.php';

if (file_exists(dirname(__DIR__).'/config/bootstrap.php')) {
    require dirname(__DIR__).'/config/bootstrap.php';
} elseif (method_exists(Dotenv::class, 'bootEnv')) {
    (new Dotenv())->bootEnv(dirname(__DIR__).'/.env');
}

// Register Endeavor in ClockMock to skip the waiting time between retries
ClockMock::register(Endeavor::class);

贡献

欢迎提交拉取请求。对于重大更改,请首先打开一个问题来讨论您想要更改的内容。

请确保适当地更新测试。

许可证

BSD 3条款许可证