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

Redis cluster refactoring #178

Merged
merged 15 commits into from
Nov 5, 2019
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.11 under development
------------------------

- no changes in this release.
- Enh #66, #134, #135, #136, #142, #143, #147: Support Redis in cluster mode (hofrob)


2.0.10 October 22, 2019
Expand Down
89 changes: 89 additions & 0 deletions src/Cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace yii\redis;

use Yii;
use yii\db\Exception;
use yii\di\Instance;

/**
Expand Down Expand Up @@ -75,6 +76,22 @@
* ]
* ~~~
*
* If you're using redis in cluster mode and want to use `MGET` and `MSET` effectively, you will need to supply a
* [hash tag](https://redis.io/topics/cluster-spec#keys-hash-tags) to allocate cache keys to the same hash slot.
*
* ~~~
* \Yii::$app->cache->multiSet([
* 'posts{user1}' => 123,
* 'settings{user1}' => [
* 'showNickname' => false,
* 'sortBy' => 'created_at',
* ],
* 'unreadMessages{user1}' => 5,
* ]);
* ~~~
*
* @property bool $isCluster
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
Expand Down Expand Up @@ -113,11 +130,26 @@ class Cache extends \yii\caching\Cache
* @see $enableReplicas
*/
public $replicas = [];
/**
* @var bool|null force cluster mode, don't check on every request. If this is null, cluster mode will be checked
* once per request whenever the cache is accessed. To disable the check, set to true if cluster mode
* should be enabled, or false if it should be disabled.
* @since 2.0.11
*/
public $forceClusterMode;

/**
* @var Connection currently active connection.
*/
private $_replica;
/**
* @var bool remember if redis is in cluster mode for the whole request
*/
private $_isCluster;
/**
* @var bool if hash tags were supplied for a MGET/MSET operation
*/
private $_hashTagAvailable = false;


/**
Expand Down Expand Up @@ -159,16 +191,37 @@ protected function getValue($key)
*/
protected function getValues($keys)
{
if ($this->isCluster && !$this->_hashTagAvailable) {
return parent::getValues($keys);
}

$response = $this->getReplica()->executeCommand('MGET', $keys);
$result = [];
$i = 0;
foreach ($keys as $key) {
$result[$key] = $response[$i++];
}

$this->_hashTagAvailable = false;

return $result;
}

public function buildKey($key)
{
if (
is_string($key)
&& $this->isCluster
&& preg_match('/^(.*)({.+})(.*)$/', $key, $matches) === 1) {

$this->_hashTagAvailable = true;

return parent::buildKey($matches[1] . $matches[3]) . $matches[2];
}

return parent::buildKey($key);
}

/**
* @inheritdoc
*/
Expand All @@ -188,6 +241,10 @@ protected function setValue($key, $value, $expire)
*/
protected function setValues($data, $expire)
{
if ($this->isCluster && !$this->_hashTagAvailable) {
return parent::setValues($data, $expire);
}

$args = [];
foreach ($data as $key => $value) {
$args[] = $key;
Expand Down Expand Up @@ -215,9 +272,41 @@ protected function setValues($data, $expire)
}
}

$this->_hashTagAvailable = false;

return $failedKeys;
}

/**
* Returns `true` if the redis extension is forced to run in cluster mode through config or the redis command
* `CLUSTER INFO` executes successfully, `false` otherwise.
*
* Setting [[forceClusterMode]] to either `true` or `false` is preferred.
* @return bool whether redis is running in cluster mode or not
* @since 2.0.11
*/
public function getIsCluster()
hofrob marked this conversation as resolved.
Show resolved Hide resolved
{
if ($this->forceClusterMode !== null) {
return $this->forceClusterMode;
}

if ($this->_isCluster === null) {
$this->_isCluster = false;
try {
$this->redis->executeCommand('CLUSTER INFO');
$this->_isCluster = true;
} catch (Exception $exception) {
samdark marked this conversation as resolved.
Show resolved Hide resolved
// if redis is running without cluster support, this command results in:
// `ERR This instance has cluster support disabled`
// and [[Connection::executeCommand]] throws an exception
// we want to ignore it
}
}

return $this->_isCluster;
}

/**
* @inheritdoc
*/
Expand Down
Loading