A super easy PHP Framework for web development! https://github.com/exacti/phacil-framework
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

344 lines
9.3 KiB

<?php
/**
* Credis, a Redis interface for the modest
*
* @author Justin Poliey <jdp34@njit.edu>
* @copyright 2009 Justin Poliey <jdp34@njit.edu>
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
* @package Credis
*/
/**
* A generalized Credis_Client interface for a cluster of Redis servers
*
* @deprecated
*/
class Credis_Cluster
{
/**
* Collection of Credis_Client objects attached to Redis servers
* @var Credis_Client[]
*/
protected $clients;
/**
* If a server is set as master, all write commands go to that one
* @var Credis_Client
*/
protected $masterClient;
/**
* Aliases of Credis_Client objects attached to Redis servers, used to route commands to specific servers
* @see Credis_Cluster::to
* @var array
*/
protected $aliases;
/**
* Hash ring of Redis server nodes
* @var array
*/
protected $ring;
/**
* Individual nodes of pointers to Redis servers on the hash ring
* @var array
*/
protected $nodes;
/**
* The commands that are not subject to hashing
* @var array
* @access protected
*/
protected $dont_hash;
/**
* Currently working cluster-wide database number.
* @var int
*/
protected $selectedDb = 0;
/**
* Creates an interface to a cluster of Redis servers
* Each server should be in the format:
* array(
* 'host' => hostname,
* 'port' => port,
* 'db' => db,
* 'password' => password,
* 'timeout' => timeout,
* 'alias' => alias,
* 'persistent' => persistence_identifier,
* 'master' => master
* 'write_only'=> true/false
* )
*
* @param array $servers The Redis servers in the cluster.
* @param int $replicas
* @param bool $standAlone
* @throws CredisException
*/
public function __construct($servers, $replicas = 128, $standAlone = false)
{
$this->clients = array();
$this->masterClient = null;
$this->aliases = array();
$this->ring = array();
$this->replicas = (int)$replicas;
$client = null;
foreach ($servers as $server)
{
if(is_array($server)){
$client = new Credis_Client(
$server['host'],
$server['port'],
isset($server['timeout']) ? $server['timeout'] : 2.5,
isset($server['persistent']) ? $server['persistent'] : '',
isset($server['db']) ? $server['db'] : 0,
isset($server['password']) ? $server['password'] : null
);
if (isset($server['alias'])) {
$this->aliases[$server['alias']] = $client;
}
if(isset($server['master']) && $server['master'] === true){
$this->masterClient = $client;
if(isset($server['write_only']) && $server['write_only'] === true){
continue;
}
}
} elseif($server instanceof Credis_Client){
$client = $server;
} else {
throw new CredisException('Server should either be an array or an instance of Credis_Client');
}
if($standAlone) {
$client->forceStandalone();
}
$this->clients[] = $client;
for ($replica = 0; $replica <= $this->replicas; $replica++) {
$md5num = hexdec(substr(md5($client->getHost().':'.$client->getPort().'-'.$replica),0,7));
$this->ring[$md5num] = count($this->clients)-1;
}
}
ksort($this->ring, SORT_NUMERIC);
$this->nodes = array_keys($this->ring);
$this->dont_hash = array_flip(array(
'RANDOMKEY', 'DBSIZE', 'PIPELINE', 'EXEC',
'SELECT', 'MOVE', 'FLUSHDB', 'FLUSHALL',
'SAVE', 'BGSAVE', 'LASTSAVE', 'SHUTDOWN',
'INFO', 'MONITOR', 'SLAVEOF'
));
if($this->masterClient !== null && count($this->clients()) == 0){
$this->clients[] = $this->masterClient;
for ($replica = 0; $replica <= $this->replicas; $replica++) {
$md5num = hexdec(substr(md5($this->masterClient->getHost().':'.$this->masterClient->getHost().'-'.$replica),0,7));
$this->ring[$md5num] = count($this->clients)-1;
}
$this->nodes = array_keys($this->ring);
}
}
/**
* @param Credis_Client $masterClient
* @param bool $writeOnly
* @return Credis_Cluster
*/
public function setMasterClient(Credis_Client $masterClient, $writeOnly=false)
{
if(!$masterClient instanceof Credis_Client){
throw new CredisException('Master client should be an instance of Credis_Client');
}
$this->masterClient = $masterClient;
if (!isset($this->aliases['master'])) {
$this->aliases['master'] = $masterClient;
}
if(!$writeOnly){
$this->clients[] = $this->masterClient;
for ($replica = 0; $replica <= $this->replicas; $replica++) {
$md5num = hexdec(substr(md5($this->masterClient->getHost().':'.$this->masterClient->getHost().'-'.$replica),0,7));
$this->ring[$md5num] = count($this->clients)-1;
}
$this->nodes = array_keys($this->ring);
}
return $this;
}
/**
* Get a client by index or alias.
*
* @param string|int $alias
* @throws CredisException
* @return Credis_Client
*/
public function client($alias)
{
if (is_int($alias) && isset($this->clients[$alias])) {
return $this->clients[$alias];
}
else if (isset($this->aliases[$alias])) {
return $this->aliases[$alias];
}
throw new CredisException("Client $alias does not exist.");
}
/**
* Get an array of all clients
*
* @return array|Credis_Client[]
*/
public function clients()
{
return $this->clients;
}
/**
* Execute a command on all clients
*
* @return array
*/
public function all()
{
$args = func_get_args();
$name = array_shift($args);
$results = array();
foreach($this->clients as $client) {
$results[] = call_user_func_array([$client, $name], $args);
}
return $results;
}
/**
* Get the client that the key would hash to.
*
* @param string $key
* @return \Credis_Client
*/
public function byHash($key)
{
return $this->clients[$this->hash($key)];
}
/**
* @param int $index
* @return void
*/
public function select($index)
{
$this->selectedDb = (int) $index;
}
/**
* Execute a Redis command on the cluster with automatic consistent hashing and read/write splitting
*
* @param string $name
* @param array $args
* @return mixed
*/
public function __call($name, $args)
{
if($this->masterClient !== null && !$this->isReadOnlyCommand($name)){
$client = $this->masterClient;
}elseif (count($this->clients()) == 1 || isset($this->dont_hash[strtoupper($name)]) || !isset($args[0])) {
$client = $this->clients[0];
}
else {
$hashKey = $args[0];
if (is_array($hashKey)) {
$hashKey = join('|', $hashKey);
}
$client = $this->byHash($hashKey);
}
// Ensure that current client is working on the same database as expected.
if ($client->getSelectedDb() != $this->selectedDb) {
$client->select($this->selectedDb);
}
return call_user_func_array([$client, $name], $args);
}
/**
* Get client index for a key by searching ring with binary search
*
* @param string $key The key to hash
* @return int The index of the client object associated with the hash of the key
*/
public function hash($key)
{
$needle = hexdec(substr(md5($key),0,7));
$server = $min = 0;
$max = count($this->nodes) - 1;
while ($max >= $min) {
$position = (int) (($min + $max) / 2);
$server = $this->nodes[$position];
if ($needle < $server) {
$max = $position - 1;
}
else if ($needle > $server) {
$min = $position + 1;
}
else {
break;
}
}
return $this->ring[$server];
}
public function isReadOnlyCommand($command)
{
static $readOnlyCommands = array(
'DBSIZE' => true,
'INFO' => true,
'MONITOR' => true,
'EXISTS' => true,
'TYPE' => true,
'KEYS' => true,
'SCAN' => true,
'RANDOMKEY' => true,
'TTL' => true,
'GET' => true,
'MGET' => true,
'SUBSTR' => true,
'STRLEN' => true,
'GETRANGE' => true,
'GETBIT' => true,
'LLEN' => true,
'LRANGE' => true,
'LINDEX' => true,
'SCARD' => true,
'SISMEMBER' => true,
'SINTER' => true,
'SUNION' => true,
'SDIFF' => true,
'SMEMBERS' => true,
'SSCAN' => true,
'SRANDMEMBER' => true,
'ZRANGE' => true,
'ZREVRANGE' => true,
'ZRANGEBYSCORE' => true,
'ZREVRANGEBYSCORE' => true,
'ZCARD' => true,
'ZSCORE' => true,
'ZCOUNT' => true,
'ZRANK' => true,
'ZREVRANK' => true,
'ZSCAN' => true,
'HGET' => true,
'HMGET' => true,
'HEXISTS' => true,
'HLEN' => true,
'HKEYS' => true,
'HVALS' => true,
'HGETALL' => true,
'HSCAN' => true,
'PING' => true,
'AUTH' => true,
'SELECT' => true,
'ECHO' => true,
'QUIT' => true,
'OBJECT' => true,
'BITCOUNT' => true,
'TIME' => true,
'SORT' => true,
);
return array_key_exists(strtoupper($command), $readOnlyCommands);
}
}