Skip to content
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

#66 distribute the read operation on Redis Cache #132

Merged
merged 9 commits into from
Jan 4, 2018
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Yii Framework 2 redis extension Change Log
2.0.8 under development
-----------------------

- no changes in this release.
- Enh #66: Cache component can be configured to read / get from replicas (ryusoft)


2.0.7 December 11, 2017
Expand Down
84 changes: 82 additions & 2 deletions Cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,28 @@
* ]
* ~~~
*
* If you have multiple redis replicas (e.g. AWS ElasticCache Redis) you can configure the cache to
* send read operations to the replicas. If no replicas are configured, all operations will be performed on the
* master connection configured via the [[redis]] property.
*
* ~~~
* [
* 'components' => [
* 'cache' => [
* 'class' => 'yii\redis\Cache',
* 'enableReplicas' => true,
* 'replicas' => [
* // config for replica redis connections, (default class will be yii\redis\Connection if not provided)
* // you can optionally put in master as hostname as well, as all GET operation will use replicas
* 'redis',//id of Redis [[Connection]] Component
* ['hostname' => 'redis-slave-002.xyz.0001.apse1.cache.amazonaws.com'],
* ['hostname' => 'redis-slave-003.xyz.0001.apse1.cache.amazonaws.com'],
* ],
* ],
* ],
* ]
* ~~~
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
Expand All @@ -66,6 +88,36 @@ class Cache extends \yii\caching\Cache
* with a Redis [[Connection]] object.
*/
public $redis = 'redis';
/**
* @var bool whether to enable read / get from redis replicas.
* @since 2.0.8
* @see $replicas
*/
public $enableReplicas = false;
/**
* @var array the Redis [[Connection]] configurations for redis replicas.
* Each entry is a class configuration, which will be used to instantiate a replica connection.
* The default class is [[Connection|yii\redis\Connection]]. You should at least provide a hostname.
*
* Configuration example:
*
* ```php
* 'replicas' => [
* 'redis',
* ['hostname' => 'redis-slave-002.xyz.0001.apse1.cache.amazonaws.com'],
* ['hostname' => 'redis-slave-003.xyz.0001.apse1.cache.amazonaws.com'],
* ],
* ```
*
* @since 2.0.8
* @see $enableReplicas
*/
public $replicas = [];

/**
* @var Connection currently active connection.
*/
private $_replica;


/**
Expand Down Expand Up @@ -99,15 +151,15 @@ public function exists($key)
*/
protected function getValue($key)
{
return $this->redis->executeCommand('GET', [$key]);
return $this->getReplica()->executeCommand('GET', [$key]);
}

/**
* @inheritdoc
*/
protected function getValues($keys)
{
$response = $this->redis->executeCommand('MGET', $keys);
$response = $this->getReplica()->executeCommand('MGET', $keys);
$result = [];
$i = 0;
foreach ($keys as $key) {
Expand Down Expand Up @@ -195,4 +247,32 @@ protected function flushValues()
{
return $this->redis->executeCommand('FLUSHDB');
}

/**
* It will return the current Replica Redis [[Connection]], and fall back to default [[redis]] [[Connection]]
* defined in this instance. Only used in getValue() and getValues().
* @since 2.0.8
* @return array|string|Connection
* @throws \yii\base\InvalidConfigException
*/
protected function getReplica()
{
if ($this->enableReplicas === false) {
return $this->redis;
}

if ($this->_replica !== null) {
return $this->_replica;
}

if (empty($this->replicas)) {
return $this->_replica = $this->redis;
}

$replicas = $this->replicas;
shuffle($replicas);
$config = array_shift($replicas);
$this->_replica = Instance::ensure($config, Connection::className());
return $this->_replica;
}
}
64 changes: 64 additions & 0 deletions tests/RedisCacheTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace yiiunit\extensions\redis;

use yii\base\InvalidConfigException;
use yii\redis\Cache;
use yii\redis\Connection;
use yiiunit\framework\caching\CacheTestCase;
Expand Down Expand Up @@ -39,6 +40,12 @@ protected function getCacheInstance()
return $this->_cacheInstance;
}

protected function resetCacheInstance()
{
$this->getCacheInstance()->flush();
$this->_cacheInstance = null;
}

public function testExpireMilliseconds()
{
$cache = $this->getCacheInstance();
Expand Down Expand Up @@ -120,4 +127,61 @@ public function testMultiByteGetAndSet()
$cache->set($key, $data);
$this->assertSame($cache->get($key), $data);
}

public function testReplica()
{
$this->resetCacheInstance();

$cache = $this->getCacheInstance();
$cache->enableReplicas = true;

$key = 'replica-1';
$value = 'replica';

//No Replica listed
$this->assertFalse($cache->get($key));
$cache->set($key, $value);
$this->assertSame($cache->get($key), $value);

$cache->replicas = [
['hostname' => 'localhost'],
];
$this->assertSame($cache->get($key), $value);

//One Replica listed
$this->resetCacheInstance();
$cache = $this->getCacheInstance();
$cache->enableReplicas = true;
$cache->replicas = [
['hostname' => 'localhost'],
];
$this->assertFalse($cache->get($key));
$cache->set($key, $value);
$this->assertSame($cache->get($key), $value);

//Multiple Replicas listed
$this->resetCacheInstance();
$cache = $this->getCacheInstance();
$cache->enableReplicas = true;

$cache->replicas = [
['hostname' => 'localhost'],
['hostname' => 'localhost'],
];
$this->assertFalse($cache->get($key));
$cache->set($key, $value);
$this->assertSame($cache->get($key), $value);

//invalid config
$this->resetCacheInstance();
$cache = $this->getCacheInstance();
$cache->enableReplicas = true;

$cache->replicas = ['redis'];
$this->assertFalse($cache->get($key));
$cache->set($key, $value);
$this->assertSame($cache->get($key), $value);

$this->resetCacheInstance();
}
}