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

Skip to content

Commit 9c02ef3

Browse files
[Messenger] [Redis] Redis Sentinel support - issue #41762
1 parent a0d778e commit 9c02ef3

File tree

4 files changed

+84
-8
lines changed

4 files changed

+84
-8
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

77
* Deprecate not setting the `delete_after_ack` config option (or DSN parameter),
88
its default value will change to `true` in 6.0
9+
* Add support for Redis Sentinel
910

1011
5.3
1112
---

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,4 +411,26 @@ public function testLastErrorGetsCleared()
411411

412412
$this->assertSame('xack error', $e->getMessage());
413413
}
414+
415+
public function testInvalidSentinelMasterName()
416+
{
417+
try {
418+
Connection::fromDsn(getenv('MESSENGER_REDIS_DSN'), ['delete_after_ack' => true], null);
419+
} catch (\Exception $e) {
420+
self::markTestSkipped($e->getMessage());
421+
}
422+
423+
if (!getenv('MESSENGER_REDIS_SENTINEL_MASTER')) {
424+
self::markTestSkipped('Redis sentinel is not configured');
425+
}
426+
427+
$master = getenv('MESSENGER_REDIS_DSN');
428+
$uid = uniqid('sentinel_');
429+
430+
$exp = explode('://', $master, 2)[1];
431+
$this->expectException(\InvalidArgumentException::class);
432+
$this->expectExceptionMessage(sprintf('Failed to retrieve master information from master name "%s" and address "%s".', $uid, $exp));
433+
434+
Connection::fromDsn(sprintf('%s/messenger-clearlasterror', $master), ['delete_after_ack' => true, 'sentinel_master' => $uid], null);
435+
}
414436
}

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

Lines changed: 26 additions & 8 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'), ['delete_after_ack' => true], $this->redis);
37+
$this->connection = Connection::fromDsn(getenv('MESSENGER_REDIS_DSN'), ['delete_after_ack' => true, 'sentinel_master' => getenv('MESSENGER_REDIS_SENTINEL_MASTER')], $this->redis);
3838
$this->connection->cleanup();
3939
$this->connection->setup();
4040
} catch (\Exception $e) {
@@ -142,8 +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'), ['delete_after_ack' => true], $this->redis);
146-
145+
$connection = Connection::fromDsn(getenv('MESSENGER_REDIS_DSN'), ['delete_after_ack' => true, 'sentinel_master' => getenv('MESSENGER_REDIS_SENTINEL_MASTER')], $this->redis);
147146
$connection->cleanup();
148147
$connection->setup();
149148

@@ -170,7 +169,7 @@ public function testConnectionClaimAndRedeliver()
170169
// lower redeliver timeout and claim interval
171170
$connection = Connection::fromDsn(
172171
getenv('MESSENGER_REDIS_DSN'),
173-
['redeliver_timeout' => 0, 'claim_interval' => 500, 'delete_after_ack' => true],
172+
['redeliver_timeout' => 0, 'claim_interval' => 500, 'delete_after_ack' => true, 'sentinel_master' => getenv('MESSENGER_REDIS_SENTINEL_MASTER')],
174173
$this->redis
175174
);
176175

@@ -214,6 +213,25 @@ public function testConnectionClaimAndRedeliver()
214213
$connection->ack($message['id']);
215214
}
216215

216+
public function testLazySentinel()
217+
{
218+
$connection = Connection::fromDsn(getenv('MESSENGER_REDIS_DSN'),
219+
['lazy' => true,
220+
'delete_after_ack' => true,
221+
'sentinel_master' => getenv('MESSENGER_REDIS_SENTINEL_MASTER'), ], $this->redis);
222+
223+
$connection->add('1', []);
224+
$this->assertNotEmpty($message = $connection->get());
225+
$this->assertSame([
226+
'message' => json_encode([
227+
'body' => '1',
228+
'headers' => [],
229+
]),
230+
], $message['data']);
231+
$connection->reject($message['id']);
232+
$connection->cleanup();
233+
}
234+
217235
public function testLazyCluster()
218236
{
219237
$this->skipIfRedisClusterUnavailable();
@@ -273,7 +291,7 @@ public function testFromDsnWithMultipleHosts()
273291
}, $hosts);
274292
$dsn = implode(',', $dsn);
275293

276-
$this->assertInstanceOf(Connection::class, Connection::fromDsn($dsn, ['delete_after_ack' => true]));
294+
$this->assertInstanceOf(Connection::class, Connection::fromDsn($dsn, ['delete_after_ack' => true, 'sentinel_master' => getenv('MESSENGER_REDIS_SENTINEL_MASTER')]));
277295
}
278296

