eventsauce/backoff

退避策略接口

资助包维护!
frankdejonge

1.2.0 2023-09-17 20:31 UTC

This package is auto-updated.

Last update: 2024-09-18 14:10:35 UTC


README

此库提供封装退避策略的接口。

composer require eventsauce/backoff

利用退避策略

退避策略在尝试重试特定任务的代码块中应用。

<?php

use EventSauce\BackOff\BackOffStrategy;

class BusinessLogic
{
    public function __construct(
        private ExternalDependency $dependency,
        private BackOffStrategy $backOff,
    ) {}

    public function performAction(): void
    {
        $tries = 0;

        start:
        try {
            ++$tries;
            $this->dependency->actionThatMayFail();
        } catch (Throwable $throwable) {
            $this->backOff->backOff($tries, $throwable);
            goto start;
        }
    }
}

指数退避

一个著名的退避策略是指数退避,这是默认提供的策略。

sleep = initial_delay * (base ^ (number_of_tries - 1)
<?php

use EventSauce\BackOff\ExponentialBackOffStrategy;

$backOff = new ExponentialBackOffStrategy(
    100000, // initial delay in microseconds, 0.1 seconds
    15, //  max number of tries
    2500000, // (optional) max delay in microseconds, default 2.5 seconds
    2.0, // (optional) base to control the growth factor, default 2.0
);

$businessLogic = new BusinessLogic(new ExternalDependency(), $backOff);

try {
    $businessLogic->performAction();
} catch (Throwable $throwable) {
    // handle the throwable
}

斐波那契退避

斐波那契退避策略根据斐波那契数列增加退避时间。

sleep = initial_delay * fibonacci(number_of_tries)
<?php

use EventSauce\BackOff\FibonacciBackOffStrategy;

$backOff = new FibonacciBackOffStrategy(
    100000, // initial delay in microseconds, 0.1 seconds
    15, // max number of tries
    2500000, // (optional) max delay in microseconds, default 2.5 seconds
);

$businessLogic = new BusinessLogic(new ExternalDependency(), $backOff);

try {
    $businessLogic->performAction();
} catch (Throwable $throwable) {
    // handle the throwable
}

线性退避

线性退避策略线性增加退避时间。

sleep = initial_delay * number_of_tries
<?php

use EventSauce\BackOff\LinearBackOffStrategy;

$backOff = new LinearBackOffStrategy(
    100000, // initial delay in microseconds, 0.1 seconds
    15, // max number of tries
    2500000, // (optional) max delay in microseconds, default 2.5 seconds
);

$businessLogic = new BusinessLogic(new ExternalDependency(), $backOff);

try {
    $businessLogic->performAction();
} catch (Throwable $throwable) {
    // handle the throwable
}

抖动

当许多客户端被迫重试时,具有确定性的间隔可能会导致许多客户端同时重试。向其中添加随机性可以确保重试客户端在时间上分散。这种随机性确保客户端不太可能同时重试。

使用抖动

每个接受休眠的策略都接受一个EventSauce\BackOff\Jitter\Jitter实现。

use EventSauce\BackOff\ExponentialBackOffStrategy;
use EventSauce\BackOff\FibonacciBackOffStrategy;
use EventSauce\BackOff\LinearBackOffStrategy;

$exponential = new ExponentialBackOffStrategy(100000, 25, jitter: $jitter);
$fibonacci = new FibonacciBackOffStrategy(100000, 25, jitter: $jitter);
$linear = new LinearBackOffStrategy(100000, 25, jitter: $jitter);

全抖动

full jitter

全抖动使用从0到初始计算的休眠时间之间的随机值。

sleep = number_between(0, sleep)
use EventSauce\BackOff\Jitter\FullJitter;
$jitter = new FullJitter();

半抖动

half jitter

全抖动使用从初始休眠时间的一半到全部初始休眠时间的随机值。

sleep = sleep / 2 + number_between(0 , sleep / 2)
use EventSauce\BackOff\Jitter\HalfJitter;
$jitter = new HalfJitter();

分散抖动

scattered jitter

分散抖动使用一个范围,在该范围内分散结果值。为了说明,这里有一些示例

jittered = sleep * range
base = sleep - jittered
sleep = base + number_between(0 , jittered * 2)
use EventSauce\BackOff\Jitter\ScatteredJitter;
$jitter = new ScatteredJitter($range = 0.5);

设计理念

与其他指数退避库不同,此库不运行您想要重试的操作。这使得包的设计非常简单。它也不对周围代码施加任何限制。

您可以根据返回值进行重试

use EventSauce\BackOff\BackOffStrategy;

function action(Client $client, BackOffStrategy $backOff): void
{
    $tries = 0;
    start:
    $tries++;
    $response = $client->doSomething();
    
    if ($response == SomeParticular::VALUE) {
        $backOff->backOff($tries, new LogicException('Exhausted back-off'));
        goto start;
    }
}

您可以在特定异常类型上进行重试

use EventSauce\BackOff\BackOffStrategy;

function action(Client $client, BackOffStrategy $backOff): void
{
    $tries = 0;
    start:
    $tries++;
    
    try {
        $client->doSomething();
    } catch (SpecificException $exception) {
        $backOff->backOff($tries, $exception);
        goto start;
    }
}

选择权在你。享受吧!

PS:是的,有很多goto语句,请处理它 😎

但是Frank,我超级懒惰!

好吧好吧好吧,在这种情况下,请使用BackOffRunner类来运行任何带有重试策略的callable

use EventSauce\BackOff\BackOffRunner;
use EventSauce\BackOff\ExponentialBackOffStrategy;

$strategy = new ExponentialBackOffStrategy(initialDelayMs: 100, maxTries: 5);
$runner = new BackOffRunner($strategy);

$runner->run(function () {
    // Do something that might throw an exception!
});

只想在特定异常上重试?将异常类作为第二个构造函数参数传递。

use EventSauce\BackOff\BackOffRunner;
use EventSauce\BackOff\ExponentialBackOffStrategy;

$strategy = new ExponentialBackOffStrategy(initialDelayMs: 100, maxTries: 5);
$runner = new BackOffRunner($strategy, LogicException::class);

$runner->run(function () {
    // Only LogicException is retried, the rest is not!
});