Skip to content

Commit 63d7309

Browse files
feature #31437 [Cache] Add Redis Sentinel support (StephenClouse)
This PR was squashed before being merged into the 4.4-dev branch (closes #31437). Discussion ---------- [Cache] Add Redis Sentinel support | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | | License | MIT | Doc PR | symfony/symfony-docs#11545 This change adds support for Redis Sentinel clusters to the Cache component Redis adapter. The DSN format is syntactically equivalent to cluster support, but adds a new parameter `redis_sentinel` that should be set to the sentinel service name. This support requires the use of predis as the underlying connection library. The redis extension does not support sentinel at this time. Commits ------- 80e8b21 [Cache] Add Redis Sentinel support
2 parents 5239873 + 80e8b21 commit 63d7309

File tree

3 files changed

+63
-1
lines changed

3 files changed

+63
-1
lines changed

src/Symfony/Component/Cache/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+
4.4.0
5+
-----
6+
7+
* added support for connecting to Redis Sentinel clusters
8+
49
4.3.0
510
-----
611

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Cache\Tests\Adapter;
13+
14+
use Symfony\Component\Cache\Adapter\AbstractAdapter;
15+
use Symfony\Component\Cache\Adapter\RedisAdapter;
16+
17+
class RedisAdapterSentinelTest extends AbstractRedisAdapterTest
18+
{
19+
public static function setupBeforeClass()
20+
{
21+
if (!class_exists('Predis\Client')) {
22+
self::markTestSkipped('The Predis\Client class is required.');
23+
}
24+
if (!$hosts = getenv('REDIS_SENTINEL_HOSTS')) {
25+
self::markTestSkipped('REDIS_SENTINEL_HOSTS env var is not defined.');
26+
}
27+
if (!$service = getenv('REDIS_SENTINEL_SERVICE')) {
28+
self::markTestSkipped('REDIS_SENTINEL_SERVICE env var is not defined.');
29+
}
30+
31+
self::$redis = AbstractAdapter::createConnection('redis:?host['.str_replace(' ', ']&host[', $hosts).']', ['redis_sentinel' => $service]);
32+
}
33+
34+
/**
35+
* @expectedException \Symfony\Component\Cache\Exception\InvalidArgumentException
36+
* @expectedExceptionMessage Invalid Redis DSN: cannot use both redis_cluster and redis_sentinel at the same time
37+
*/
38+
public function testInvalidDSNHasBothClusterAndSentinel()
39+
{
40+
$dsn = 'redis:?host[redis1]&host[redis2]&host[redis3]&redis_cluster=1&redis_sentinel=mymaster';
41+
RedisAdapter::createConnection($dsn);
42+
}
43+
}

src/Symfony/Component/Cache/Traits/RedisTrait.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ trait RedisTrait
3838
'tcp_keepalive' => 0,
3939
'lazy' => null,
4040
'redis_cluster' => false,
41+
'redis_sentinel' => null,
4142
'dbindex' => 0,
4243
'failover' => 'none',
4344
];
@@ -146,9 +147,13 @@ public static function createConnection($dsn, array $options = [])
146147
throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s', $dsn));
147148
}
148149

150+
if (isset($params['redis_sentinel']) && !class_exists(\Predis\Client::class)) {
151+
throw new CacheException(sprintf('Redis Sentinel support requires the "predis/predis" package: %s', $dsn));
152+
}
153+
149154
$params += $query + $options + self::$defaultConnectionOptions;
150155

151-
if (null === $params['class'] && \extension_loaded('redis')) {
156+
if (null === $params['class'] && !isset($params['redis_sentinel']) && \extension_loaded('redis')) {
152157
$class = $params['redis_cluster'] ? \RedisCluster::class : (1 < \count($hosts) ? \RedisArray::class : \Redis::class);
153158
} else {
154159
$class = null === $params['class'] ? \Predis\Client::class : $params['class'];
@@ -246,6 +251,12 @@ public static function createConnection($dsn, array $options = [])
246251
} elseif (is_a($class, \Predis\Client::class, true)) {
247252
if ($params['redis_cluster']) {
248253
$params['cluster'] = 'redis';
254+
if (isset($params['redis_sentinel'])) {
255+
throw new InvalidArgumentException(sprintf('Cannot use both "redis_cluster" and "redis_sentinel" at the same time: %s', $dsn));
256+
}
257+
} elseif (isset($params['redis_sentinel'])) {
258+
$params['replication'] = 'sentinel';
259+
$params['service'] = $params['redis_sentinel'];
249260
}
250261
$params += ['parameters' => []];
251262
$params['parameters'] += [
@@ -268,6 +279,9 @@ public static function createConnection($dsn, array $options = [])
268279
}
269280

270281
$redis = new $class($hosts, array_diff_key($params, self::$defaultConnectionOptions));
282+
if (isset($params['redis_sentinel'])) {
283+
$redis->getConnection()->setSentinelTimeout($params['timeout']);
284+
}
271285
} elseif (class_exists($class, false)) {
272286
throw new InvalidArgumentException(sprintf('"%s" is not a subclass of "Redis", "RedisArray", "RedisCluster" nor "Predis\Client".', $class));
273287
} else {

0 commit comments

Comments
 (0)