279297
public function testJsonError()
@@ -292,7 +310,7 @@ public function testGetNonBlocking()
292310
{
293311
$redis = new \Redis();
294312

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

297315
$this->assertNull($connection->get()); // no message, should return null immediately
298316
$connection->add('1', []);
@@ -304,15 +322,15 @@ public function testGetNonBlocking()
304322
public function testGetAfterReject()
305323
{
306324
$redis = new \Redis();
307-
$connection = Connection::fromDsn('redis://localhost/messenger-rejectthenget', ['delete_after_ack' => true], $redis);
325+
$connection = Connection::fromDsn('redis://localhost/messenger-rejectthenget', ['delete_after_ack' => true, 'sentinel_master' => null], $redis);
308326

309327
$connection->add('1', []);
310328
$connection->add('2', []);
311329

312330
$failing = $connection->get();
313331
$connection->reject($failing['id']);
314332

315-
$connection = Connection::fromDsn('redis://localhost/messenger-rejectthenget', ['delete_after_ack' => true]);
333+
$connection = Connection::fromDsn('redis://localhost/messenger-rejectthenget', ['delete_after_ack' => true, 'sentinel_master' => null]);
316334
$this->assertNotNull($connection->get());
317335

318336
$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
@@ -42,6 +42,11 @@ class Connection
4242
'lazy' => false,
4343
'auth' => null,
4444
'serializer' => \Redis::SERIALIZER_PHP,
45+
'sentinel_master' => null, // String, master to look for (optional, default is NULL meaning Sentinel support is disabled)
46+
'sentinel_timeout' => 0, // Float, value in seconds (optional, default is 0 meaning unlimited)
47+
'sentinel_read_timeout' => 0, // Float, value in seconds (optional, default is 0 meaning unlimited)
48+
'sentinel_retry_interval' => 0, // Int, value in milliseconds (optional, default is 0)
49+
'sentinel_persistent_id' => null, // String, persistent connection id (optional, default is NULL meaning not persistent)
4550
];
4651

4752
private $connection;
@@ -57,6 +62,11 @@ class Connection
5762
private $deleteAfterAck;
5863
private $deleteAfterReject;
5964
private $couldHavePendingMessages = true;
65+
private $sentinelMaster;
66+
private $sentinelTimeout;
67+
private $sentinelReadTimeout;
68+
private $sentinelRetryInterval;
69+
private $sentinelPersistentId;
6070

6171
/**
6272
* @param \Redis|\RedisCluster|null $redis
@@ -76,6 +86,21 @@ public function __construct(array $configuration, array $connectionCredentials =
7686
$auth = null;
7787
}
7888

89+
$sm = $redisOptions['sentinel_master'] ?? null;
90+
$this->sentinelMaster = (null == $sm || (\is_string($sm) && 0 === \strlen($sm))) ? null : $sm;
91+
$this->sentinelTimeout = $redisOptions['sentinel_timeout'] ?? self::DEFAULT_OPTIONS['sentinel_timeout'];
92+
$this->sentinelReadTimeout = $redisOptions['sentinel_read_timeout'] ?? self::DEFAULT_OPTIONS['sentinel_read_timeout'];
93+
$this->sentinelRetryInterval = $redisOptions['sentinel_retry_interval'] ?? self::DEFAULT_OPTIONS['sentinel_retry_interval'];
94+
$this->sentinelPersistentId = $redisOptions['sentinel_persistent_id'] ?? self::DEFAULT_OPTIONS['sentinel_persistent_id'];
95+
96+
if (null !== $this->sentinelMaster && !class_exists(\Predis\Client::class) && !class_exists(\RedisSentinel::class)) {
97+
throw new InvalidArgumentException('Redis Sentinel support requires the "predis/predis" package or the "redis" extension v5.2 or higher.');
98+
}
99+
100+
if (null !== $this->sentinelMaster && $redis instanceof \RedisCluster) {
101+
throw new InvalidArgumentException('Cannot configure Redis Sentinel and Redis Cluster instance at the same time.');
102+
}
103+
79104
$lazy = $configuration['lazy'] ?? self::DEFAULT_OPTIONS['lazy'];
80105
if (\is_array($host) || $redis instanceof \RedisCluster) {
81106
$hosts = \is_string($host) ? [$host.':'.$port] : $host; // Always ensure we have an array
@@ -84,6 +109,16 @@ public function __construct(array $configuration, array $connectionCredentials =
84109
};
85110
$redis = $lazy ? new RedisClusterProxy($redis, $initializer) : $initializer($redis);
86111
} else {
112+
if (null !== $this->sentinelMaster) {
113+
$sentinelClient = new \RedisSentinel($host, $port, $this->sentinelTimeout, $this->sentinelPersistentId, $this->sentinelRetryInterval, $this->sentinelReadTimeout);
114+
115+
if (!$address = $sentinelClient->getMasterAddrByName($this->sentinelMaster)) {
116+
throw new InvalidArgumentException(sprintf('Failed to retrieve master information from master name "%s" and address "%s:%d".', $this->sentinelMaster, $host, $port));
117+
}
118+
119+
[$host, $port] = $address;
120+
}
121+
87122
$redis = $redis ?? new \Redis();
88123
$initializer = static function ($redis) use ($host, $port, $auth, $serializer, $dbIndex) {
89124
return self::initializeRedis($redis, $host, $port, $auth, $serializer, $dbIndex);

0 commit comments

Comments
 (0)