forked from colinmollenhour/credis
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Sentinel.php
423 lines (392 loc) · 12 KB
/
Sentinel.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
<?php
/**
* Credis_Sentinel
*
* Implements the Sentinel API as mentioned on http://redis.io/topics/sentinel.
* Sentinel is aware of master and slave nodes in a cluster and returns instances of Credis_Client accordingly.
*
* The complexity of read/write splitting can also be abstract by calling the createCluster() method which returns a
* Credis_Cluster object that contains both the master server and a random slave. Credis_Cluster takes care of the
* read/write splitting
*
* @author Thijs Feryn <[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
* @package Credis_Sentinel
*/
class Credis_Sentinel
{
/**
* Contains a client that connects to a Sentinel node.
* Sentinel uses the same protocol as Redis which makes using Credis_Client convenient.
* @var Credis_Client
*/
protected $_client;
/**
* Contains an active instance of Credis_Cluster per master pool
* @var array
*/
protected $_cluster = array();
/**
* Contains an active instance of Credis_Client representing a master
* @var array
*/
protected $_master = array();
/**
* Contains an array Credis_Client objects representing all slaves per master pool
* @var array
*/
protected $_slaves = array();
/**
* Use the phpredis extension or the standalone implementation
* @var bool
* @deprecated
*/
protected $_standAlone = false;
/**
* Store the AUTH password used by Credis_Client instances
* @var string
*/
protected $_password = '';
/**
* Store the AUTH username used by Credis_Client instances (Redis v6+)
* @var string
*/
protected $_username = '';
/**
* @var null|float
*/
protected $_timeout;
/**
* @var string
*/
protected $_persistent;
/**
* @var int
*/
protected $_db;
/**
* @var string|null
*/
protected $_replicaCmd = null;
/**
* @var string|null
*/
protected $_redisVersion = null;
/**
* Connect with a Sentinel node. Sentinel will do the master and slave discovery
*
* @param Credis_Client $client
* @param string $password (deprecated - use setClientPassword)
* @throws CredisException
*/
public function __construct(Credis_Client $client, $password = null, $username = null)
{
$client->forceStandalone(); // SENTINEL command not currently supported by phpredis
$this->_client = $client;
$this->_password = $password;
$this->_username = $username;
$this->_timeout = null;
$this->_persistent = '';
$this->_db = 0;
}
/**
* Clean up client on destruct
*/
public function __destruct()
{
$this->_client->close();
}
/**
* @param float $timeout
* @return $this
*/
public function setClientTimeout($timeout)
{
$this->_timeout = $timeout;
return $this;
}
/**
* @param string $persistent
* @return $this
*/
public function setClientPersistent($persistent)
{
$this->_persistent = $persistent;
return $this;
}
/**
* @param int $db
* @return $this
*/
public function setClientDatabase($db)
{
$this->_db = $db;
return $this;
}
/**
* @param null|string $password
* @return $this
*/
public function setClientPassword($password)
{
$this->_password = $password;
return $this;
}
/**
* @param null|string $username
* @return $this
*/
public function setClientUsername($username)
{
$this->_username = $username;
return $this;
}
/**
* @param null|string $replicaCmd
* @return $this
*/
public function setReplicaCommand($replicaCmd)
{
$this->_replicaCmd = $replicaCmd;
return $this;
}
public function detectRedisVersion()
{
if ($this->_redisVersion !== null && $this->_replicaCmd !== null) {
return;
}
$serverInfo = $this->info('server');
$this->_redisVersion = $serverInfo['redis_version'];
// Redis v7+ renames the replica command to 'replicas' instead of 'slaves'
$this->_replicaCmd = version_compare($this->_redisVersion, '7.0.0', '>=') ? 'replicas' : 'slaves';
}
/**
* @return Credis_Sentinel
* @deprecated
*/
public function forceStandalone()
{
$this->_standAlone = true;
return $this;
}
/**
* Discover the master node automatically and return an instance of Credis_Client that connects to the master
*
* @param string $name
* @return Credis_Client
* @throws CredisException
*/
public function createMasterClient($name)
{
$master = $this->getMasterAddressByName($name);
if (!isset($master[0]) || !isset($master[1])) {
throw new CredisException('Master not found');
}
return new Credis_Client($master[0], $master[1], $this->_timeout, $this->_persistent, $this->_db, $this->_password, $this->_username);
}
/**
* If a Credis_Client object exists for a master, return it. Otherwise create one and return it
* @param string $name
* @return Credis_Client
*/
public function getMasterClient($name)
{
if (!isset($this->_master[$name])) {
$this->_master[$name] = $this->createMasterClient($name);
}
return $this->_master[$name];
}
/**
* Discover the slave nodes automatically and return an array of Credis_Client objects
*
* @param string $name
* @return Credis_Client[]
* @throws CredisException
*/
public function createSlaveClients($name)
{
$slaves = $this->slaves($name);
$workingSlaves = array();
foreach ($slaves as $slave) {
if (!isset($slave[9])) {
throw new CredisException('Can\' retrieve slave status');
}
if (!strstr($slave[9], 's_down') && !strstr($slave[9], 'disconnected')) {
$workingSlaves[] = new Credis_Client($slave[3], $slave[5], $this->_timeout, $this->_persistent, $this->_db, $this->_password, $this->_username);
}
}
return $workingSlaves;
}
/**
* If an array of Credis_Client objects exist for a set of slaves, return them. Otherwise create and return them
* @param string $name
* @return Credis_Client[]
*/
public function getSlaveClients($name)
{
if (!isset($this->_slaves[$name])) {
$this->_slaves[$name] = $this->createSlaveClients($name);
}
return $this->_slaves[$name];
}
/**
* Returns a Redis cluster object containing a random slave and the master
* When $selectRandomSlave is true, only one random slave is passed.
* When $selectRandomSlave is false, all clients are passed and hashing is applied in Credis_Cluster
* When $writeOnly is false, the master server will also be used for read commands.
* When $masterOnly is true, only the master server will also be used for both read and write commands. $writeOnly will be ignored and forced to set to false.
* @param string $name
* @param int $db
* @param int $replicas
* @param bool $selectRandomSlave
* @param bool $writeOnly
* @param bool $masterOnly
* @return Credis_Cluster
* @throws CredisException
* @deprecated
*/
public function createCluster($name, $db = 0, $replicas = 128, $selectRandomSlave = true, $writeOnly = false, $masterOnly = false)
{
$clients = array();
$workingClients = array();
$master = $this->master($name);
if (strstr($master[9], 's_down') || strstr($master[9], 'disconnected')) {
throw new CredisException('The master is down');
}
if (!$masterOnly) {
$slaves = $this->slaves($name);
foreach ($slaves as $slave) {
if (!strstr($slave[9], 's_down') && !strstr($slave[9], 'disconnected')) {
$workingClients[] = array('host' => $slave[3], 'port' => $slave[5], 'master' => false, 'db' => $db, 'password' => $this->_password);
}
}
if (count($workingClients) > 0) {
if ($selectRandomSlave) {
if (!$writeOnly) {
$workingClients[] = array('host' => $master[3], 'port' => $master[5], 'master' => false, 'db' => $db, 'password' => $this->_password);
}
$clients[] = $workingClients[rand(0, count($workingClients) - 1)];
} else {
$clients = $workingClients;
}
}
} else {
$writeOnly = false;
}
$clients[] = array('host' => $master[3], 'port' => $master[5], 'db' => $db, 'master' => true, 'write_only' => $writeOnly, 'password' => $this->_password);
return new Credis_Cluster($clients, $replicas, $this->_standAlone);
}
/**
* If a Credis_Cluster object exists, return it. Otherwise create one and return it.
* @param string $name
* @param int $db
* @param int $replicas
* @param bool $selectRandomSlave
* @param bool $writeOnly
* @param bool $masterOnly
* @return Credis_Cluster
* @throws CredisException
* @deprecated
*/
public function getCluster($name, $db = 0, $replicas = 128, $selectRandomSlave = true, $writeOnly = false, $masterOnly = false)
{
if (!isset($this->_cluster[$name])) {
$this->_cluster[$name] = $this->createCluster($name, $db, $replicas, $selectRandomSlave, $writeOnly, $masterOnly);
}
return $this->_cluster[$name];
}
/**
* Catch-all method
* @param string $name
* @param array $args
* @return mixed
*/
public function __call($name, $args)
{
array_unshift($args, $name);
return call_user_func(array($this->_client, 'sentinel'), $args);
}
/**
* get information block for the sentinel instance
*
* @param string|NUll $section
*
* @return array
*/
public function info($section = null)
{
if ($section) {
return $this->_client->info($section);
}
return $this->_client->info();
}
/**
* Return information about all registered master servers
* @return mixed
*/
public function masters()
{
return $this->_client->sentinel('masters');
}
/**
* Return all information for slaves that are associated with a single master
* @param string $name
* @return mixed
*/
public function slaves($name)
{
if ($this->_replicaCmd === null) {
$this->detectRedisVersion();
}
return $this->_client->sentinel($this->_replicaCmd, $name);
}
/**
* Get the information for a specific master
* @param string $name
* @return mixed
*/
public function master($name)
{
return $this->_client->sentinel('master', $name);
}
/**
* Get the hostname and port for a specific master
* @param string $name
* @return mixed
*/
public function getMasterAddressByName($name)
{
return $this->_client->sentinel('get-master-addr-by-name', $name);
}
/**
* Check if the Sentinel is still responding
* @return string|Credis_Client
*/
public function ping()
{
return $this->_client->ping();
}
/**
* Perform an auto-failover which will re-elect another master and make the current master a slave
* @param string $name
* @return mixed
*/
public function failover($name)
{
return $this->_client->sentinel('failover', $name);
}
/**
* @return string
*/
public function getHost()
{
return $this->_client->getHost();
}
/**
* @return int
*/
public function getPort()
{
return $this->_client->getPort();
}
}