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

Skip to content

Commit 58dd1de

Browse files
[Messenger] [Redis] Redis Sentinel support - issue #41762
1 parent 8252414 commit 58dd1de

File tree

4 files changed

+86
-7
lines changed

4 files changed

+86
-7
lines changed

src/Symfony/Component/Messenger/Bridge/Redis/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
6.1
5+
---
6+
7+
* Add support for Redis Sentinel
8+
49
6.0
510
---
611

src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/ConnectionTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ public function testLastErrorGetsCleared()
360360
$this->assertSame('xack error', $e->getMessage());
361361
}
362362

363+
363364
/**
364365
* @dataProvider provideIdPatterns
365366
*/
@@ -381,4 +382,26 @@ public function provideIdPatterns(): \Generator
381382
$redis->expects($this->atLeastOnce())->method('rawCommand')->willReturn('1');
382383
yield '100ms delay' => ['/^\w+\.\d+$/', $redis, 100];
383384
}
385+
386+
public function testInvalidSentinelMasterName()
387+
{
388+
try {
389+
Connection::fromDsn(getenv('MESSENGER_REDIS_DSN'), ['delete_after_ack' => true], null);
390+
} catch (\Exception $e) {
391+
self::markTestSkipped($e->getMessage());
392+
}
393+
394+
if (!getenv('MESSENGER_REDIS_SENTINEL_MASTER')) {
395+
self::markTestSkipped('Redis sentinel is not configured');
396+
}
397+
398+
$master = getenv('MESSENGER_REDIS_DSN');
399+
$uid = uniqid('sentinel_');
400+
401+
$exp = explode('://', $master, 2)[1];
402+
$this->expectException(\InvalidArgumentException::class);
403+
$this->expectExceptionMessage(sprintf('Failed to retrieve master information from master name "%s" and address "%s".', $uid, $exp));
404+
405+
Connection::fromDsn(sprintf('%s/messenger-clearlasterror', $master), ['delete_after_ack' => true, 'sentinel_master' => $uid], null);
406+
}
384407
}

src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisExtIntegrationTest.php

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ protected function setUp(): void
3434

3535
try {
3636
$this->redis = new \Redis();
37-
$this->connection = Connection::fromDsn(getenv('MESSENGER_REDIS_DSN'), [], $this->redis);
37+
$this->connection = Connection::fromDsn(getenv('MESSENGER_REDIS_DSN'), ['sentinel_master' => getenv('MESSENGER_REDIS_SENTINEL_MASTER')], $this->redis);
3838
$this->connection->cleanup();
3939
$this->connection->setup();
4040
} catch (\Exception $e) {
@@ -142,7 +142,7 @@ public function testConnectionSendDelayedMessagesWithSameContent()
142142
public function testConnectionBelowRedeliverTimeout()
143143
{
144144
// lower redeliver timeout and claim interval
145-
$connection = Connection::fromDsn(getenv('MESSENGER_REDIS_DSN'), [], $this->redis);
145+
$connection = Connection::fromDsn(getenv('MESSENGER_REDIS_DSN'), ['sentinel_master' => getenv('MESSENGER_REDIS_SENTINEL_MASTER')], $this->redis);
146146

147147
$connection->cleanup();
148148
$connection->setup();
@@ -170,7 +170,8 @@ public function testConnectionClaimAndRedeliver()
170170
// lower redeliver timeout and claim interval
171171
$connection = Connection::fromDsn(
172172
getenv('MESSENGER_REDIS_DSN'),
173-
['redeliver_timeout' => 0, 'claim_interval' => 500],
173+
['redeliver_timeout' => 0, 'claim_interval' => 500, 'sentinel_master' => getenv('MESSENGER_REDIS_SENTINEL_MASTER')],
174+
174175
$this->redis
175176
);
176177

@@ -214,6 +215,25 @@ public function testConnectionClaimAndRedeliver()
214215
$connection->ack($message['id']);
215216
}
216217

218+
public function testLazySentinel()
219+
{
220+
$connection = Connection::fromDsn(getenv('MESSENGER_REDIS_DSN'),
221+
['lazy' => true,
222+
'delete_after_ack' => true,
223+
'sentinel_master' => getenv('MESSENGER_REDIS_SENTINEL_MASTER'), ], $this->redis);
224+
225+
$connection->add('1', []);
226+
$this->assertNotEmpty($message = $connection->get());
227+
$this->assertSame([
228+
'message' => json_encode([
229+
'body' => '1',
230+
'headers' => [],
231+
]),
232+
], $message['data']);
233+
$connection->reject($message['id']);
234+
$connection->cleanup();
235+
}
236+
217237
public function testLazyCluster()
218238
{
219239
$this->skipIfRedisClusterUnavailable();
@@ -273,7 +293,7 @@ public function testFromDsnWithMultipleHosts()
273293
}, $hosts);
274294
$dsn = implode(',', $dsn);
275295

276-
$this->assertInstanceOf(Connection::class, Connection::fromDsn($dsn));
296+
$this->assertInstanceOf(Connection::class, Connection::fromDsn($dsn, ['sentinel_master' => getenv('MESSENGER_REDIS_SENTINEL_MASTER')]));
277297
}
278298

