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.
		
		
		
		
		
			
		
			
				
					
					
						
							343 lines
						
					
					
						
							9.3 KiB
						
					
					
				
			
		
		
	
	
							343 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);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 |