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

Skip to content

Commit 2ac1f39

Browse files
[Messenger] [Redis] Redis Sentinel support - issue #41762
1 parent f32af46 commit 2ac1f39

File tree

4 files changed

+81
-11
lines changed

4 files changed

+81
-11
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: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,4 +387,36 @@ public function testLastErrorGetsCleared()
387387

388388
$this->assertSame('xack error', $e->getMessage());
389389
}
390+
391+
public function testSentinelEnabledWithRedisInstance()
392+
{
393+
$this->expectException(\InvalidArgumentException::class);
394+
$this->expectExceptionMessage('Cannot configure Redis Sentinel and Redis instance at the same time');
395+
396+
$redis = $this->createMock(\Redis::class);
397+
Connection::fromDsn('redis://localhost/messenger-clearlasterror', ['delete_after_ack' => true, 'sentinel_master' => 'any'], $redis);
398+
}
399+
400+
public function testSentinelEnabledWithRedisClusterInstance()
401+
{
402+
$this->expectException(\InvalidArgumentException::class);
403+
$this->expectExceptionMessage('Cannot configure Redis Sentinel and Redis Cluster instance at the same time');
404+
405+
$redis = $this->createMock(\RedisCluster::class);
406+
Connection::fromDsn('redis://localhost/messenger-clearlasterror', ['delete_after_ack' => true, 'sentinel_master' => 'any'], $redis);
407+
}
408+
409+
public function testInvalidSentinelMasterName()
410+
{
411+
$master = getenv('MESSENGER_REDIS_SENTINEL_MASTER');
412+
if (!(bool) $master) {
413+
self::markTestSkipped('skip because redis sentinel is not configured');
414+
}
415+
416+
$uid = uniqid('sentinel_');
417+
$this->expectException(\InvalidArgumentException::class);
418+
$this->expectExceptionMessage(sprintf('Failed to retrieve master information from master name "%s" and address "127.0.0.1:5000"', $uid));
419+
420+
Connection::fromDsn('redis://127.0.0.1:5000/messenger-clearlasterror', ['delete_after_ack' => true, 'sentinel_master' => $uid], null);
421+
}
390422
}

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

Lines changed: 10 additions & 11 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) {
@@ -110,8 +110,7 @@ public function testConnectionSendDelayedMessagesWithSameContent()
110110
public function testConnectionBelowRedeliverTimeout()
111111
{
112112
// lower redeliver timeout and claim interval
113-
$connection = Connection::fromDsn(getenv('MESSENGER_REDIS_DSN'), ['delete_after_ack' => true], $this->redis);
114-
113+
$connection = Connection::fromDsn(getenv('MESSENGER_REDIS_DSN'), ['delete_after_ack' => true, 'sentinel_master' => getenv('MESSENGER_REDIS_SENTINEL_MASTER')], $this->redis);
115114
$connection->cleanup();
116115
$connection->setup();
117116

@@ -138,7 +137,7 @@ public function testConnectionClaimAndRedeliver()
138137
// lower redeliver timeout and claim interval
139138
$connection = Connection::fromDsn(
140139
getenv('MESSENGER_REDIS_DSN'),
141-
['redeliver_timeout' => 0, 'claim_interval' => 500, 'delete_after_ack' => true],
140+
['redeliver_timeout' => 0, 'claim_interval' => 500, 'delete_after_ack' => true, 'sentinel_master' => getenv('MESSENGER_REDIS_SENTINEL_MASTER')],
142141
$this->redis
143142
);
144143

@@ -194,7 +193,7 @@ public function testLazyCluster()
194193
public function testLazy()
195194
{
196195
$redis = new \Redis();
197-
$connection = Connection::fromDsn('redis://localhost/messenger-lazy?lazy=1', ['delete_after_ack' => true], $redis);
196+
$connection = Connection::fromDsn('redis://localhost/messenger-lazy?lazy=1', ['delete_after_ack' => true, 'sentinel_master' => getenv('MESSENGER_REDIS_SENTINEL_MASTER')], $redis);
198197

199198
$connection->add('1', []);
200199
$this->assertNotEmpty($message = $connection->get());
@@ -207,7 +206,7 @@ public function testDbIndex()
207206
{
208207
$redis = new \Redis();
209208

210-
Connection::fromDsn('redis://localhost/queue?dbindex=2', ['delete_after_ack' => true], $redis);
209+
Connection::fromDsn('redis://localhost/queue?dbindex=2', ['delete_after_ack' => true, 'sentinel_master' => getenv('MESSENGER_REDIS_SENTINEL_MASTER')], $redis);
211210

212211
$this->assertSame(2, $redis->getDbNum());
213212
}
@@ -223,13 +222,13 @@ public function testFromDsnWithMultipleHosts()
223222
}, $hosts);
224223
$dsn = implode(',', $dsn);
225224

226-
$this->assertInstanceOf(Connection::class, Connection::fromDsn($dsn, ['delete_after_ack' => true]));
225+
$this->assertInstanceOf(Connection::class, Connection::fromDsn($dsn, ['delete_after_ack' => true, 'sentinel_master' => getenv('MESSENGER_REDIS_SENTINEL_MASTER')]));
227226
}
228227

