Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing PhpRedis client #36

Open
wants to merge 18 commits into
base: 2.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ before_install:
- if [ -n "$LUMEN_VERSION" ]; then composer remove --dev --no-update "laravel/framework"; fi
- if [ -n "$LUMEN_VERSION" ]; then composer require --no-update "laravel/lumen-framework:$LUMEN_VERSION"; fi
- if [ -z "$HORIZON" ]; then composer remove --dev --no-update "laravel/horizon"; fi
- if [[ ${TRAVIS_PHP_VERSION:0:1} == "7" ]]; then printf "\n" | pecl install -f redis; fi

install: travis_retry composer install --prefer-dist --no-interaction --no-suggest

Expand Down
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,13 @@ Requirements

- PHP 5.4 or greater
- [Redis][redis] 2.8 or greater (for Sentinel support)
- [Predis][predis] 1.1 or greater (for Sentinel client support)
- [Laravel][laravel] or [Lumen][lumen] 5.0 or greater (4.x doesn't support the
required Predis version)

Driver options:
- [Predis][predis] 1.1 or greater (for Sentinel client support)
- [PhpRedis][php-redis] 5.3.4 or greater (for Sentinel client support)

**Note:** Laravel 5.4 introduced the ability to use the [PhpRedis][php-redis]
extension as a Redis client for the framework. This package does not yet
support the PhpRedis option.
Expand Down Expand Up @@ -132,6 +135,7 @@ QUEUE_CONNECTION=redis-sentinel # Laravel >= 5.7
QUEUE_DRIVER=redis-sentinel # Laravel <= 5.6
REDIS_DRIVER=redis-sentinel

