Skip to content

Commit 389fb68

Browse files
Merge pull request #138 from colinmollenhour/multiple-read-slaves
Deprecate flawed 'cluster' functionality, add support for multiple st…
2 parents 0ea3e4b + 541d817 commit 389fb68

File tree

2 files changed

+95
-70
lines changed

2 files changed

+95
-70
lines changed

Cm/Cache/Backend/Redis.php

Lines changed: 58 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -150,17 +150,14 @@ protected function getClientOptions($options = array())
150150
*/
151151
public function __construct($options = array())
152152
{
153-
if ( empty($options['server']) ) {
153+
if ( empty($options['server']) && empty($options['cluster'])) {
154154
Zend_Cache::throwException('Redis \'server\' not specified.');
155155
}
156156

157-
$port = isset($options['port']) ? $options['port'] : 6379;
158-
$slaveSelect = isset($options['slave_select_callable']) && is_callable($options['slave_select_callable']) ? $options['slave_select_callable'] : null;
159-
$sentinelMaster = empty($options['sentinel_master']) ? NULL : $options['sentinel_master'];
160-
161157
$this->_clientOptions = $this->getClientOptions($options);
162158

163159
// If 'sentinel_master' is specified then server is actually sentinel and master address should be fetched from server.
160+
$sentinelMaster = empty($options['sentinel_master']) ? NULL : $options['sentinel_master'];
164161
if ($sentinelMaster) {
165162
$sentinelClientOptions = isset($options['sentinel']) && is_array($options['sentinel'])
166163
? $this->getClientOptions($options['sentinel'] + $options)
@@ -220,6 +217,7 @@ public function __construct($options = array())
220217
if ($options['load_from_slaves'] == 2) {
221218
array_push($slaves, $this->_redis); // Also send reads to the master
222219
}
220+
$slaveSelect = isset($options['slave_select_callable']) && is_callable($options['slave_select_callable']) ? $options['slave_select_callable'] : null;
223221
if ($slaveSelect) {
224222
$slave = $slaveSelect($slaves, $this->_redis);
225223
} else {
@@ -240,28 +238,52 @@ public function __construct($options = array())
240238
}
241239

242240
// Instantiate Credis_Cluster
241+
// DEPRECATED
243242
else if ( ! empty($options['cluster'])) {
244243
$this->_setupReadWriteCluster($options);
245244
}
246245

247-
// Direct connection to single Redis server
246+
// Direct connection to single Redis server and optional slaves
248247
else {
248+
$port = isset($options['port']) ? $options['port'] : 6379;
249249
$this->_redis = new Credis_Client($options['server'], $port, $this->_clientOptions->timeout, $this->_clientOptions->persistent);
250250
$this->_applyClientOptions($this->_redis);
251251

252252
// Support loading from a replication slave
253253
if (isset($options['load_from_slave'])) {
254254
if (is_array($options['load_from_slave'])) {
255-
$server = $options['load_from_slave']['server'];
256-
$port = $options['load_from_slave']['port'];
257-
258-
$clientOptions = $this->getClientOptions($options['load_from_slave'] + $options);
259-
} else {
255+
if (isset($options['load_from_slave']['server'])) { // Single slave
256+
$server = $options['load_from_slave']['server'];
257+
$port = $options['load_from_slave']['port'];
258+
$clientOptions = $this->getClientOptions($options['load_from_slave'] + $options);
259+
$totalServers = 2;
260+
} else { // Multiple slaves
261+
$slaveKey = array_rand($options['load_from_slave'], 1);
262+
$slave = $options['load_from_slave'][$slaveKey];
263+
$server = $slave['server'];
264+
$port = $slave['port'];
265+
$clientOptions = $this->getClientOptions($slave + $options);
266+
$totalServers = count($options['load_from_slave']) + 1;
267+
}
268+
} else { // String
260269
$server = $options['load_from_slave'];
261270
$port = 6379;
262271
$clientOptions = $this->_clientOptions;
272+
273+
// If multiple addresses are given, split and choose a random one
274+
if (strpos($server, ',') !== FALSE) {
275+
$slaves = preg_split('/\s*,\s*/', $server, -1, PREG_SPLIT_NO_EMPTY);
276+
$slaveKey = array_rand($slaves, 1);
277+
$server = $slaves[$slaveKey];
278+
$port = NULL;
279+
$totalServers = count($slaves) + 1;
280+
} else {
281+
$totalServers = 2;
282+
}
263283
}
264-
if (is_string($server)) {
284+
// Skip setting up slave if master is not write only and it is randomly chosen to be the read server
285+
$masterWriteOnly = isset($options['master_write_only']) ? (int) $options['master_write_only'] : FALSE;
286+
if (is_string($server) && $server && ! (!$masterWriteOnly && rand(1,$totalServers) === 1)) {
265287
try {
266288
$slave = new Credis_Client($server, $port, $clientOptions->timeout, $clientOptions->persistent);
267289
$this->_applyClientOptions($slave, TRUE, $clientOptions);
@@ -394,50 +416,39 @@ protected function _applyClientOptions(Credis_Client $client, $forceSelect = FAL
394416
}
395417
}
396418

397-
protected function _setupReadWriteCluster($options) {
398-
$clusterNodes = array();
399-
400-
if (array_key_exists('master', $options['cluster']) && !empty($options['cluster']['master'])) {
419+
/**
420+
* @deprecated - Previously this setup an instance of Credis_Cluster but this class was not complete or flawed
421+
* @param $options
422+
*/
423+
protected function _setupReadWriteCluster($options)
424+
{
425+
if (!empty($options['cluster']['master'])) {
401426
foreach ($options['cluster']['master'] as $masterNode) {
402427
if (empty($masterNode['server']) || empty($masterNode['port'])) {
403428
continue;
404429
}
405430

406-
$clusterNodes[] = array(
407-
'host' => $masterNode['server'],
408-
'port' => $masterNode['port'],
409-
'alias' => 'master',
410-
'master' => true,
411-
'write_only' => true,
412-
'timeout' => $this->_clientOptions->timeout,
413-
'persistent' => $this->_clientOptions->persistent,
414-
'db' => (int) $options['database'],
431+
$this->_redis = new Credis_Client(
432+
$masterNode['host'],
433+
$masterNode['port'],
434+
isset($masterNode['timeout']) ? $masterNode['timeout'] : 2.5,
435+
isset($masterNode['persistent']) ? $masterNode['persistent'] : ''
415436
);
416-
417-
break; // limit to 1
437+
$this->_applyClientOptions($this->_redis);
438+
break;
418439
}
419440
}
420441

421-
if (!empty($clusterNodes) && array_key_exists('slave', $options['cluster']) && !empty($options['cluster']['slave'])) {
422-
foreach ($options['cluster']['slave'] as $slaveNodes) {
423-
if (empty($masterNode['server']) || empty($masterNode['port'])) {
424-
continue;
425-
}
426-
427-
$clusterNodes[] = array(
428-
'host' => $slaveNodes['server'],
429-
'port' => $slaveNodes['port'],
430-
'alias' => 'slave' . count($clusterNodes),
431-
'timeout' => $this->_clientOptions->timeout,
432-
'persistent' => $this->_clientOptions->persistent,
433-
'db' => (int) $options['database'],
434-
'password' => $options['password'],
435-
);
436-
}
437-
}
438-
439-
if (!empty($clusterNodes)) {
440-
$this->_redis = new Credis_Cluster($clusterNodes);
442+
if (!empty($options['cluster']['slave'])) {
443+
$slaveKey = array_rand($options['cluster']['slave'], 1);
444+
$slave = $options['cluster']['slave'][$slaveKey];
445+
$this->_slave = new Credis_Client(
446+
$slave['host'],
447+
$slave['port'],
448+
isset($slave['timeout']) ? $slave['timeout'] : 2.5,
449+
isset($slave['persistent']) ? $slave['persistent'] : ''
450+
);
451+
$this->_applyClientOptions($this->_redis, TRUE);
441452
}
442453
}
443454

README.md

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -116,44 +116,58 @@ Example configuration:
116116
<backend_options>
117117
<server>tcp://redis-master:6379</server>
118118
<load_from_slave>tcp://redis-slaves:6379</load_from_slave>
119+
<master_write_only>0</master_write_only> <!-- Use 1 to prevent reads from master -->
120+
<timeout>0.5</timeout>
121+
</backend_options>
122+
</cache>
123+
124+
### Static Configuration
125+
126+
You may also statically specify the master and slave servers by passing either an array to `load_from_slave` or a string
127+
with multiple addresses separated by a comma.
128+
129+
<!-- This is a child node of config/global -->
130+
<cache>
131+
<backend>Cm_Cache_Backend_Redis</backend>
132+
<backend_options>
133+
<server>tcp://redis-master:6379</server>
134+
<load_from_slave>tcp://redis-slave1:6379,tcp://redis-slave2:6379</load_from_slave>
135+
<master_write_only>0</master_write_only> <!-- Use 1 to prevent reads from master -->
119136
<timeout>0.5</timeout>
120137
</backend_options>
121138
</cache>
122139

123140
## ElastiCache
124141

125-
The following example configuration lets you use ElastiCache Redis (cluster mode disabled) where the writes are sent to the Primary node and reads are sent to the replicas. This lets you distribute the read traffic between the different nodes.
142+
The following example configuration lets you use ElastiCache Redis (cluster mode disabled) where the writes are sent to
143+
the Primary node and reads are sent to the replicas. This lets you distribute the read traffic between the different nodes.
126144

127145
The instructions to find the primary and read replica endpoints are [here](http://docs.aws.amazon.com/AmazonElastiCache/latest/UserGuide/Endpoints.html#Endpoints.Find.Redis).
128146

129147
<!-- This is a child node of config/global/cache -->
130148
<backend_options>
131149
<server>primary-endpoint.0001.euw1.cache.amazonaws.com</server>
132150
<port>6379</port>
133-
<database>0</database> <!-- Make sure database is 0 -->
134-
.
135-
. <!-- Other settings -->
136-
.
137-
<cluster>
138-
<master>
139-
<node-001>
140-
<server>primary-endpoint.0001.euw1.cache.amazonaws.com</server>
141-
<port>6379</port>
142-
</node-001>
143-
</master>
144-
<slave>
145-
<node-001>
146-
<server>replica-endpoint-1.jwbaun.0001.euw1.cache.amazonaws.com</server>
147-
<port>6379</port>
148-
</node-001>
149-
<node-002>
150-
<server>replica-endpoint-2.jwbaun.0001.euw1.cache.amazonaws.com</server>
151-
<port>6379</port>
152-
</node-002>
153-
</slave>
154-
</cluster>
151+
<database>0</database> <!-- Make sure database is 0 -->
152+
<master_write_only>1</master_write_only>
153+
<load_from_slave>
154+
<node-001>
155+
<server>replica-endpoint-1.jwbaun.0001.euw1.cache.amazonaws.com</server>
156+
<port>6379</port>
157+
</node-001>
158+
<node-002>
159+
<server>replica-endpoint-2.jwbaun.0001.euw1.cache.amazonaws.com</server>
160+
<port>6379</port>
161+
</node-002>
162+
</load_from_slave>
155163
</backend_options>
156164

165+
#### DEPRECATION NOTICE
166+
167+
Previously the ElastiCache config instructions suggested setting up a `<cluster>` node but this functionality was flawed
168+
and is no longer supported. The config is still parsed and loaded for backwards-compatibility but chooses a random slave
169+
to read from rather than using md5 hash of the keys.
170+
157171
## RELATED / TUNING
158172

159173
- The recommended "maxmemory-policy" is "volatile-lru". All tag metadata is non-volatile so it is

0 commit comments

Comments
 (0)