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.
		
		
		
		
		
			
		
			
				
					
					
						
							338 lines
						
					
					
						
							10 KiB
						
					
					
				
			
		
		
	
	
							338 lines
						
					
					
						
							10 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
 | 
						|
     * @return Credis_Client
 | 
						|
     * @throws CredisException
 | 
						|
     */
 | 
						|
    public function client($alias)
 | 
						|
    {
 | 
						|
        if (is_int($alias) && isset($this->clients[$alias])) {
 | 
						|
            return $this->clients[$alias];
 | 
						|
        } elseif (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;
 | 
						|
            } elseif ($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);
 | 
						|
    }
 | 
						|
}
 | 
						|
 |