8000 [Messenger] [Redis] Redis Sentinel support - issue #41762 · symfony/symfony@8230577 · GitHub
[go: up one dir, main page]

Skip to content

Commit 8230577

Browse files
[Messenger] [Redis] Redis Sentinel support - issue #41762
1 parent 12130f4 commit 8230577

File tree

4 files changed

+91
-8
lines changed

4 files changed

+91
-8
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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,28 @@ public function provideIdPatterns(): \Generator
379379

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

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: 35 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+
'sentinel_timeout' => 0, // Float, value in seconds (optional, default is 0 meaning unlimited)
46+
'sentinel_read_timeout' => 0, // Float, value in seconds (optional, default is 0 meaning unlimited)
47+
'sentinel_retry_interval' => 0, // Int, value in milliseconds (optional, default is 0)
48+
'sentinel_persistent_id' => null, // String, persistent connection id (optional, default is NULL meaning not persistent)
4449
];
4550

4651
private \Redis|\RedisCluster|RedisProxy|RedisClusterProxy $connection;
@@ -56,6 +61,11 @@ class Connection
5661
private bool $deleteAfterAck;
5762
private bool $deleteAfterReject;
5863
private bool $couldHavePendingMessages = true;
64+
private $sentinelMaster;
65+
private $sentinelTimeout;
66+
private $sentinelReadTimeout;
67+
private $sentinelRetryInterval;
68+
private $sentinelPersistentId;
5969

6070
public function __construct(array $configuration, array $connectionCredentials = [], array $redisOptions = [], \Redis|\RedisCluster $redis = null)
6171
{
@@ -72,6 +82,21 @@ public function __construct(array $configuration, array $connectionCredentials =
7282
$auth = null;
7383
}
7484

85+
$sm = $redisOptions['sentinel_master'] ?? null;
86+
$this->sentinelMaster = (null == $sm || (\is_string($sm) && '' === $sm)) ? null : $sm;
87+
$this->sentinelTimeout = $redisOptions['sentinel_timeout'] ?? self::DEFAULT_OPTIONS['sentinel_timeout'];
88+
$this->sentinelReadTimeout = $redisOptions['sentinel_read_timeout'] ?? self::DEFAULT_OPTIONS['sentinel_read_timeout'];
89+
$this->sentinelRetryInterval = $redisOptions['sentinel_retry_interval'] ?? self::DEFAULT_OPTIONS['sentinel_retry_interval'];
90+
$this->sentinelPersistentId = $redisOptions['sentinel_persistent_id'] ?? self::DEFAULT_OPTIONS['sentinel_persistent_id'];
91+
92+
if (null !== $this->sentinelMaster && !class_exists(\Predis\Client::class) && !class_exists(\RedisSentinel::class)) {
93+
throw new InvalidArgumentException('Redis Sentinel support requires the "predis/predis" package or the "redis" extension v5.2 or higher.');
94+
}
95+
96+
if (null !== $this->sentinelMaster && $redis instanceof \RedisCluster) {
97+
throw new InvalidArgumentException('Cannot configure Redis Sentinel and Redis Cluster instance at the same time.');
98+
}
99+
75100
$lazy = $configuration['lazy'] ?? self::DEFAULT_OPTIONS['lazy'];
76101
if (\is_array($host) || $redis instanceof \RedisCluster) {
77102
$hosts = \is_string($host) ? [$host.':'.$port] : $host; // Always ensure we have an array
@@ -80,6 +105,16 @@ public function __construct(array $configuration, array $connectionCredentials =
80105
};
81106
$redis = $lazy ? new RedisClusterProxy($redis, $initializer) : $initializer($redis);
82107
} else {
108+
if (null !== $this->sentinelMaster) {
109+
$sentinelClient = new \RedisSentinel($host, $port, $this->sentinelTimeout, $this->sentinelPersistentId, $this->sentinelRetryInterval, $this->sentinelReadTimeout);
110+
111+
if (!$address = $sentinelClient->getMasterAddrByName($this->sentinelMaster)) {
112+
throw new InvalidArgumentException(sprintf('Failed to retrieve master information from master name "%s" and address "%s:%d".', $this->sentinelMaster, $host, $port));
113+
}
114+
115+
[$host, $port] = $address;
116+
}
117+
83118
$redis = $redis ?? new \Redis();
84119
$initializer = static function ($redis) use ($host, $port, $auth, $serializer, $dbIndex) {
85120
return self::initializeRedis($redis, $host, $port, $auth, $serializer, $dbIndex);

0 commit comments

Comments
 (0)
0