229228
public function testJsonError()
230229
{
231230
$redis = new \Redis();
232-
$connection = Connection::fromDsn('redis://localhost/json-error', ['delete_after_ack' => true], $redis);
231+
$connection = Connection::fromDsn('redis://localhost/json-error', ['delete_after_ack' => true, 'sentinel_master' => getenv('MESSENGER_REDIS_SENTINEL_MASTER')], $redis);
233232
try {
234233
$connection->add("\xB1\x31", []);
235234
} catch (TransportException $e) {
@@ -242,7 +241,7 @@ public function testGetNonBlocking()
242241
{
243242
$redis = new \Redis();
244243

245-
$connection = Connection::fromDsn('redis://localhost/messenger-getnonblocking', ['delete_after_ack' => true], $redis);
244+
$connection = Connection::fromDsn('redis://localhost/messenger-getnonblocking', ['delete_after_ack' => true, 'sentinel_master' => getenv('MESSENGER_REDIS_SENTINEL_MASTER')], $redis);
246245

247246
$this->assertNull($connection->get()); // no message, should return null immediately
248247
$connection->add('1', []);
@@ -254,15 +253,15 @@ public function testGetNonBlocking()
254253
public function testGetAfterReject()
255254
{
256255
$redis = new \Redis();
257-
$connection = Connection::fromDsn('redis://localhost/messenger-rejectthenget', ['delete_after_ack' => true], $redis);
256+
$connection = Connection::fromDsn('redis://localhost/messenger-rejectthenget', ['delete_after_ack' => true, 'sentinel_master' => getenv('MESSENGER_REDIS_SENTINEL_MASTER')], $redis);
258257

259258
$connection->add('1', []);
260259
$connection->add('2', []);
261260

262261
$failing = $connection->get();
263262
$connection->reject($failing['id']);
264263

265-
$connection = Connection::fromDsn('redis://localhost/messenger-rejectthenget', ['delete_after_ack' => true]);
264+
$connection = Connection::fromDsn('redis://localhost/messenger-rejectthenget', ['delete_after_ack' => true, 'sentinel_master' => getenv('MESSENGER_REDIS_SENTINEL_MASTER')]);
266265
$this->assertNotNull($connection->get());
267266

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

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ class Connection
4242 10000
'lazy' => false,
4343
'auth' => null,
4444
'serializer' => \Redis::SERIALIZER_PHP,
45+
'sentinel_master' => null,
46+
'sentinel_timeout' => 1000, // ms
47+
'sentinel_read_timeout' => 1000, // ms
48+
'sentinel_retry_interval' => 1000, // ms
49+
'sentinel_persistent_id' => null,
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,24 @@ public function __construct(array $configuration, array $connectionCredentials =
7686
$auth = null;
7787
}
7888

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

0 commit comments

Comments
 (0)
0