Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 96e3c84

Browse files
committed
[Messenger] Add jitter parameter to MultiplierRetryStrategy
1 parent ccbdc1a commit 96e3c84

File tree

6 files changed

+54
-3
lines changed

6 files changed

+54
-3
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

+1
Original file line numberDiff line numberDiff line change
@@ -1586,6 +1586,7 @@ function ($a) {
15861586
->integerNode('delay')->defaultValue(1000)->min(0)->info('Time in ms to delay (or the initial value when multiplier is used)')->end()
15871587
->floatNode('multiplier')->defaultValue(2)->min(1)->info('If greater than 1, delay will grow exponentially for each retry: this delay = (delay * (multiple ^ retries))')->end()
15881588
->integerNode('max_delay')->defaultValue(0)->min(0)->info('Max time in ms that a retry should ever be delayed (0 = infinite)')->end()
1589+
->floatNode('jitter')->defaultValue(0.1)->min(0)->max(1)->info('Randomness in percent (between 0 and 1) to apply to the delay')->end()
15891590
->end()
15901591
->end()
15911592
->scalarNode('rate_limiter')

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -2195,7 +2195,8 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder
21952195
->replaceArgument(0, $transport['retry_strategy']['max_retries'])
21962196
->replaceArgument(1, $transport['retry_strategy']['delay'])
21972197
->replaceArgument(2, $transport['retry_strategy']['multiplier'])
2198-
->replaceArgument(3, $transport['retry_strategy']['max_delay']);
2198+
->replaceArgument(3, $transport['retry_strategy']['max_delay'])
2199+
->replaceArgument(4, $transport['retry_strategy']['jitter']);
21992200
$container->setDefinition($retryServiceId, $retryDefinition);
22002201

22012202
$transportRetryReferences[$name] = new Reference($retryServiceId);

src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php

+1
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@
160160
abstract_arg('delay ms'),
161161
abstract_arg('multiplier'),
162162
abstract_arg('max delay ms'),
163+
abstract_arg('jitter'),
163164
])
164165

165166
// rate limiter

src/Symfony/Component/Messenger/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CHANGELOG
77
* Add option `redis_sentinel` as an alias for `sentinel_master`
88
* Add `--all` option to the `messenger:consume` command
99
* Make `#[AsMessageHandler]` final
10+
* Add parameter `$jitter` to `MultiplierRetryStrategy` in order to randomize delay and prevent the thundering herd effect.
1011

1112
7.0
1213
---

src/Symfony/Component/Messenger/Retry/MultiplierRetryStrategy.php

+13-1
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,16 @@ class MultiplierRetryStrategy implements RetryStrategyInterface
3636
private int $delayMilliseconds;
3737
private float $multiplier;
3838
private int $maxDelayMilliseconds;
39+
private float $jitter;
3940

4041
/**
4142
* @param int $maxRetries The maximum number of times to retry
4243
* @param int $delayMilliseconds Amount of time to delay (or the initial value when multiplier is used)
4344
* @param float $multiplier Multiplier to apply to the delay each time a retry occurs
4445
* @param int $maxDelayMilliseconds Maximum delay to allow (0 means no maximum)
46+
* @param float $jitter Probability of randomness int delay (0 = none, 1 = 100% random)
4547
*/
46-
public function __construct(int $maxRetries = 3, int $delayMilliseconds = 1000, float $multiplier = 1, int $maxDelayMilliseconds = 0)
48+
public function __construct(int $maxRetries = 3, int $delayMilliseconds = 1000, float $multiplier = 1, int $maxDelayMilliseconds = 0, float $jitter = 0.1)
4749
{
4850
$this->maxRetries = $maxRetries;
4951

@@ -61,6 +63,11 @@ public function __construct(int $maxRetries = 3, int $delayMilliseconds = 1000,
6163
throw new InvalidArgumentException(sprintf('Max delay must be greater than or equal to zero: "%s" given.', $maxDelayMilliseconds));
6264
}
6365
$this->maxDelayMilliseconds = $maxDelayMilliseconds;
66+
67+
if ($jitter < 0 || $jitter > 1) {
68+
throw new InvalidArgumentException(sprintf('Jitter must be between 0 and 1: "%s" given.', $jitter));
69+
}
70+
$this->jitter = $jitter;
6471
}
6572

6673
/**
@@ -82,6 +89,11 @@ public function getWaitingTime(Envelope $message, \Throwable $throwable = null):
8289

8390
$delay = $this->delayMilliseconds * $this->multiplier ** $retries;
8491

92+
if ($this->jitter > 0) {
93+
$randomness = (int) ($delay * $this->jitter);
94+
$delay += random_int(-$randomness, +$randomness);
95+
}
96+
8597
if ($delay > $this->maxDelayMilliseconds && 0 !== $this->maxDelayMilliseconds) {
8698
return $this->maxDelayMilliseconds;
8799
}

src/Symfony/Component/Messenger/Tests/Retry/MultiplierRetryStrategyTest.php

+36-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public function testIsRetryableWithNoStamp()
5454
*/
5555
public function testGetWaitTime(int $delay, float $multiplier, int $maxDelay, int $previousRetries, int $expectedDelay)
5656
{
57-
$strategy = new MultiplierRetryStrategy(10, $delay, $multiplier, $maxDelay);
57+
$strategy = new MultiplierRetryStrategy(10, $delay, $multiplier, $maxDelay, 0);
5858
$envelope = new Envelope(new \stdClass(), [new RedeliveryStamp($previousRetries)]);
5959

6060
$this->assertSame($expectedDelay, $strategy->getWaitingTime($envelope));
@@ -89,4 +89,39 @@ public static function getWaitTimeTests(): iterable
8989
yield [1000, 1.5555, 5000, 1, 1556];
9090
yield [1000, 1.5555, 5000, 2, 2420];
9191
}
92+
93+
/**
94+
* @dataProvider getJitterTest
95+
*/
96+
public function testJitter(float $jitter, int $maxMin, int $maxMax)
97+
{
98+
$strategy = new MultiplierRetryStrategy(3, 1000, 1, 0, $jitter);
99+
$envelope = new Envelope(new \stdClass());
100+
101+
$min = 1000;
102+
$max = 1000;
103+
for ($i = 0; $i < 100; ++$i) {
104+
$delay = $strategy->getWaitingTime($envelope);
105+
$min = min($min, $delay);
106+
$max = max($max, $delay);
107+
}
108+
109+
$this->assertGreaterThanOrEqual($maxMin, $min);
110+
$this->assertLessThanOrEqual($maxMax, $max);
111+
}
112+
113+
public static function getJitterTest(): iterable
114+
{
115+
yield [1.0, 0, 2000];
116+
yield [0.9, 100, 1900];
117+
yield [0.8, 200, 1800];
118+
yield [0.7, 300, 1700];
119+
yield [0.6, 400, 1600];
120+
yield [0.5, 500, 1500];
121+
yield [0.4, 600, 1400];
122+
yield [0.3, 700, 1300];
123+
yield [0.2, 800, 1200];
124+
yield [0.1, 900, 1100];
125+
yield [0.0, 1000, 1000];
126+
}
92127
}

0 commit comments

Comments
 (0)