279299
public function testJsonError()
@@ -292,7 +312,7 @@ public function testGetNonBlocking()
292312
{
293313
$redis = new \Redis();
294314

295-
$connection = Connection::fromDsn('redis://localhost/messenger-getnonblocking', [], $redis);
315+
$connection = Connection::fromDsn('redis://localhost/messenger-getnonblocking', ['sentinel_master' => null], $redis);
296316

297317
$this->assertNull($connection->get()); // no message, should return null immediately
298318
$connection->add('1', []);
@@ -304,15 +324,16 @@ public function testGetNonBlocking()
304324
public function testGetAfterReject()
305325
{
306326
$redis = new \Redis();
307-
$connection = Connection::fromDsn('redis://localhost/messenger-rejectthenget', [], $redis);
327+
$connection = Connection::fromDsn('redis://localhost/messenger-rejectthenget', ['sentinel_master' => null], $redis);
308328

309329
$connection->add('1', []);
310330
$connection->add('2', []);
311331

312332
$failing = $connection->get();
313333
$connection->reject($failing['id']);
314334

315-
$connection = Connection::fromDsn('redis://localhost/messenger-rejectthenget', []);
335+
$connection = Connection::fromDsn('redis://localhost/messenger-rejectthenget', ['sentinel_master' => null]);
336+
316337
$this->assertNotNull($connection->get());
317338

318339
$redis->del('messenger-rejectthenget');

src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ class Connection
4141
'lazy' => false,
4242
'auth' => null,
4343
'serializer' => \Redis::SERIALIZER_PHP,
44+
'sentinel_master' => null, // String, master to look for (optional, default is NULL meaning Sentinel support is disabled)
45+
'timeout' => 0, // Float, value in seconds (optional, default is 0 meaning unlimited)
46+
'read_timeout' => 0, // Float, value in seconds (optional, default is 0 meaning unlimited)
47+
'retry_interval' => 0, // Int, value in milliseconds (optional, default is 0)
48+
'persistent_id' => null, // String, persistent connection id (optional, default is NULL meaning not persistent)
4449
];
4550

4651
private \Redis|\RedisCluster|RedisProxy|RedisClusterProxy $connection;
@@ -72,6 +77,21 @@ public function __construct(array $configuration, array $connectionCredentials =
7277
$auth = null;
7378
}
7479

80+
$sm = $redisOptions['sentinel_master'] ?? null;
81+
$sentinelMaster = (null == $sm || (\is_string($sm) && '' === $sm)) ? null : $sm;
82+
$sentinelTimeout = $redisOptions['timeout'] ?? self::DEFAULT_OPTIONS['timeout'];
83+
$sentinelReadTimeout = $redisOptions['read_timeout'] ?? self::DEFAULT_OPTIONS['read_timeout'];
84+
$sentinelRetryInterval = $redisOptions['retry_interval'] ?? self::DEFAULT_OPTIONS['retry_interval'];
85+
$sentinelPersistentId = $redisOptions['persistent_id'] ?? self::DEFAULT_OPTIONS['persistent_id'];
86+
87+
if (null !== $sentinelMaster && !class_exists(\Predis\Client::class) && !class_exists(\RedisSentinel::class)) {
88+
throw new InvalidArgumentException('Redis Sentinel support requires the "predis/predis" package or the "redis" extension v5.2 or higher.');
89+
}
90+
91+
if (null !== $sentinelMaster && $redis instanceof \RedisCluster) {
92+
throw new InvalidArgumentException('Cannot configure Redis Sentinel and Redis Cluster instance at the same time.');
93+
}
94+
7595
$lazy = $configuration['lazy'] ?? self::DEFAULT_OPTIONS['lazy'];
7696
if (\is_array($host) || $redis instanceof \RedisCluster) {
7797
$hosts = \is_string($host) ? [$host.':'.$port] : $host; // Always ensure we have an array
@@ -80,6 +100,16 @@ public function __construct(array $configuration, array $connectionCredentials =
80100
};
81101
$redis = $lazy ? new RedisClusterProxy($redis, $initializer) : $initializer($redis);
82102
} else {
103+
if (null !== $sentinelMaster) {
104+
$sentinelClient = new \RedisSentinel($host, $port, $sentinelTimeout, $sentinelPersistentId, $sentinelRetryInterval, $sentinelReadTimeout);
105+
106+
if (!$address = $sentinelClient->getMasterAddrByName($sentinelMaster)) {
107+
throw new InvalidArgumentException(sprintf('Failed to retrieve master information from master name "%s" and address "%s:%d".', $sentinelMaster, $host, $port));
108+
}
109+
110+
[$host, $port] = $address;
111+
}
112+
83113
$redis = $redis ?? new \Redis();
84114
$initializer = static function ($redis) use ($host, $port, $auth, $serializer, $dbIndex) {
85115
return self::initializeRedis($redis, $host, $port, $auth, $serializer, $dbIndex);

0 commit comments

Comments
 (0)