-
Notifications
You must be signed in to change notification settings - Fork 47
Implementing PhpRedis client #36
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
base: 2.x
Are you sure you want to change the base?
Changes from all commits
9079c94
9c0093d
56662b2
ef688e1
81e95f9
dec46c3
d2a53a5
4f555d2
eff0fef
923875e
46cdf3a
03faa65
7681bcf
165ec41
dcd0370
a60ce79
e6e623a
0f69a2b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 <j.zant@slash2.nl> | ||
* @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. | ||
} | ||
}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,279 @@ | ||
<?php | ||
|
||
namespace Monospice\LaravelRedisSentinel\Connectors; | ||
|
||
use Illuminate\Support\Arr; | ||
use Illuminate\Redis\Connectors\PhpRedisConnector as LaravelPhpRedisConnector; | ||
use LogicException; | ||
use Monospice\LaravelRedisSentinel\Connections\PhpRedisConnection; | ||
use Monospice\LaravelRedisSentinel\Exceptions\RedisRetryException; | ||
use Redis; | ||
use RedisSentinel; | ||
use RedisException; | ||
|
||
/** | ||
* Initializes PhpRedis Client instances for Redis Sentinel connections | ||
* | ||
* @category Package | ||
* @package Monospice\LaravelRedisSentinel | ||
* @author Jeffrey Zant <j.zant@slash2.nl> | ||
* @license See LICENSE file | ||
* @link http://github.com/monospice/laravel-redis-sentinel-drivers | ||
*/ | ||
class PhpRedisConnector extends LaravelPhpRedisConnector | ||
{ | ||
/** | ||
* Holds the current sentinel servers. | ||
* | ||
* @var array | ||
*/ | ||
protected $servers; | ||
|
||
/** | ||
* 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 $connectorRetryLimit = 20; | ||
|
||
/** | ||
* The time in milliseconds to wait before the client retries a failed | ||
* command. | ||
* | ||
* @var int | ||
*/ | ||
protected $connectorRetryWait = 1000; | ||
|
||
/** | ||
* Configuration options specific to Sentinel connection operation | ||
* | ||
* Some of the Sentinel configuration options can be entered in this class. | ||
* The retry_wait and retry_limit values are passed to the connection. | ||
* | ||
* @var array | ||
*/ | ||
protected $sentinelKeys = [ | ||
'sentinel_timeout' => null, | ||
'retry_wait' => null, | ||
'retry_limit' => null, | ||
'update_sentinels' => null, | ||
|
||
'sentinel_persistent' => null, | ||
'sentinel_read_timeout' => null, | ||
]; | ||
|
||
/** | ||
* Instantiate the connector and check if the required extension is available. | ||
*/ | ||
public function __construct() | ||
{ | ||
if (! extension_loaded('redis')) { | ||
throw new LogicException('Please make sure the PHP Redis extension is installed and enabled.'); | ||
} | ||
|
||
if (! class_exists(RedisSentinel::class)) { | ||
throw new LogicException('Please make sure the PHP Redis extension is up to date (5.3.4 or greater).'); | ||
} | ||
} | ||
|
||
/** | ||
* Create a new Redis Sentinel connection from the provided configuration | ||
* options | ||
* | ||
* @param array $server The client configuration for the connection | ||
* @param array $options The global client options shared by all Sentinel | ||
* connections | ||
* | ||
* @return PhpRedisConnection The Sentinel connection containing a configured | ||
* PhpRedis Client | ||
*/ | ||
public function connect(array $servers, array $options = [ ]) | ||
{ | ||
// Set the initial Sentinel servers. | ||
$this->servers = array_map(function ($server) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should extract the options key before setting the servers or it can result trying to connect to the wrong hostname here: https://github.com/monospice/laravel-redis-sentinel-drivers/pull/36/files#diff-3bf85f59585710cebd9ae7787d8b7ffb7df273ce8ffe2e72053ef96b4bb81ccaR157 |
||
return $this->formatServer($server); | ||
}, $servers); | ||
|
||
// Set the connector retry limit. | ||
if (isset($options['connector_retry_limit']) && is_numeric($options['connector_retry_limit'])) { | ||
$this->connectorRetryLimit = (int) $options['connector_retry_limit']; | ||
} | ||
|
||
// Set the connector retry wait. | ||
if (isset($options['connector_retry_wait']) && is_numeric($options['connector_retry_wait'])) { | ||
$this->connectorRetryWait = (int) $options['connector_retry_wait']; | ||
} | ||
|
||
// Merge the global options shared by all Sentinel connections with | ||
// connection-specific options | ||
$clientOpts = array_merge($options, Arr::pull($servers, 'options', [ ])); | ||
jeffreyzant marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Extract the array of Sentinel connection options from the rest of | ||
// the client options | ||
$sentinelOpts = array_intersect_key($clientOpts, $this->sentinelKeys); | ||
|
||
// Filter the Sentinel connection options elements from the client | ||
// options array | ||
$clientOpts = array_diff_key($clientOpts, $this->sentinelKeys); | ||
|
||
// Create a client by calling the Sentinel servers | ||
$connector = function () use ($options) { | ||
return $this->createClientWithSentinel($options); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you are using |
||
}; | ||
|
||
// Create a connection and retry if this fails. | ||
$connection = self::retryOnFailure(function () use ($connector) { | ||
return $connector(); | ||
}, $this->connectorRetryLimit, $this->connectorRetryWait); | ||
|
||
return new PhpRedisConnection($connection, $connector, $sentinelOpts); | ||
} | ||
|
||
/** | ||
* Create the Redis client instance | ||
* | ||
* @param array $options | ||
* @return Redis | ||
* | ||
* @throws LogicException | ||
*/ | ||
protected function createClientWithSentinel(array $options) | ||
{ | ||
$serverConfigurations = $this->servers; | ||
$clientConfiguration = isset($options['parameters']) ? $options['parameters'] : []; | ||
|
||
$updateSentinels = isset($options['update_sentinels']) ? $options['update_sentinels'] : false; | ||
$sentinelService = isset($options['service']) ? $options['service'] : 'mymaster'; | ||
$sentinelTimeout = isset($options['sentinel_timeout']) ? $options['sentinel_timeout'] : 0; | ||
$sentinelPersistent = isset($options['sentinel_persistent']) ? $options['sentinel_persistent'] : null; | ||
$sentinelReadTimeout = isset($options['sentinel_read_timeout']) ? $options['sentinel_read_timeout'] : 0; | ||
|
||
// Shuffle the server configurations to perform some loadbalancing. | ||
shuffle($serverConfigurations); | ||
|
||
// Try to connect to any of the servers. | ||
foreach ($serverConfigurations as $idx => $serverConfiguration) { | ||
$host = isset($serverConfiguration['host']) ? $serverConfiguration['host'] : 'localhost'; | ||
$port = isset($serverConfiguration['port']) ? $serverConfiguration['port'] : 26379; | ||
|
||
// Create a connection to the Sentinel instance. Using a retry_interval of 0, retrying | ||
// is done inside the PhpRedisConnection. Cannot seem to get the retry_interval to work: | ||
// https://github.com/phpredis/phpredis/blob/37a90257d09b4efa75230769cf535484116b2b67/library.c#L343 | ||
$sentinel = new RedisSentinel($host, $port, $sentinelTimeout, $sentinelPersistent, 0, $sentinelReadTimeout); | ||
|
||
try { | ||
// Check if the Sentinel server list needs to be updated. | ||
// Put the current server and the other sentinels in the server list. | ||
if ($updateSentinels === true) { | ||
$this->updateSentinels($sentinel, $host, $port, $sentinelService); | ||
} | ||
|
||
// Lookup the master node. | ||
$master = $sentinel->getMasterAddrByName($sentinelService); | ||
if (is_array($master) && ! count($master)) { | ||
throw new RedisException(sprintf('No master found for service "%s".', $sentinelService)); | ||
} | ||
|
||
// Create a PhpRedis client for the selected master node. | ||
return $this->createClient(array_merge($clientConfiguration, $serverConfiguration, [ | ||
'host' => $master[0], | ||
'port' => $master[1], | ||
])); | ||
} catch (RedisException $e) { | ||
// Rethrow the expection when the last server is reached. | ||
if ($idx === count($serverConfigurations) - 1) { | ||
throw $e; | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Update the list With sentinel servers. | ||
* | ||
* @param RedisSentinel $sentinel | ||
* @param string $currentHost | ||
* @param int $currentPort | ||
* @param string $service | ||
* @return void | ||
*/ | ||
protected function updateSentinels(RedisSentinel $sentinel, string $currentHost, int $currentPort, string $service) | ||
{ | ||
$this->servers = array_merge( | ||
[ | ||
jeffreyzant marked this conversation as resolved.
Show resolved
Hide resolved
|
||
[ | ||
'host' => $currentHost, | ||
'port' => $currentPort, | ||
] | ||
], array_map(function ($sentinel) { | ||
return [ | ||
'host' => $sentinel['ip'], | ||
'port' => $sentinel['port'], | ||
]; | ||
}, $sentinel->sentinels($service)) | ||
); | ||
} | ||
|
||
/** | ||
* Format a server. | ||
* | ||
* @param mixed $server | ||
* @return array | ||
* | ||
* @throws RedisException | ||
*/ | ||
protected function formatServer($server) | ||
{ | ||
if (is_string($server)) { | ||
list($host, $port) = explode(':', $server); | ||
if (! $host || ! $port) { | ||
throw new RedisException('Could not format the server definition.'); | ||
} | ||
|
||
return ['host' => $host, 'port' => (int) $port]; | ||
} | ||
|
||
if (! is_array($server)) { | ||
throw new RedisException('Could not format the server definition.'); | ||
} | ||
|
||
return $server; | ||
} | ||
|
||
/** | ||
* Retry the callback when a RedisException is catched. | ||
* | ||
* @param callable $callback The operation to execute. | ||
* @param int $retryLimit The number of times the retry is performed. | ||
* @param int $retryWait The time in milliseconds to wait before retrying again. | ||
* @param callable $failureCallback The operation to execute when a failure happens. | ||
* @return mixed The result of the first successful attempt. | ||
* | ||
* @throws RedisRetryException After exhausting the allowed number of | ||
* attempts to connect. | ||
*/ | ||
public static function retryOnFailure(callable $callback, int $retryLimit, int $retryWait, callable $failureCallback = null) | ||
{ | ||
$attempts = 0; | ||
$previousException = null; | ||
|
||
while ($attempts < $retryLimit) { | ||
try { | ||
return $callback(); | ||
} catch (RedisException $exception) { | ||
$previousException = $exception; | ||
|
||
if ($failureCallback) { | ||
call_user_func($failureCallback); | ||
} | ||
|
||
usleep($retryWait * 1000); | ||
|
||
$attempts++; | ||
} | ||
} | ||
|
||
throw new RedisRetryException(sprintf('Reached the (re)connect limit of %d attempts', $attempts), 0, $previousException); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<?php | ||
|
||
namespace Monospice\LaravelRedisSentinel\Exceptions; | ||
|
||
use RedisException as PhpRedisException; | ||
|
||
class RedisRetryException extends PhpRedisException | ||
{ | ||
// | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
<?php | ||
|
||
namespace Monospice\LaravelRedisSentinel\Tests\Integration\Connections; | ||
|
||
use Monospice\LaravelRedisSentinel\Connections\PhpRedisConnection; | ||
use Monospice\LaravelRedisSentinel\Connectors\PhpRedisConnector; | ||
use Monospice\LaravelRedisSentinel\Tests\Support\DummyException; | ||
use Monospice\LaravelRedisSentinel\Tests\Support\IntegrationTestCase; | ||
use Monospice\LaravelRedisSentinel\Exceptions\RedisRetryException; | ||
use Redis; | ||
use RedisException; | ||
|
||
class PhpRedisConnectionTest extends IntegrationTestCase | ||
{ | ||
/** | ||
* The instance of the PhpRedis client wrapper under test. | ||
* | ||
* @var PhpRedisConnection | ||
*/ | ||
protected $subject; | ||
|
||
/** | ||
* Run this setup before each test | ||
* | ||
* @return void | ||
*/ | ||
public function setUp() | ||
{ | ||
parent::setUp(); | ||
|
||
if (! extension_loaded('redis')) { | ||
return; | ||
} | ||
|
||
$this->subject = $this->makeConnection(); | ||
} | ||
|
||
public function testAllowsTransactionsOnAggregateConnection() | ||
{ | ||
if (! extension_loaded('redis')) { | ||
$this->markTestSkipped('The redis extension is not installed. Please install the extension to enable '.__CLASS__); | ||
|
||
return; | ||
} | ||
|
||
$transaction = $this->subject->transaction(); | ||
|
||
$this->assertInstanceOf(Redis::class, $transaction); | ||
} | ||
|
||
public function testExecutesCommandsInTransaction() | ||
{ | ||
if (! extension_loaded('redis')) { | ||
$this->markTestSkipped('The redis extension is not installed. Please install the extension to enable '.__CLASS__); | ||
|
||
return; | ||
} | ||
|
||
$result = $this->subject->transaction(function ($trans) { | ||
$trans->set('test-key', 'test value'); | ||
$trans->get('test-key'); | ||
}); | ||
|
||
$this->assertCount(2, $result); | ||
$this->assertTrue($result[0]); | ||
$this->assertEquals('test value', $result[1]); | ||
$this->assertRedisKeyEquals('test-key', 'test value'); | ||
} | ||
|
||
public function testExecutesTransactionsOnMaster() | ||
{ | ||
if (! extension_loaded('redis')) { | ||
$this->markTestSkipped('The redis extension is not installed. Please install the extension to enable '.__CLASS__); | ||
|
||
return; | ||
} | ||
|
||
$expectedSubset = ['role' => 'master']; | ||
|
||
$info = $this->subject->transaction(function ($transaction) { | ||
$transaction->info(); | ||
}); | ||
|
||
$this->assertArraySubset($expectedSubset, $info[0]); | ||
} | ||
|
||
public function testAbortsTransactionOnException() | ||
{ | ||
if (! extension_loaded('redis')) { | ||
$this->markTestSkipped('The redis extension is not installed. Please install the extension to enable '.__CLASS__); | ||
|
||
return; | ||
} | ||
|
||
$exception = null; | ||
|
||
try { | ||
$this->subject->transaction(function ($trans) { | ||
$trans->set('test-key', 'test value'); | ||
throw new DummyException(); | ||
}); | ||
} catch (DummyException $exception) { | ||
// With PHPUnit, we need to wrap the throwing block to perform | ||
// assertions afterward. | ||
} | ||
|
||
$this->assertNotNull($exception); | ||
$this->assertRedisKeyEquals('test-key', null); | ||
} | ||
|
||
public function testRetriesTransactionWhenConnectionFails() | ||
{ | ||
if (! extension_loaded('redis')) { | ||
$this->markTestSkipped('The redis extension is not installed. Please install the extension to enable '.__CLASS__); | ||
|
||
return; | ||
} | ||
|
||
$this->expectException(RedisRetryException::class); | ||
|
||
$this->subject = $this->makeConnection(1, 0); // retry once and immediately | ||
|
||
$this->subject->transaction(function () { | ||
throw new RedisException(); | ||
}); | ||
} | ||
|
||
public function testCanReconnectWhenConnectionFails() | ||
{ | ||
if (! extension_loaded('redis')) { | ||
$this->markTestSkipped('The redis extension is not installed. Please install the extension to enable '.__CLASS__); | ||
|
||
return; | ||
} | ||
|
||
$retries = 3; | ||
$attempts = 0; | ||
|
||
$this->subject = $this->makeConnection($retries, 0); // retry immediately | ||
|
||
$this->subject->transaction(function ($trans) use (&$attempts, $retries) { | ||
$attempts++; | ||
|
||
if ($attempts < $retries) { | ||
throw new RedisException(); | ||
} else { | ||
$trans->set('test-key', 'test value'); | ||
} | ||
}); | ||
|
||
$this->assertGreaterThan(1, $attempts, 'First try does not count.'); | ||
$this->assertRedisKeyEquals('test-key', 'test value'); | ||
} | ||
|
||
/** | ||
* Initialize a PhpRedis client using the test connection configuration | ||
* that can verify connectivity failure handling. | ||
* | ||
* @param int|null $retryLimit | ||
* @param int|null $retryWait | ||
* @return PhpRedisConnection A client instance for the subject under test. | ||
*/ | ||
protected function makeConnection(int $retryLimit = null, int $retryWait = null) | ||
{ | ||
$connector = new PhpRedisConnector(); | ||
|
||
$options = $this->config['options']; | ||
if ($retryLimit !== null) { | ||
$options['retry_limit'] = $retryLimit; | ||
} | ||
|
||
if ($retryWait !== null) { | ||
$options['retry_wait'] = $retryWait; | ||
} | ||
|
||
return $connector->connect($this->config['default'], $options); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
<?php | ||
|
||
namespace Monospice\LaravelRedisSentinel\Tests\Integration\Connections; | ||
|
||
use Monospice\LaravelRedisSentinel\Connections\PhpRedisConnection; | ||
use Monospice\LaravelRedisSentinel\Connectors\PhpRedisConnector; | ||
use Monospice\LaravelRedisSentinel\Tests\Support\IntegrationTestCase; | ||
use Monospice\LaravelRedisSentinel\Exceptions\RedisRetryException; | ||
|
||
class PhpRedisConnectorTest extends IntegrationTestCase | ||
{ | ||
/** | ||
* Run this setup before each test | ||
* | ||
* @return void | ||
*/ | ||
public function setUp() | ||
{ | ||
parent::setUp(); | ||
|
||
if (! extension_loaded('redis')) { | ||
return; | ||
} | ||
} | ||
|
||
public function testCanConnect() | ||
{ | ||
if (! extension_loaded('redis')) { | ||
$this->markTestSkipped('The redis extension is not installed. Please install the extension to enable '.__CLASS__); | ||
|
||
return; | ||
} | ||
|
||
$connector = new PhpRedisConnector(); | ||
$client = $connector->connect($this->config['default'], $this->config['options']); | ||
|
||
$this->assertInstanceOf(PhpRedisConnection::class, $client); | ||
} | ||
|
||
public function testRetriesTransactionWhenConnectionFails() | ||
{ | ||
if (! extension_loaded('redis')) { | ||
$this->markTestSkipped('The redis extension is not installed. Please install the extension to enable '.__CLASS__); | ||
|
||
return; | ||
} | ||
|
||
$this->expectException(RedisRetryException::class); | ||
|
||
$connector = new PhpRedisConnector(); | ||
|
||
$servers = [ | ||
[ | ||
'host' => '127.0.0.1', | ||
'port' => 1111, | ||
], | ||
]; | ||
|
||
$options = array_merge([ | ||
'connector_retry_limit' => 3, | ||
'connector_retry_wait' => 0, | ||
]); | ||
|
||
$connector = new PhpRedisConnector(); | ||
$connector->connect($servers, $options); | ||
} | ||
} |
There was a problem hiding this comment.
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
andretry_wait
? Would it be possible to allow thePhpRedisConnection
to reuse the connector logic (and retry configuration) ofPhpRedisConnector
?There was a problem hiding this comment.
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
andretry_wait
are used for reconnecting after a failure. Theconnector_
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?
There was a problem hiding this comment.
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.