Add jitter to RetryBackof
This commit is contained in:
parent
ca220a1992
commit
ace731437e
@ -1641,8 +1641,8 @@ class Configuration implements ConfigurationInterface
|
|||||||
->addDefaultsIfNotSet()
|
->addDefaultsIfNotSet()
|
||||||
->beforeNormalization()
|
->beforeNormalization()
|
||||||
->always(function ($v) {
|
->always(function ($v) {
|
||||||
if (isset($v['backoff_service']) && (isset($v['delay']) || isset($v['multiplier']) || isset($v['max_delay']))) {
|
if (isset($v['backoff_service']) && (isset($v['delay']) || isset($v['multiplier']) || isset($v['max_delay']) || isset($v['jitter']))) {
|
||||||
throw new \InvalidArgumentException('The "backoff_service" option cannot be used along with the "delay", "multiplier" or "max_delay" options.');
|
throw new \InvalidArgumentException('The "backoff_service" option cannot be used along with the "delay", "multiplier", "max_delay" or "jitter" options.');
|
||||||
}
|
}
|
||||||
if (isset($v['decider_service']) && (isset($v['http_codes']))) {
|
if (isset($v['decider_service']) && (isset($v['http_codes']))) {
|
||||||
throw new \InvalidArgumentException('The "decider_service" option cannot be used along with the "http_codes" options.');
|
throw new \InvalidArgumentException('The "decider_service" option cannot be used along with the "http_codes" options.');
|
||||||
@ -1670,6 +1670,7 @@ class Configuration implements ConfigurationInterface
|
|||||||
->integerNode('delay')->defaultValue(1000)->min(0)->info('Time in ms to delay (or the initial value when multiplier is used)')->end()
|
->integerNode('delay')->defaultValue(1000)->min(0)->info('Time in ms to delay (or the initial value when multiplier is used)')->end()
|
||||||
->floatNode('multiplier')->defaultValue(2)->min(1)->info('If greater than 1, delay will grow exponentially for each retry: (delay * (multiple ^ retries))')->end()
|
->floatNode('multiplier')->defaultValue(2)->min(1)->info('If greater than 1, delay will grow exponentially for each retry: (delay * (multiple ^ retries))')->end()
|
||||||
->integerNode('max_delay')->defaultValue(0)->min(0)->info('Max time in ms that a retry should ever be delayed (0 = infinite)')->end()
|
->integerNode('max_delay')->defaultValue(0)->min(0)->info('Max time in ms that a retry should ever be delayed (0 = infinite)')->end()
|
||||||
|
->floatNode('jitter')->defaultValue(0.1)->min(0)->max(1)->info('Randomness in percent (between 0 and 1)) to apply to the delay')->end()
|
||||||
->end()
|
->end()
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
@ -2075,7 +2075,8 @@ class FrameworkExtension extends Extension
|
|||||||
$retryDefinition
|
$retryDefinition
|
||||||
->replaceArgument(0, $retryOptions['delay'])
|
->replaceArgument(0, $retryOptions['delay'])
|
||||||
->replaceArgument(1, $retryOptions['multiplier'])
|
->replaceArgument(1, $retryOptions['multiplier'])
|
||||||
->replaceArgument(2, $retryOptions['max_delay']);
|
->replaceArgument(2, $retryOptions['max_delay'])
|
||||||
|
->replaceArgument(3, $retryOptions['jitter']);
|
||||||
$container->setDefinition($retryServiceId, $retryDefinition);
|
$container->setDefinition($retryServiceId, $retryDefinition);
|
||||||
|
|
||||||
$backoffReference = new Reference($retryServiceId);
|
$backoffReference = new Reference($retryServiceId);
|
||||||
|
@ -17,7 +17,7 @@ use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
|||||||
/**
|
/**
|
||||||
* A retry backOff with a constant or exponential retry delay.
|
* A retry backOff with a constant or exponential retry delay.
|
||||||
*
|
*
|
||||||
* For example, if $delayMilliseconds=10000 & $multiplier=1 (default),
|
* For example, if $delayMilliseconds=10000 & $multiplier=1,
|
||||||
* each retry will wait exactly 10 seconds.
|
* each retry will wait exactly 10 seconds.
|
||||||
*
|
*
|
||||||
* But if $delayMilliseconds=10000 & $multiplier=2:
|
* But if $delayMilliseconds=10000 & $multiplier=2:
|
||||||
@ -33,13 +33,15 @@ final class ExponentialBackOff implements RetryBackOffInterface
|
|||||||
private $delayMilliseconds;
|
private $delayMilliseconds;
|
||||||
private $multiplier;
|
private $multiplier;
|
||||||
private $maxDelayMilliseconds;
|
private $maxDelayMilliseconds;
|
||||||
|
private $jitter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $delayMilliseconds Amount of time to delay (or the initial value when multiplier is used)
|
* @param int $delayMilliseconds Amount of time to delay (or the initial value when multiplier is used)
|
||||||
* @param float $multiplier Multiplier to apply to the delay each time a retry occurs
|
* @param float $multiplier Multiplier to apply to the delay each time a retry occurs
|
||||||
* @param int $maxDelayMilliseconds Maximum delay to allow (0 means no maximum)
|
* @param int $maxDelayMilliseconds Maximum delay to allow (0 means no maximum)
|
||||||
|
* @param float $jitter Probability of randomness int delay (0 = none, 1 = 100% random)
|
||||||
*/
|
*/
|
||||||
public function __construct(int $delayMilliseconds = 1000, float $multiplier = 2.0, int $maxDelayMilliseconds = 0)
|
public function __construct(int $delayMilliseconds = 1000, float $multiplier = 2.0, int $maxDelayMilliseconds = 0, float $jitter = 0.1)
|
||||||
{
|
{
|
||||||
if ($delayMilliseconds < 0) {
|
if ($delayMilliseconds < 0) {
|
||||||
throw new InvalidArgumentException(sprintf('Delay must be greater than or equal to zero: "%s" given.', $delayMilliseconds));
|
throw new InvalidArgumentException(sprintf('Delay must be greater than or equal to zero: "%s" given.', $delayMilliseconds));
|
||||||
@ -55,11 +57,20 @@ final class ExponentialBackOff implements RetryBackOffInterface
|
|||||||
throw new InvalidArgumentException(sprintf('Max delay must be greater than or equal to zero: "%s" given.', $maxDelayMilliseconds));
|
throw new InvalidArgumentException(sprintf('Max delay must be greater than or equal to zero: "%s" given.', $maxDelayMilliseconds));
|
||||||
}
|
}
|
||||||
$this->maxDelayMilliseconds = $maxDelayMilliseconds;
|
$this->maxDelayMilliseconds = $maxDelayMilliseconds;
|
||||||
|
|
||||||
|
if ($jitter < 0 || $jitter > 1) {
|
||||||
|
throw new InvalidArgumentException(sprintf('Jitter must be between 0 and 1: "%s" given.', $jitter));
|
||||||
|
}
|
||||||
|
$this->jitter = $jitter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDelay(int $retryCount, string $requestMethod, string $requestUrl, array $requestOptions, int $responseStatusCode, array $responseHeaders, ?string $responseContent, ?TransportExceptionInterface $exception): int
|
public function getDelay(int $retryCount, string $requestMethod, string $requestUrl, array $requestOptions, int $responseStatusCode, array $responseHeaders, ?string $responseContent, ?TransportExceptionInterface $exception): int
|
||||||
{
|
{
|
||||||
$delay = $this->delayMilliseconds * $this->multiplier ** $retryCount;
|
$delay = $this->delayMilliseconds * $this->multiplier ** $retryCount;
|
||||||
|
if ($this->jitter > 0) {
|
||||||
|
$randomness = $delay * $this->jitter;
|
||||||
|
$delay = $delay + random_int(-$randomness, +$randomness);
|
||||||
|
}
|
||||||
|
|
||||||
if ($delay > $this->maxDelayMilliseconds && 0 !== $this->maxDelayMilliseconds) {
|
if ($delay > $this->maxDelayMilliseconds && 0 !== $this->maxDelayMilliseconds) {
|
||||||
return $this->maxDelayMilliseconds;
|
return $this->maxDelayMilliseconds;
|
||||||
|
@ -21,7 +21,7 @@ class ExponentialBackOffTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function testGetDelay(int $delay, int $multiplier, int $maxDelay, int $previousRetries, int $expectedDelay)
|
public function testGetDelay(int $delay, int $multiplier, int $maxDelay, int $previousRetries, int $expectedDelay)
|
||||||
{
|
{
|
||||||
$backOff = new ExponentialBackOff($delay, $multiplier, $maxDelay);
|
$backOff = new ExponentialBackOff($delay, $multiplier, $maxDelay, 0);
|
||||||
|
|
||||||
self::assertSame($expectedDelay, $backOff->getDelay($previousRetries, 'GET', 'http://example.com/', [], 200, [], null, null));
|
self::assertSame($expectedDelay, $backOff->getDelay($previousRetries, 'GET', 'http://example.com/', [], 200, [], null, null));
|
||||||
}
|
}
|
||||||
@ -50,4 +50,22 @@ class ExponentialBackOffTest extends TestCase
|
|||||||
yield [0, 2, 10000, 0, 0];
|
yield [0, 2, 10000, 0, 0];
|
||||||
yield [0, 2, 10000, 1, 0];
|
yield [0, 2, 10000, 1, 0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testJitter()
|
||||||
|
{
|
||||||
|
$backOff = new ExponentialBackOff(1000, 1, 0, 1);
|
||||||
|
$belowHalf = 0;
|
||||||
|
$aboveHalf = 0;
|
||||||
|
for ($i = 0; $i < 20; ++$i) {
|
||||||
|
$delay = $backOff->getDelay(0, 'GET', 'http://example.com/', [], 200, [], null, null);
|
||||||
|
if ($delay < 500) {
|
||||||
|
++$belowHalf;
|
||||||
|
} elseif ($delay > 1500) {
|
||||||
|
++$aboveHalf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertGreaterThanOrEqual(1, $belowHalf);
|
||||||
|
$this->assertGreaterThanOrEqual(1, $aboveHalf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user