#113071 init Namoshek
This commit is contained in:
parent
3421b02c8b
commit
0e934d08ac
|
@ -0,0 +1,152 @@
|
|||
скопированно отсюда https://github.com/Namoshek/laravel-redis-sentinel/tree/master
|
||||
|
||||
# Laravel Redis Sentinel driver for `phpredis/phpredis` PHP extension
|
||||
|
||||
[](https://packagist.org/packages/namoshek/laravel-redis-sentinel)
|
||||
[](https://packagist.org/packages/namoshek/laravel-redis-sentinel)
|
||||
[](https://github.com/Namoshek/laravel-redis-sentinel/actions?query=workflow%3ATests)
|
||||
[](https://sonarcloud.io/dashboard?id=namoshek_laravel-redis-sentinel)
|
||||
[](https://sonarcloud.io/dashboard?id=namoshek_laravel-redis-sentinel)
|
||||
[](https://sonarcloud.io/dashboard?id=namoshek_laravel-redis-sentinel)
|
||||
[](https://sonarcloud.io/dashboard?id=namoshek_laravel-redis-sentinel)
|
||||
[](https://sonarcloud.io/dashboard?id=namoshek_laravel-redis-sentinel)
|
||||
[](https://packagist.org/packages/namoshek/laravel-redis-sentinel)
|
||||
|
||||
This package provides a Laravel Redis driver which allows connecting to a Redis master through a Redis Sentinel instance.
|
||||
The package is intended to be used in a Kubernetes environment or similar, where connecting to Redis Sentinels is possible through a load balancer.
|
||||
|
||||
This driver is an alternative to [`monospice/laravel-redis-sentinel-drivers`](https://github.com/monospice/laravel-redis-sentinel-drivers).
|
||||
The primary difference is that this driver supports the [`phpredis/phpredis` PHP extension](https://github.com/phpredis/phpredis)
|
||||
and has significantly simpler configuration, due to a simpler architecture.
|
||||
In detail this means that this package does not override the entire Redis subsystem of Laravel, it only adds an additional driver.
|
||||
|
||||
By default, Laravel supports the `predis` and `phpredis` drivers. This package adds a third `phpredis-sentinel` driver,
|
||||
which is an extension of the `phpredis` driver for Redis Sentinel.
|
||||
An extension for `predis` is currently not available and not necessary, since [`predis/predis`](https://github.com/predis/predis) already supports
|
||||
connecting to Redis through one or more Sentinels.
|
||||
|
||||
## Installation
|
||||
|
||||
You can install the package via composer:
|
||||
|
||||
```bash
|
||||
composer require namoshek/laravel-redis-sentinel
|
||||
```
|
||||
|
||||
The service provider which comes with the package is registered automatically.
|
||||
|
||||
## Configuration
|
||||
|
||||
The package requires no extra configuration and does therefore not provide an additional configuration file.
|
||||
|
||||
## Usage
|
||||
|
||||
To use the Redis Sentinel driver, the `redis` section in `config/database.php` needs to be adjusted:
|
||||
|
||||
```php
|
||||
'redis' => [
|
||||
'client' => env('REDIS_CLIENT', 'phpredis-sentinel'),
|
||||
|
||||
'default' => [
|
||||
'sentinel_host' => env('REDIS_SENTINEL_HOST', '127.0.0.1'),
|
||||
'sentinel_port' => (int) env('REDIS_SENTINEL_PORT', 26379),
|
||||
'sentinel_service' => env('REDIS_SENTINEL_SERVICE', 'mymaster'),
|
||||
'sentinel_timeout' => (float) env('REDIS_SENTINEL_TIMEOUT', 0),
|
||||
'sentinel_persistent' => env('REDIS_SENTINEL_PERSISTENT'),
|
||||
'sentinel_retry_interval' => (int) env('REDIS_SENTINEL_RETRY_INTERVAL', 0),
|
||||
'sentinel_read_timeout' => (float) env('REDIS_SENTINEL_READ_TIMEOUT', 0),
|
||||
'sentinel_username' => env('REDIS_SENTINEL_USERNAME'),
|
||||
'sentinel_password' => env('REDIS_SENTINEL_PASSWORD'),
|
||||
'password' => env('REDIS_PASSWORD'),
|
||||
'database' => (int) env('REDIS_DB', 0),
|
||||
]
|
||||
]
|
||||
```
|
||||
|
||||
Instead of changing `redis.client` in the configuration file directly, you can also set `REDIS_CLIENT=phpredis-sentinel` in the environment variables.
|
||||
|
||||
As you can see, there are also a few new `sentinel_*` options available for each Redis connection.
|
||||
Most of them work very similar to the normal Redis options, except that they are used for the connection to Redis Sentinel.
|
||||
Noteworthy is the `sentinel_service`, which represents the instance name of the monitored Redis master.
|
||||
|
||||
All other options are the same for the Redis Sentinel driver, except that `url` is not supported and `host` and `port` are ignored.
|
||||
|
||||
### SSL/TLS Support
|
||||
|
||||
If you want to use SSL/TLS to connect to Redis Sentinel, you need to add an additional configuration option `sentinel_ssl` next to the other `sentinel_*` settings:
|
||||
|
||||
```php
|
||||
'sentinel_ssl' => [
|
||||
// ... SSL settings ...
|
||||
],
|
||||
```
|
||||
|
||||
Available SSL context options can be found in the [official PHP documentation](https://www.php.net/manual/en/context.ssl.php). Please note that SSL support for the Sentinel connection was added to the `phpredis` extension starting in version 6.1.
|
||||
|
||||
Also note that if your Redis Sentinel resolves SSL connections to Redis, you potentially need to add additional context options for your Redis connection:
|
||||
|
||||
```php
|
||||
'context' => [
|
||||
'stream' => [
|
||||
// ... SSL settings ...
|
||||
]
|
||||
],
|
||||
'scheme' => 'tls',
|
||||
```
|
||||
|
||||
A full configuration example using SSL for Redis Sentinel as well as Redis looks like this if authentication is also enabled (environment variables omitted for clarity):
|
||||
|
||||
```php
|
||||
'redis' => [
|
||||
'client' => 'phpredis-sentinel',
|
||||
|
||||
'redis_with_tls' => [
|
||||
'sentinel_host' => 'tls://sentinel_host',
|
||||
'sentinel_port' => 26379,
|
||||
'sentinel_service' => 'mymaster',
|
||||
'sentinel_timeout' => 0,
|
||||
'sentinel_persistent' => false,
|
||||
'sentinel_retry_interval' => 0,
|
||||
'sentinel_read_timeout' => 0,
|
||||
'sentinel_username' => 'sentinel_username',
|
||||
'sentinel_password' => 'sentinel_password',
|
||||
'sentinel_ssl' => [
|
||||
'cafile' => '/path/to/sentinel_ca.crt',
|
||||
],
|
||||
'context' => [
|
||||
'stream' => [
|
||||
'cafile' => '/path/to/redis_ca.crt',
|
||||
],
|
||||
],
|
||||
'scheme' => 'tls',
|
||||
'username' => 'redis_username',
|
||||
'password' => 'redis_password',
|
||||
'database' => 1,
|
||||
]
|
||||
]
|
||||
```
|
||||
|
||||
The important parts are the `tls://` protocol in `sentinel_host` as well as the `tls` in `scheme`, plus the `sentinel_ssl` and `context.stream` options.
|
||||
|
||||
Because Redis Sentinel resolves Redis instances by IP and port, your Redis certificate needs to have the IP as SAN. Alternatively, you can set `verify_peer` and maybe also `verify_peer_name` to `false`.
|
||||
|
||||
### How does it work?
|
||||
|
||||
An additional Laravel Redis driver is added (`phpredis-sentinel`), which resolves the currently declared master instance of a replication
|
||||
cluster as active Redis instance. Under the hood, this driver relies on the framework driver for [`phpredis/phpredis`](https://github.com/phpredis/phpredis),
|
||||
it only wraps the connection part of it and adds some error handling which forcefully reconnects in case of a failover.
|
||||
|
||||
Please be aware that this package does not manage load balancing between Sentinels (which is supposed to be done on an infrastructure level)
|
||||
and does also not load balance read/write calls to replica/master nodes. All traffic is sent to the currently reported master.
|
||||
|
||||
## Developing
|
||||
|
||||
To run the tests locally, a Redis cluster needs to be running.
|
||||
The repository contains a script (thanks to [`monospice/laravel-redis-sentinel-drivers`](https://github.com/monospice/laravel-redis-sentinel-drivers))
|
||||
which can be used to start one by running `sh start-redis-cluster.sh`.
|
||||
The script requires that Redis is installed on your machine. To install Redis on Ubuntu or Debian,
|
||||
you can use `sudo apt update && sudo apt install redis-server`. For other operating systems, please see [redis.io](https://redis.io/).
|
||||
|
||||
## License
|
||||
|
||||
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
|
|
@ -11,7 +11,6 @@
|
|||
}
|
||||
],
|
||||
"require": {
|
||||
"namoshek/laravel-redis-sentinel": "^0.8|^0.9"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
|
|
@ -0,0 +1,258 @@
|
|||
<?php
|
||||
|
||||
/* @noinspection PhpRedundantCatchClauseInspection */
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Namoshek\Redis\Sentinel\Connections;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Redis\Connections\PhpRedisConnection;
|
||||
use Illuminate\Support\Str;
|
||||
use Redis;
|
||||
use RedisException;
|
||||
|
||||
/**
|
||||
* The connection to Redis after connecting through a Sentinel using the PhpRedis extension.
|
||||
*/
|
||||
class PhpRedisSentinelConnection extends PhpRedisConnection
|
||||
{
|
||||
// The following array contains all exception message parts which are interpreted as a connection loss or
|
||||
// another unavailability of Redis.
|
||||
private const ERROR_MESSAGES_INDICATING_UNAVAILABILITY = [
|
||||
'connection closed',
|
||||
'connection refused',
|
||||
'connection lost',
|
||||
'failed while reconnecting',
|
||||
'is loading the dataset in memory',
|
||||
'php_network_getaddresses',
|
||||
'read error on connection',
|
||||
'socket',
|
||||
'went away',
|
||||
'loading',
|
||||
'readonly',
|
||||
"can't write against a read only replica",
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws RedisException
|
||||
*/
|
||||
public function scan($cursor, $options = []): mixed
|
||||
{
|
||||
try {
|
||||
return parent::scan($cursor, $options);
|
||||
} catch (RedisException $e) {
|
||||
$this->reconnectIfRedisIsUnavailableOrReadonly($e);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws RedisException
|
||||
*/
|
||||
public function zscan($key, $cursor, $options = []): mixed
|
||||
{
|
||||
try {
|
||||
return parent::zscan($key, $cursor, $options);
|
||||
} catch (RedisException $e) {
|
||||
$this->reconnectIfRedisIsUnavailableOrReadonly($e);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws RedisException
|
||||
*/
|
||||
public function hscan($key, $cursor, $options = []): mixed
|
||||
{
|
||||
try {
|
||||
return parent::hscan($key, $cursor, $options);
|
||||
} catch (RedisException $e) {
|
||||
$this->reconnectIfRedisIsUnavailableOrReadonly($e);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws RedisException
|
||||
*/
|
||||
public function sscan($key, $cursor, $options = []): mixed
|
||||
{
|
||||
try {
|
||||
return parent::sscan($key, $cursor, $options);
|
||||
} catch (RedisException $e) {
|
||||
$this->reconnectIfRedisIsUnavailableOrReadonly($e);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws RedisException
|
||||
*/
|
||||
public function pipeline(?callable $callback = null): Redis|array
|
||||
{
|
||||
try {
|
||||
return parent::pipeline($callback);
|
||||
} catch (RedisException $e) {
|
||||
$this->reconnectIfRedisIsUnavailableOrReadonly($e);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws RedisException
|
||||
*/
|
||||
public function transaction(?callable $callback = null): Redis|array
|
||||
{
|
||||
try {
|
||||
return parent::transaction($callback);
|
||||
} catch (RedisException $e) {
|
||||
$this->reconnectIfRedisIsUnavailableOrReadonly($e);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws RedisException
|
||||
*/
|
||||
public function evalsha($script, $numkeys, ...$arguments): mixed
|
||||
{
|
||||
try {
|
||||
return parent::evalsha($script, $numkeys, $arguments);
|
||||
} catch (RedisException $e) {
|
||||
$this->reconnectIfRedisIsUnavailableOrReadonly($e);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws RedisException
|
||||
*/
|
||||
public function subscribe($channels, Closure $callback): void
|
||||
{
|
||||
try {
|
||||
parent::subscribe($channels, $callback);
|
||||
} catch (RedisException $e) {
|
||||
$this->reconnectIfRedisIsUnavailableOrReadonly($e);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws RedisException
|
||||
*/
|
||||
public function psubscribe($channels, Closure $callback): void
|
||||
{
|
||||
try {
|
||||
parent::psubscribe($channels, $callback);
|
||||
} catch (RedisException $e) {
|
||||
$this->reconnectIfRedisIsUnavailableOrReadonly($e);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws RedisException
|
||||
*/
|
||||
public function flushdb(): void
|
||||
{
|
||||
try {
|
||||
parent::flushdb();
|
||||
} catch (RedisException $e) {
|
||||
$this->reconnectIfRedisIsUnavailableOrReadonly($e);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws RedisException
|
||||
*/
|
||||
public function command($method, array $parameters = []): mixed
|
||||
{
|
||||
try {
|
||||
return parent::command($method, $parameters);
|
||||
} catch (RedisException $e) {
|
||||
$this->reconnectIfRedisIsUnavailableOrReadonly($e);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws RedisException
|
||||
*/
|
||||
public function __call($method, $parameters): mixed
|
||||
{
|
||||
try {
|
||||
return parent::__call(strtolower($method), $parameters);
|
||||
} catch (RedisException $e) {
|
||||
$this->reconnectIfRedisIsUnavailableOrReadonly($e);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspects the given exception and reconnects the client if the reported error indicates that the server
|
||||
* went away or is in readonly mode, which may happen in case of a Redis Sentinel failover.
|
||||
*/
|
||||
private function reconnectIfRedisIsUnavailableOrReadonly(RedisException $exception): void
|
||||
{
|
||||
// We convert the exception message to lower-case in order to perform case-insensitive comparison.
|
||||
$exceptionMessage = strtolower($exception->getMessage());
|
||||
|
||||
// Because we also match only partial exception messages, we cannot use in_array() at this point.
|
||||
foreach (self::ERROR_MESSAGES_INDICATING_UNAVAILABILITY as $errorMessage) {
|
||||
if (str_contains($exceptionMessage, $errorMessage)) {
|
||||
// Here we reconnect through Redis Sentinel if we lost connection to the server or if another unavailability occurred.
|
||||
// We may actually reconnect to the same, broken server. But after a failover occured, we should be ok.
|
||||
// It may take a moment until the Sentinel returns the new master, so this may be triggered multiple times.
|
||||
$this->reconnect();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconnects to the Redis server by overriding the current connection.
|
||||
*/
|
||||
private function reconnect(): void
|
||||
{
|
||||
$this->client = $this->connector ? call_user_func($this->connector) : $this->client;
|
||||
}
|
||||
}
|
|
@ -2,29 +2,125 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Dominion\Redis\Sentinel\Connectors;
|
||||
namespace Namoshek\Redis\Sentinel\Connectors;
|
||||
|
||||
use Illuminate\Redis\Connectors\PhpRedisConnector;
|
||||
use Illuminate\Support\Arr;
|
||||
use Namoshek\Redis\Sentinel\Connections\PhpRedisSentinelConnection;
|
||||
use Namoshek\Redis\Sentinel\Exceptions\ConfigurationException;
|
||||
use Redis;
|
||||
use RedisException;
|
||||
use RedisSentinel;
|
||||
|
||||
/**
|
||||
* Allows to connect to a Sentinel driven Redis master using the PhpRedis extension.
|
||||
*/
|
||||
class PhpRedisSentinelConnector extends \Namoshek\Redis\Sentinel\Connectors\PhpRedisSentinelConnector
|
||||
class PhpRedisSentinelConnector extends PhpRedisConnector
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws RedisException
|
||||
*/
|
||||
public function connect(array $config, array $options): PhpRedisSentinelConnection
|
||||
{
|
||||
$connector = function () use ($config, $options) {
|
||||
return $this->createClient(array_merge(
|
||||
$config,
|
||||
$options,
|
||||
Arr::pull($config, 'options', [])
|
||||
));
|
||||
};
|
||||
|
||||
return new PhpRedisSentinelConnection($connector(), $connector, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the PhpRedis client instance which connects to Redis Sentinel.
|
||||
*
|
||||
* @throws ConfigurationException
|
||||
* @throws RedisException
|
||||
*/
|
||||
protected function createClient(array $config): Redis
|
||||
{
|
||||
$service = $config['sentinel_service'] ?? 'mymaster';
|
||||
|
||||
$sentinel = $this->connectToSentinel($config);
|
||||
|
||||
$master = $sentinel->master($service);
|
||||
|
||||
if (! $this->isValidMaster($master)) {
|
||||
throw new RedisException(sprintf("No master found for service '%s'.", $service));
|
||||
}
|
||||
|
||||
return parent::createClient(array_merge($config, [
|
||||
'host' => $master['ip'],
|
||||
'port' => $master['port'],
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether master is valid or not.
|
||||
*/
|
||||
protected function isValidMaster(mixed $master): bool
|
||||
{
|
||||
return is_array($master) && isset($master['ip']) && isset($master['port']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the configured Redis Sentinel instance.
|
||||
*
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
private function connectToSentinel(array $config): RedisSentinel
|
||||
{
|
||||
$hosts = is_array($config['sentinel_host']) ? $config['sentinel_host'] : [$config['sentinel_host']];
|
||||
foreach ($hosts as $host)
|
||||
{
|
||||
$newConfig = $config;
|
||||
$newConfig['sentinel_host'] = $host;
|
||||
$RedisSentinel = parent::connectToSentinel($newConfig);
|
||||
if($RedisSentinel->ping())
|
||||
{
|
||||
break;
|
||||
}
|
||||
$host = $config['sentinel_host'] ?? '';
|
||||
$port = $config['sentinel_port'] ?? 26379;
|
||||
$timeout = $config['sentinel_timeout'] ?? 0.2;
|
||||
$persistent = $config['sentinel_persistent'] ?? null;
|
||||
$retryInterval = $config['sentinel_retry_interval'] ?? 0;
|
||||
$readTimeout = $config['sentinel_read_timeout'] ?? 0;
|
||||
$username = $config['sentinel_username'] ?? '';
|
||||
$password = $config['sentinel_password'] ?? '';
|
||||
$ssl = $config['sentinel_ssl'] ?? null;
|
||||
|
||||
if (strlen(trim($host)) === 0) {
|
||||
throw new ConfigurationException('No host has been specified for the Redis Sentinel connection.');
|
||||
}
|
||||
return $RedisSentinel;
|
||||
|
||||
$auth = null;
|
||||
if (strlen(trim($username)) !== 0 && strlen(trim($password)) !== 0) {
|
||||
$auth = [$username, $password];
|
||||
} elseif (strlen(trim($password)) !== 0) {
|
||||
$auth = $password;
|
||||
}
|
||||
|
||||
if (version_compare(phpversion('redis'), '6.0', '>=')) {
|
||||
$options = [
|
||||
'host' => $host,
|
||||
'port' => $port,
|
||||
'connectTimeout' => $timeout,
|
||||
'persistent' => $persistent,
|
||||
'retryInterval' => $retryInterval,
|
||||
'readTimeout' => $readTimeout,
|
||||
];
|
||||
|
||||
if ($auth !== null) {
|
||||
$options['auth'] = $auth;
|
||||
}
|
||||
|
||||
if (version_compare(phpversion('redis'), '6.1', '>=') && $ssl !== null) {
|
||||
$options['ssl'] = $ssl;
|
||||
}
|
||||
|
||||
return new RedisSentinel($options);
|
||||
}
|
||||
|
||||
if ($auth !== null) {
|
||||
/** @noinspection PhpMethodParametersCountMismatchInspection */
|
||||
return new RedisSentinel($host, $port, $timeout, $persistent, $retryInterval, $readTimeout, $auth);
|
||||
}
|
||||
|
||||
return new RedisSentinel($host, $port, $timeout, $persistent, $retryInterval, $readTimeout);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Namoshek\Redis\Sentinel\Exceptions;
|
||||
|
||||
/**
|
||||
* Exception to be used if wrong application configuration is encountered.
|
||||
*/
|
||||
class ConfigurationException extends \RuntimeException
|
||||
{
|
||||
}
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Dominion\Redis\Sentinel;
|
||||
namespace Namoshek\Redis\Sentinel;
|
||||
|
||||
use Illuminate\Redis\RedisManager;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Dominion\Redis\Sentinel\Connectors\PhpRedisSentinelConnector;
|
||||
use Namoshek\Redis\Sentinel\Connectors\PhpRedisSentinelConnector;
|
||||
|
||||
/**
|
||||
* Registers and boots services of the Laravel Redis Sentinel package.
|
||||
|
@ -21,7 +21,7 @@ class RedisSentinelServiceProvider extends ServiceProvider
|
|||
public function register(): void
|
||||
{
|
||||
$this->app->extend('redis', function (RedisManager $service) {
|
||||
return $service->extend('dominion-phpredis-sentinel', fn () => new PhpRedisSentinelConnector);
|
||||
return $service->extend('phpredis-sentinel', fn () => new PhpRedisSentinelConnector);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue