Skip to content

Commit 56662b2

Browse files
committed
wrap all phpredis methods with retryOnFailure
1 parent 9c0093d commit 56662b2

File tree

2 files changed

+144
-35
lines changed

2 files changed

+144
-35
lines changed

src/Connections/PhpRedisConnection.php

Lines changed: 126 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@
22

33
namespace Monospice\LaravelRedisSentinel\Connections;
44

5+
use Closure;
56
use Illuminate\Redis\Connections\PhpRedisConnection as LaravelPhpRedisConnection;
67
use Redis;
78
use RedisException;
89

910
/**
1011
* Executes Redis commands using the PhpRedis client.
1112
*
12-
* This package extends Laravel's PhpRedisConnection class to work around issues
13-
* experienced when using the PhpRedis client to send commands over "aggregate"
14-
* connections (in this case, Sentinel connections).
13+
* This package extends Laravel's PhpRedisConnection class to wrap all command
14+
* methods with a retryOnFailure method.
1515
*
1616
* @category Package
1717
* @package Monospice\LaravelRedisSentinel
18-
* @author @pdbreen, Cy Rossignol <cy@rossignols.me>
18+
* @author Jeffrey Zant <j.zant@slash2.nl>
1919
* @license See LICENSE file
2020
* @link https://github.com/monospice/laravel-redis-sentinel-drivers
2121
*/
@@ -54,33 +54,137 @@ public function __construct($client, callable $connector = null, array $sentinel
5454
}
5555

5656
/**
57-
* Execute commands in a transaction.
57+
* {@inheritdoc} in addition retry on client failure.
58+
*
59+
* @param mixed $cursor
60+
* @param array $options
61+
* @return mixed
62+
*/
63+
public function scan($cursor, $options = [])
64+
{
65+
return $this->retryOnFailure(function () use ($cursor, $options) {
66+
return parent::scan($cursor, $options);
67+
});
68+
}
69+
70+
/**
71+
* {@inheritdoc} in addition retry on client failure.
72+
*
73+
* @param string $key
74+
* @param mixed $cursor
75+
* @param array $options
76+
* @return mixed
77+
*/
78+
public function zscan($key, $cursor, $options = [])
79+
{
80+
return $this->retryOnFailure(function () use ($key, $cursor, $options) {
81+
parent::zscan($key, $cursor, $options);
82+
});
83+
}
84+
85+
/**
86+
* {@inheritdoc} in addition retry on client failure.
87+
*
88+
* @param string $key
89+
* @param mixed $cursor
90+
* @param array $options
91+
* @return mixed
92+
*/
93+
public function hscan($key, $cursor, $options = [])
94+
{
95+
return $this->retryOnFailure(function () use ($key, $cursor, $options) {
96+
parent::hscan($key, $cursor, $options);
97+
});
98+
}
99+
100+
/**
101+
* {@inheritdoc} in addition retry on client failure.
102+
*
103+
* @param string $key
104+
* @param mixed $cursor
105+
* @param array $options
106+
* @return mixed
107+
*/
108+
public function sscan($key, $cursor, $options = [])
109+
{
110+
return $this->retryOnFailure(function () use ($key, $cursor, $options) {
111+
parent::sscan($key, $cursor, $options);
112+
});
113+
}
114+
115+
/**
116+
* {@inheritdoc} in addition retry on client failure.
117+
*
118+
* @param callable|null $callback
119+
* @return \Redis|array
120+
*/
121+
public function pipeline(callable $callback = null)
122+
{
123+
return $this->retryOnFailure(function () use ($callback) {
124+
return parent::pipeline($callback);
125+
});
126+
}
127+
128+
/**
129+
* {@inheritdoc} in addition retry on client failure.
58130
*
59131
* @param callable|null $callback
60132
* @return \Redis|array
61133
*/
62134
public function transaction(callable $callback = null)
63135
{
64136
return $this->retryOnFailure(function () use ($callback) {
65-
$transaction = $this->client()->multi();
137+
return parent::transaction($callback);
138+
});
139+
}
66140

