Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[Cache] RedisAdapter::createConnection does not get master from redis sentinel when redis extension is installed #46998

Copy link
Copy link
Closed
@warslett

Description

@warslett
Issue body actions

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

  1. Ensure the redis php extension is installed
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      Morty Proxy This is a proxified and sanitized view of the page, visit original site.