Description
Symfony version(s) affected
5.4
Description
Symfony 4.4 introduced Redis Sentinel support for the cache component which depended on the predis/predis library. Since Symfony 5.4, if the redis extension is installed, redis sentinel is only configured correctly by the createConnection function if a single sentinel host is provided.
Background
Redis Sentinel is a high availability solution for Redis where by one or more Redis Sentinel instances monitor multiple replicated redis instances and assign one a "master". An application can then query the redis sentinel to fetch the address of the current redis master. If the redis master goes down then the redis sentinels vote on a new instance to be the redis master therefor ensuring automatic redundancy.
This functionality is documented here. In Symfony 5.4 support for the RedisSentinel class was introduced which is part of the redis php extension.
Problem
The problem is that symfony/cache/Traits/RedisTrait.php line 181 (5.4 branch) determines that by default if the redis extension is installed we should use the redis extension classes \Redis or \RedisArray instead of \Predis\Client using \RedisArray whenever multiple hosts are provided.
if (null === $params['class'] && \extension_loaded('redis')) {
$class = $params['redis_cluster'] ? \RedisCluster::class : (1 < \count($hosts) ? \RedisArray::class : \Redis::class);
} else {
$class = $params['class'] ?? \Predis\Client::class;
if (isset($params['redis_sentinel']) && !is_a($class, \Predis\Client::class, true) && !class_exists(\RedisSentinel::class)) {
throw new CacheException(sprintf('Cannot use Redis Sentinel: class "%s" does not extend "Predis\Client" and ext-redis >= 5.2 not found: "%s".', $class, $dsn));
}
}
There is then some additional logic in the following if block on line 191 which gets the address of the master but only if the class is \Redis (when there is only one host). If multiple hosts were provided then createConnection will return an instance of \RedisArray which does not know how to use sentinel to fetch the address of the redis master.
This means that since Symfony 5.4 the configuration documented in the symfony docs for redis sentinel won't acrtually work and won't be able to connect to the redis master.
Using redis sentinel with only a single sentinel host somewhat defeats the purpose as if you only have one sentinel instance you have created a single point of failure which defeats the purpose of using a high availability solution.
Temporary work around
Install the predis/predis composer library and then force createConnection to use it by passing the class parameter:
redis:?host[sentinel1:26379]&host[sentinel2:26379]&host[sentinel3:26379]&redis_sentinel=mymaster&class=\Predis\Client
How to reproduce
- Ensure the redis php extension is installed
- create a redis connection with multiple sentinel hosts:
RedisAdapter::createConnection('redis:?host[sentinel1:26379]&host[sentinel2:26379]&host[sentinel3:26379]&redis_sentinel=mymaster')
Expected Behaviour
An instance of \Redis should be returned with a connection to the current redis master assigned by redis sentinel
Current Behaviour
An instance of \RedisArray is returned with each of the redis sentinel hosts configured as redis hosts in the redis array (which will fail to connect as they are talking to a sentinel instance not a normal redis instance)
Possible Solution
I would expect either:
- createConnection should not use the \RedisArray class if the redis_sentinel parameter is set and should instead use the \Redis class. It should loop through each of the sentinel hosts provided and try to get the address for the master from each one. If it fails to get the master from any of the provided sentinel hosts it should throw an exception
- if multiple sentinel hosts are provided with the redis_sentinel parameter it should either use the \Predis\Client class (which deals with mutliple sentinel hosts correctly) or throw an exception if it is not installed explaining that Predis must be installed in order to use sentinel with multiple hosts
Additional Context
No response