67-
return is_null($callback)
68-
? $transaction
69-
: tap($transaction, $callback)->exec();
141+
/**
142+
* {@inheritdoc} in addition retry on client failure.
143+
*
144+
* @param array|string $channels
145+
* @param \Closure $callback
146+
* @return void
147+
*/
148+
public function subscribe($channels, Closure $callback)
149+
{
150+
return $this->retryOnFailure(function () use ($channels, $callback) {
151+
return parent::subscribe($channels, $callback);
70152
});
71153
}
154+
155+
/**
156+
* {@inheritdoc} in addition retry on client failure.
157+
*
158+
* @param array|string $channels
159+
* @param \Closure $callback
160+
* @return void
161+
*/
162+
public function psubscribe($channels, Closure $callback)
163+
{
164+
return $this->retryOnFailure(function () use ($channels, $callback) {
165+
return parent::psubscribe($channels, $callback);
166+
});
167+
}
168+
169+
/**
170+
* {@inheritdoc} in addition retry on client failure.
171+
*
172+
* @param string $method
173+
* @param array $parameters
174+
* @return mixed
175+
*/
176+
public function command($method, array $parameters = [])
177+
{
178+
return $this->retryOnFailure(function () use ($method, $parameters) {
179+
return parent::command($method, $parameters);
180+
});
181+
}
182+
72183
/**
73184
* Attempt to retry the provided operation when the client fails to connect
74185
* to a Redis server.
75186
*
76-
* We adapt Predis' Sentinel connection failure handling logic here to
77-
* reproduce the high-availability mode provided by the actual client. To
78-
* work around "aggregate" connection limitations in Predis, this class
79-
* provides methods that don't use the high-level Sentinel connection API
80-
* of Predis directly, so it needs to handle connection failures itself.
81-
*
82187
* @param callable $callback The operation to execute.
83-
*
84188
* @return mixed The result of the first successful attempt.
85189
*
86190
* @throws RedisException After exhausting the allowed number of
@@ -98,7 +202,12 @@ protected function retryOnFailure(callable $callback)
98202

99203
usleep($this->retryWait * 1000);
100204

101-
$this->client = $this->connector();
205+
try {
206+
$this->client = $this->connector();
207+
} catch (RedisException $e) {
208+
// Ignore the the creation of a new client gets an exception.
209+
// If this exception isn't caught the retry will stop.
210+
}
102211

103212
$attempts++;
104213
}

src/Connectors/PhpRedisConnector.php

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace Monospice\LaravelRedisSentinel\Connectors;
44

55
use Illuminate\Support\Arr;
6-
use Monospice\LaravelRedisSentinel\Connections\PredisConnection;
76
use Illuminate\Redis\Connectors\PhpRedisConnector as LaravelPhpRedisConnector;
87
use Monospice\LaravelRedisSentinel\Connections\PhpRedisConnection;
98
use RedisSentinel;
@@ -14,7 +13,7 @@
1413
*
1514
* @category Package
1615
* @package Monospice\LaravelRedisSentinel
17-
* @author Cy Rossignol <cy@rossignols.me>
16+
* @author Jeffrey Zant <j.zant@slash2.nl>
1817
* @license See LICENSE file
1918
* @link http://github.com/monospice/laravel-redis-sentinel-drivers
2019
*/
@@ -30,9 +29,10 @@ class PhpRedisConnector extends LaravelPhpRedisConnector
3029
/**
3130
* Configuration options specific to Sentinel connection operation
3231
*
33-
* We cannot pass these options as an array to the Predis client.
32+
* @TODO rewrite doc.
33+
* We cannot pass these options as an array to the PhpRedis client.
3434
* Instead, we'll set them on the connection directly using methods
35-
* provided by the SentinelReplication class of the Predis package.
35+
* provided by the SentinelReplication class of the PhpRedis package.
3636
*
3737
* @var array
3838
*/
@@ -54,8 +54,8 @@ class PhpRedisConnector extends LaravelPhpRedisConnector
5454
* @param array $options The global client options shared by all Sentinel
5555
* connections
5656
*
57-
* @return PredisConnection The Sentinel connection containing a configured
58-
* Predis Client
57+
* @return PhpRedisConnection The Sentinel connection containing a configured
58+
* PhpRedis Client
5959
*/
6060
public function connect(array $servers, array $options = [ ])
6161
{
@@ -95,7 +95,7 @@ protected function createClientWithSentinel(array $options)
9595

9696
shuffle($servers);
9797

98-
foreach ($servers as $idx => $server) {
98+
foreach ($servers as $server) {
9999
$host = $server['host'] ?? 'localhost';
100100
$port = $server['port'] ?? 26739;
101101
$service = $options['service'] ?? 'mymaster';
@@ -125,20 +125,20 @@ protected function createClientWithSentinel(array $options)
125125
}
126126

127127
$master = $sentinel->getMasterAddrByName($service);
128-
if (is_array($master) && count($master)) {
129-
$config['host'] = $master[0];
130-
$config['port'] = $master[1];
131-
132-
// @TODO rewrite this config for auth.
133-
134-
return $this->createClient($config);
128+
if (! is_array($master) || ! count($master)) {
129+
throw new RedisException(sprintf('No master found for service "%s".', $service));
135130
}
131+
132+
return $this->createClient(array_merge(
133+
$options['parameters'] ?? [],
134+
$server,
135+
['host' => $master[0], 'port' => $master[1]]
136+
));
136137
} catch (RedisException $e) {
137-
// Only throw the exception if the last server can't connect
138-
if ($idx === count($servers) - 1) {
139-
throw $e;
140-
}
138+
//
141139
}
142140
}
141+
142+
throw new RedisException('Could not create a client for the configured Sentinel servers.');
143143
}
144144
}

0 commit comments

Comments
 (0)