Skip to content

Commit 6ee5e4d

Browse files
authored
Refactor of RedisSentinel to use phpredis as in https://github.com/Namoshek/laravel-redis-sentinel
1 parent 80ccd92 commit 6ee5e4d

File tree

4 files changed

+195
-185
lines changed

4 files changed

+195
-185
lines changed

README.md

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,19 @@ Change the Redis options (the example shows the defaults):
7979
'timeout' => 0.1, // in seconds
8080
'read_timeout' => '10', // in seconds
8181
'persistent_connections' => false
82-
'sentinel' => [
83-
'enable' => false, // support sentinel . Before requesting to redis, a request is made to the sentinel to get
84-
'host' => '127.0.0.1', // the address of the redis, defualt is the same as redis host if empty
85-
'port' => 26379, // the port of the master redis server, default 26379 if empty.
86-
'master' => 'mymaster' //, sentine master name, default mymaster
87-
'timeout' => 0.1 //, sentinel connection timeout
88-
'read_timeout' => null // sentinel read timeout
89-
]
82+
'sentinel' => [ // sentinel options
83+
'enable' => false, // if enabled uses sentinel to get the master before connecting to redis
84+
'host' => '127.0.0.1', // phpredis sentinel address of the redis, default is the same as redis host if empty
85+
'port' => 26379, // phpredis sentinel port of the primary redis server, default 26379 if empty.
86+
'service' => 'myprimary', //, phpredis sentinel primary name, default myprimary
87+
'timeout' => 0, // phpredis sentinel connection timeout
88+
'persistent' => null, // phpredis sentinel persistence parameter
89+
'retry_interval' => 0, // phpredis sentinel retry interval
90+
'read_timeout' => 0, // phpredis sentinel read timeout
91+
'username' => '', // phpredis sentinel auth username
92+
'password' => '', // phpredis sentinel auth password
93+
'ssl' => null,
94+
]
9095
]
9196
);
9297
```

src/Prometheus/Storage/Redis.php

Lines changed: 87 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,36 @@ class Redis implements Adapter
3131
'password' => null,
3232
'user' => null,
3333
'sentinel' => [ // sentinel options
34-
'enable' => false,
35-
'host' => null,
36-
'port' => 26379,
37-
'master' => 'mymaster',
38-
'timeout' => 0.1,
39-
'read_timeout' => null,
40-
],
34+
'enable' => false, // if enabled uses sentinel to get the master before connecting to redis
35+
'host' => '127.0.0.1', // phpredis sentinel address of the redis, default is the same as redis host if empty
36+
'port' => 26379, // phpredis sentinel port of the primary redis server, default 26379 if empty.
37+
'service' => 'myprimary', //, phpredis sentinel primary name, default myprimary
38+
'timeout' => 0, // phpredis sentinel connection timeout
39+
'persistent' => null, // phpredis sentinel persistence parameter
40+
'retry_interval' => 0, // phpredis sentinel retry interval
41+
'read_timeout' => 0, // phpredis sentinel read timeout
42+
'username' => '', // phpredis sentinel auth username
43+
'password' => '', // phpredis sentinel auth password
44+
'ssl' => null,
45+
]
4146
];
4247

48+
// The following array contains all exception message parts which are interpreted as a connection loss or
49+
// another unavailability of Redis.
50+
private const ERROR_MESSAGES_INDICATING_UNAVAILABILITY = [
51+
'connection closed',
52+
'connection refused',
53+
'connection lost',
54+
'failed while reconnecting',
55+
'is loading the dataset in memory',
56+
'php_network_getaddresses',
57+
'read error on connection',
58+
'socket',
59+
'went away',
60+
'loading',
61+
'readonly',
62+
"can't write against a read only replica",
63+
];
4364
/**
4465
* @var string
4566
*/
@@ -67,8 +88,6 @@ class Redis implements Adapter
6788
public function __construct(array $options = [])
6889
{
6990
$this->options = array_merge(self::$defaultOptions, $options);
70-
// is Sentinels ?
71-
$this->options = $this->isSentinel($this->options);
7291
$this->redis = new \Redis();
7392
}
7493

@@ -78,11 +97,12 @@ public function __construct(array $options = [])
7897
*/
7998
public function isSentinel(array $options = [])
8099
{
81-
if($options['sentinel'] && $options['sentinel']['enable']){
82-
$sentinel = new RedisSentinel($options['sentinel'],$options['host']);
83-
list($hostname, $port) = $sentinel->getMaster($options);
84-
$options['host'] = $hostname;
85-
$options['port'] = $port;
100+
if ($options['sentinel'] && $options['sentinel']['enable']) {
101+
$sentinel = new RedisSentinelConnector();
102+
$options['sentinel']['host'] = $options['sentinel']['host'] ?? $options['host'];
103+
$master = $sentinel->getMaster($options['sentinel']);
104+
$options['host'] = $master['ip'];
105+
$options['port'] = $master['port'];
86106
}
87107
return $options;
88108
}
@@ -122,8 +142,8 @@ public static function setPrefix(string $prefix): void
122142
}
123143

124144
/**
125-
* @deprecated use replacement method wipeStorage from Adapter interface
126145
* @throws StorageException
146+
* @deprecated use replacement method wipeStorage from Adapter interface
127147
*/
128148
public function flushRedis(): void
129149
{
@@ -212,6 +232,27 @@ function (array $metric): MetricFamilySamples {
212232
);
213233
}
214234

235+
/**
236+
* Inspects the given exception and reconnects the client if the reported error indicates that the server
237+
* went away or is in readonly mode, which may happen in case of a Redis Sentinel failover.
238+
*/
239+
private function reconnectIfRedisIsUnavailableOrReadonly(RedisException $exception): bool
240+
{
241+
// We convert the exception message to lower-case in order to perform case-insensitive comparison.
242+
$exceptionMessage = strtolower($exception->getMessage());
243+
244+
// Because we also match only partial exception messages, we cannot use in_array() at this point.
245+
foreach (self::ERROR_MESSAGES_INDICATING_UNAVAILABILITY as $errorMessage) {
246+
if (str_contains($exceptionMessage, $errorMessage)) {
247+
// Here we reconnect through Redis Sentinel if we lost connection to the server or if another unavailability occurred.
248+
// We may actually reconnect to the same, broken server. But after a failover occured, we should be ok.
249+
// It may take a moment until the Sentinel returns the new master, so this may be triggered multiple times.
250+
return true;
251+
}
252+
}
253+
return false;
254+
}
255+
215256
/**
216257
* @throws StorageException
217258
*/
@@ -221,7 +262,24 @@ private function ensureOpenConnection(): void
221262
return;
222263
}
223264

224-
$this->connectToServer();
265+
while (true) {
266+
try {
267+
$this->options = $this->isSentinel($this->options);
268+
$this->connectToServer();
269+
break;
270+
} catch (\RedisException $e) {
271+
$retry = $this->reconnectIfRedisIsUnavailableOrReadonly($e);
272+
if (!$retry) {
273+
throw new StorageException(
274+
sprintf("Can't connect to Redis server. %s", $e->getMessage()),
275+
$e->getCode(),
276+
$e
277+
);
278+
}
279+
}
280+
}
281+
282+
225283
$authParams = [];
226284

227285
if (isset($this->options['user']) && $this->options['user'] !== '') {
@@ -250,28 +308,20 @@ private function ensureOpenConnection(): void
250308
*/
251309
private function connectToServer(): void
252310
{
253-
try {
254-
$connection_successful = false;
255-
if ($this->options['persistent_connections'] !== false) {
256-
$connection_successful = $this->redis->pconnect(
257-
$this->options['host'],
258-
(int) $this->options['port'],
259-
(float) $this->options['timeout']
260-
);
261-
} else {
262-
$connection_successful = $this->redis->connect($this->options['host'], (int) $this->options['port'], (float) $this->options['timeout']);
263-
}
264-
if (!$connection_successful) {
265-
throw new StorageException(
266-
sprintf("Can't connect to Redis server. %s", $this->redis->getLastError()),
267-
null
268-
);
269-
}
270-
} catch (\RedisException $e) {
311+
$connection_successful = false;
312+
if ($this->options['persistent_connections'] !== false) {
313+
$connection_successful = $this->redis->pconnect(
314+
$this->options['host'],
315+
(int) $this->options['port'],
316+
(float) $this->options['timeout']
317+
);
318+
} else {
319+
$connection_successful = $this->redis->connect($this->options['host'], (int) $this->options['port'], (float) $this->options['timeout']);
320+
}
321+
if (!$connection_successful) {
271322
throw new StorageException(
272-
sprintf("Can't connect to Redis server. %s", $e->getMessage()),
273-
$e->getCode(),
274-
$e
323+
sprintf("Can't connect to Redis server. %s", $this->redis->getLastError()),
324+
null
275325
);
276326
}
277327
}

src/Prometheus/Storage/RedisSentinel.php

Lines changed: 0 additions & 140 deletions
This file was deleted.

0 commit comments

Comments
 (0)