123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- <?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 {
- $client = $this->byHash($args[0]);
- }
- // 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);
- }
- }
|