REDIS_CLIENT=predis
REDIS_HOST=sentinel1.example.com, sentinel2.example.com, 10.0.0.1, etc.
REDIS_PORT=26379
REDIS_SENTINEL_SERVICE=mymaster # or your Redis master group name
Expand Down Expand Up @@ -409,6 +413,30 @@ for a single connection. The default values are shown below:
],
```

The PhpRedis client supports extra options. The default values are shown below:

```php
'options' => [
...

// The default number of attempts to retry the connection if the inititial
// connection has failed. A value of 0 instructs the
// client to throw an exception after the first failed attempt, while a
// value of -1 causes the client to continue to retry commands indefinitely.
'connector_retry_limit' => 20,

// The default amount of time (in milliseconds) that the client waits before
// retrying the connection attempt.
'connector_retry_wait' => 1000,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do these configuration paramters play together with retry_limit and retry_wait? Would it be possible to allow the PhpRedisConnection to reuse the connector logic (and retry configuration) of PhpRedisConnector?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These settings allow for a different configuration on first connection. So the normal retry_limit and retry_wait are used for reconnecting after a failure. The connector_ options are used when creating the connection for the first time.

I've combined some of the retry logic, but I can't seem to find a way to simplify this. Maybe you have some ideas about it?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the settings are fine, just a bit hard to understand in combination with the other ones (which have quite similar names too). Explaining their difference from the similar named settings is probably all you can do at this point.


// Sets the persistent option in the RedisSentinel class.
'sentinel_persistent' => null,

// Sets the read timeout option in the RedisSentinel class. 0 means unlimited.
'sentinel_read_timeout' => 0,
],
```

### Broadcasting, Cache, Session, and Queue Drivers

After configuring the Sentinel database connections, we can instruct Laravel to
Expand Down
27 changes: 16 additions & 11 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
{
"name": "monospice/laravel-redis-sentinel-drivers",
"description": "Redis Sentinel integration for Laravel and Lumen.",
"keywords": ["redis", "sentinel", "laravel", "lumen"],
"keywords": [
"redis",
"sentinel",
"laravel",
"lumen"
],
"type": "library",
"license": "MIT",
"authors": [
Expand All @@ -12,21 +17,21 @@
],
"require": {
"php": ">=5.6.4",
"illuminate/cache": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"illuminate/contracts": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"illuminate/queue": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"illuminate/redis": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"illuminate/session": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"illuminate/support": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"illuminate/cache": "^5.4 || ^6.0 || ^7.0 || ^8.0 || ^9.0",
"illuminate/contracts": "^5.4 || ^6.0 || ^7.0 || ^8.0 || ^9.0",
"illuminate/queue": "^5.4 || ^6.0 || ^7.0 || ^8.0 || ^9.0",
"illuminate/redis": "^5.4 || ^6.0 || ^7.0 || ^8.0 || ^9.0",
"illuminate/session": "^5.4 || ^6.0 || ^7.0 || ^8.0 || ^9.0",
"illuminate/support": "^5.4 || ^6.0 || ^7.0 || ^8.0 || ^9.0",
"monospice/spicy-identifiers": "^0.1",
"predis/predis": "^1.1"
},
"require-dev": {
"laravel/framework": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"laravel/horizon": "^1.0 || ^2.0 || ^3.0 || ^4.0",
"laravel/lumen-framework": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"laravel/framework": "^5.4 || ^6.0 || ^7.0 || ^8.0 || ^9.0",
"laravel/horizon": "^1.0 || ^2.0 || ^3.0 || ^4.0 || ^5.8",
"laravel/lumen-framework": "^5.4 || ^6.0 || ^7.0 || ^8.0 || ^9.0",
"mockery/mockery": "^1.3",
"phpunit/phpunit": "^5.0"
"phpunit/phpunit": "^5.0 || ^9.5.10"
},
"autoload": {
"psr-4": {
Expand Down
2 changes: 1 addition & 1 deletion src/Configuration/HostNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class HostNormalizer
public static function normalizeConnections(array $connections)
{
foreach ($connections as $name => $connection) {
if ($name === 'options' || $name === 'clusters') {
if (in_array($name, ['client', 'options', 'clusters'])) {
continue;
}

Expand Down
227 changes: 227 additions & 0 deletions src/Connections/PhpRedisConnection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
<?php

namespace Monospice\LaravelRedisSentinel\Connections;

use Closure;
use Illuminate\Redis\Connections\PhpRedisConnection as LaravelPhpRedisConnection;
use Monospice\LaravelRedisSentinel\Connectors\PhpRedisConnector;
use Redis;
use RedisException;

/**
* Executes Redis commands using the PhpRedis client.
*
* This package extends Laravel's PhpRedisConnection class to wrap all command
* methods with a retryOnFailure method.
*
* @category Package
* @package Monospice\LaravelRedisSentinel
* @author Jeffrey Zant <[email protected]>
* @license See LICENSE file
* @link https://github.com/monospice/laravel-redis-sentinel-drivers
*/
class PhpRedisConnection extends LaravelPhpRedisConnection
{
/**
* The connection creation callback.
*
* Laravel 5 does not store the connector by default.
*
* @var callable|null
*/
protected $connector;

/**
* The number of times the client attempts to retry a command when it fails
* to connect to a Redis instance behind Sentinel.
*
* @var int
*/
protected $retryLimit = 20;
jeffreyzant marked this conversation as resolved.
Show resolved Hide resolved

/**
* The time in milliseconds to wait before the client retries a failed
* command.
*
* @var int
*/
protected $retryWait = 1000;

/**
* Create a new PhpRedis connection.
*
* @param \Redis $client
* @param callable|null $connector
* @param array $sentinelOptions
* @return void
*/
public function __construct($client, callable $connector = null, array $sentinelOptions = [])
{
parent::__construct($client, $connector);

// Set the connector when it is not set. Used for Laravel 5.
if (! $this->connector) {
$this->connector = $connector;
}

// Set the retry limit.
if (isset($sentinelOptions['retry_limit']) && is_numeric($sentinelOptions['retry_limit'])) {
$this->retryLimit = (int) $sentinelOptions['retry_limit'];
}

// Set the retry wait.
if (isset($sentinelOptions['retry_wait']) && is_numeric($sentinelOptions['retry_wait'])) {
$this->retryWait = (int) $sentinelOptions['retry_wait'];
}
}

/**
* {@inheritdoc} in addition retry on client failure.
*
* @param mixed $cursor
* @param array $options
* @return mixed
*/
public function scan($cursor, $options = [])
{
return $this->retryOnFailure(function () use ($cursor, $options) {
return parent::scan($cursor, $options);
});
}

/**
* {@inheritdoc} in addition retry on client failure.
*
* @param string $key
* @param mixed $cursor
* @param array $options
* @return mixed
*/
public function zscan($key, $cursor, $options = [])
{
return $this->retryOnFailure(function () use ($key, $cursor, $options) {
parent::zscan($key, $cursor, $options);
});
}

/**
* {@inheritdoc} in addition retry on client failure.
*
* @param string $key
* @param mixed $cursor
* @param array $options
* @return mixed
*/
public function hscan($key, $cursor, $options = [])
{
return $this->retryOnFailure(function () use ($key, $cursor, $options) {
parent::hscan($key, $cursor, $options);
});
}

/**
* {@inheritdoc} in addition retry on client failure.
*
* @param string $key
* @param mixed $cursor
* @param array $options
* @return mixed
*/
public function sscan($key, $cursor, $options = [])
{
return $this->retryOnFailure(function () use ($key, $cursor, $options) {
parent::sscan($key, $cursor, $options);
});
}

/**
* {@inheritdoc} in addition retry on client failure.
*
* @param callable|null $callback
* @return \Redis|array
*/
public function pipeline(callable $callback = null)
{
return $this->retryOnFailure(function () use ($callback) {
return parent::pipeline($callback);
});
}

/**
* {@inheritdoc} in addition retry on client failure.
*
* @param callable|null $callback
* @return \Redis|array
*/
public function transaction(callable $callback = null)
{
return $this->retryOnFailure(function () use ($callback) {
return parent::transaction($callback);
});
}

/**
* {@inheritdoc} in addition retry on client failure.
*
* @param array|string $channels
* @param \Closure $callback
* @return void
*/
public function subscribe($channels, Closure $callback)
{
return $this->retryOnFailure(function () use ($channels, $callback) {
return parent::subscribe($channels, $callback);
});
}

/**
* {@inheritdoc} in addition retry on client failure.
*
* @param array|string $channels
* @param \Closure $callback
* @return void
*/
public function psubscribe($channels, Closure $callback)
{
return $this->retryOnFailure(function () use ($channels, $callback) {
return parent::psubscribe($channels, $callback);
});
}

/**
* {@inheritdoc} in addition retry on client failure.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function command($method, array $parameters = [])
{
return $this->retryOnFailure(function () use ($method, $parameters) {
return parent::command($method, $parameters);
});
}

/**
* Attempt to retry the provided operation when the client fails to connect
* to a Redis server.
*
* @param callable $callback The operation to execute.
* @return mixed The result of the first successful attempt.
*/
protected function retryOnFailure(callable $callback)
{
return PhpRedisConnector::retryOnFailure($callback, $this->retryLimit, $this->retryWait, function () {
$this->client->close();

try {
if ($this->connector) {
$this->client = call_user_func($this->connector);
}
} catch (RedisException $e) {
// Ignore when the creation of a new client gets an exception.
// If this exception isn't caught the retry will stop.
}
});
}
}
Loading