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
-------

80e8b21525 [Cache] Add Redis Sentinel support
This commit is contained in:
Nicolas Grekas 2019-05-22 16:28:18 +02:00
commit 63d730920b
3 changed files with 63 additions and 1 deletions

View File

@ -1,6 +1,11 @@
CHANGELOG
=========
4.4.0
-----
* added support for connecting to Redis Sentinel clusters
4.3.0
-----

View File

@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Tests\Adapter;
use Symfony\Component\Cache\Adapter\AbstractAdapter;
use Symfony\Component\Cache\Adapter\RedisAdapter;
class RedisAdapterSentinelTest extends AbstractRedisAdapterTest
{
public static function setupBeforeClass()
{
if (!class_exists('Predis\Client')) {
self::markTestSkipped('The Predis\Client class is required.');
}
if (!$hosts = getenv('REDIS_SENTINEL_HOSTS')) {
self::markTestSkipped('REDIS_SENTINEL_HOSTS env var is not defined.');
}
if (!$service = getenv('REDIS_SENTINEL_SERVICE')) {
self::markTestSkipped('REDIS_SENTINEL_SERVICE env var is not defined.');
}
self::$redis = AbstractAdapter::createConnection('redis:?host['.str_replace(' ', ']&host[', $hosts).']', ['redis_sentinel' => $service]);
}
/**
* @expectedException \Symfony\Component\Cache\Exception\InvalidArgumentException
* @expectedExceptionMessage Invalid Redis DSN: cannot use both redis_cluster and redis_sentinel at the same time
*/
public function testInvalidDSNHasBothClusterAndSentinel()
{
$dsn = 'redis:?host[redis1]&host[redis2]&host[redis3]&redis_cluster=1&redis_sentinel=mymaster';
RedisAdapter::createConnection($dsn);
}
}

View File

@ -38,6 +38,7 @@ trait RedisTrait
'tcp_keepalive' => 0,
'lazy' => null,
'redis_cluster' => false,
'redis_sentinel' => null,
'dbindex' => 0,
'failover' => 'none',
];
@ -146,9 +147,13 @@ trait RedisTrait
throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s', $dsn));
}
if (isset($params['redis_sentinel']) && !class_exists(\Predis\Client::class)) {
throw new CacheException(sprintf('Redis Sentinel support requires the "predis/predis" package: %s', $dsn));
}
$params += $query + $options + self::$defaultConnectionOptions;
if (null === $params['class'] && \extension_loaded('redis')) {
if (null === $params['class'] && !isset($params['redis_sentinel']) && \extension_loaded('redis')) {
$class = $params['redis_cluster'] ? \RedisCluster::class : (1 < \count($hosts) ? \RedisArray::class : \Redis::class);
} else {
$class = null === $params['class'] ? \Predis\Client::class : $params['class'];
@ -246,6 +251,12 @@ trait RedisTrait
} elseif (is_a($class, \Predis\Client::class, true)) {
if ($params['redis_cluster']) {
$params['cluster'] = 'redis';
if (isset($params['redis_sentinel'])) {
throw new InvalidArgumentException(sprintf('Cannot use both "redis_cluster" and "redis_sentinel" at the same time: %s', $dsn));
}
} elseif (isset($params['redis_sentinel'])) {
$params['replication'] = 'sentinel';
$params['service'] = $params['redis_sentinel'];
}
$params += ['parameters' => []];
$params['parameters'] += [
@ -268,6 +279,9 @@ trait RedisTrait
}
$redis = new $class($hosts, array_diff_key($params, self::$defaultConnectionOptions));
if (isset($params['redis_sentinel'])) {
$redis->getConnection()->setSentinelTimeout($params['timeout']);
}
} elseif (class_exists($class, false)) {
throw new InvalidArgumentException(sprintf('"%s" is not a subclass of "Redis", "RedisArray", "RedisCluster" nor "Predis\Client".', $class));
} else {