Cluster.php 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. <?php
  2. namespace Qii\Cache\Redis;
  3. /**
  4. * Credis, a Redis interface for the modest
  5. *
  6. * @author Justin Poliey <jdp34@njit.edu>
  7. * @copyright 2009 Justin Poliey <jdp34@njit.edu>
  8. * @license http://www.opensource.org/licenses/mit-license.php The MIT License
  9. * @package Credis
  10. */
  11. #require_once 'Credis/Client.php';
  12. /**
  13. * A generalized Credis_Client interface for a cluster of Redis servers
  14. */
  15. class Cluster
  16. {
  17. /**
  18. * Collection of Credis_Client objects attached to Redis servers
  19. * @var Credis_Client[]
  20. */
  21. protected $clients;
  22. /**
  23. * Aliases of Credis_Client objects attached to Redis servers, used to route commands to specific servers
  24. * @see Credis_Cluster::to
  25. * @var array
  26. */
  27. protected $aliases;
  28. /**
  29. * Hash ring of Redis server nodes
  30. * @var array
  31. */
  32. protected $ring;
  33. /**
  34. * Individual nodes of pointers to Redis servers on the hash ring
  35. * @var array
  36. */
  37. protected $nodes;
  38. /**
  39. * The commands that are not subject to hashing
  40. * @var array
  41. * @access protected
  42. */
  43. protected $dont_hash;
  44. /**
  45. * Creates an interface to a cluster of Redis servers
  46. * Each server should be in the format:
  47. * array(
  48. * 'host' => hostname,
  49. * 'port' => port,
  50. * 'timeout' => timeout,
  51. * 'alias' => alias
  52. * )
  53. *
  54. * @param array $servers The Redis servers in the cluster.
  55. * @param int $replicas
  56. */
  57. public function __construct($servers, $replicas = 128)
  58. {
  59. $this->clients = array();
  60. $this->aliases = array();
  61. $this->ring = array();
  62. $clientNum = 0;
  63. foreach ($servers as $server) {
  64. $client = new \Qii\Cache\Redis\Client($server['host'], $server['port'], isset($server['timeout']) ? $server['timeout'] : 2.5, isset($server['persistent']) ? $server['persistent'] : '');
  65. $this->clients[] = $client;
  66. if (isset($server['alias'])) {
  67. $this->aliases[$server['alias']] = $client;
  68. }
  69. for ($replica = 0; $replica <= $replicas; $replica++) {
  70. $this->ring[crc32($server['host'] . ':' . $server['port'] . '-' . $replica)] = $clientNum;
  71. }
  72. $clientNum++;
  73. }
  74. ksort($this->ring, SORT_NUMERIC);
  75. $this->nodes = array_keys($this->ring);
  76. $this->dont_hash = array_flip(array(
  77. 'RANDOMKEY', 'DBSIZE', 'PIPELINE', 'EXEC',
  78. 'SELECT', 'MOVE', 'FLUSHDB', 'FLUSHALL',
  79. 'SAVE', 'BGSAVE', 'LASTSAVE', 'SHUTDOWN',
  80. 'INFO', 'MONITOR', 'SLAVEOF'
  81. ));
  82. }
  83. /**
  84. * Get a client by index or alias.
  85. *
  86. * @param string|int $alias
  87. * @throws CredisException
  88. * @return Credis_Client
  89. */
  90. public function client($alias)
  91. {
  92. if (is_int($alias) && isset($this->clients[$alias])) {
  93. return $this->clients[$alias];
  94. } else if (isset($this->aliases[$alias])) {
  95. return $this->aliases[$alias];
  96. }
  97. throw new CredisException("Client $alias does not exist.");
  98. }
  99. /**
  100. * Get an array of all clients
  101. *
  102. * @return array|Credis_Client[]
  103. */
  104. public function clients()
  105. {
  106. return $this->clients;
  107. }
  108. /**
  109. * Execute a command on all clients
  110. *
  111. * @return array
  112. */
  113. public function all()
  114. {
  115. $args = func_get_args();
  116. $name = array_shift($args);
  117. $results = array();
  118. foreach ($this->clients as $client) {
  119. $results[] = $client->__call($name, $args);
  120. }
  121. return $results;
  122. }
  123. /**
  124. * Get the client that the key would hash to.
  125. *
  126. * @param string $key
  127. * @return \Credis_Client
  128. */
  129. public function byHash($key)
  130. {
  131. return $this->clients[$this->hash($key)];
  132. }
  133. /**
  134. * Execute a Redis command on the cluster with automatic consistent hashing
  135. *
  136. * @param string $name
  137. * @param array $args
  138. * @return mixed
  139. */
  140. public function __call($name, $args)
  141. {
  142. if (isset($this->dont_hash[strtoupper($name)])) {
  143. $client = $this->clients[0];
  144. } else {
  145. $client = $this->byHash($args[0]);
  146. }
  147. return $client->__call($name, $args);
  148. }
  149. /**
  150. * Get client index for a key by searching ring with binary search
  151. *
  152. * @param string $key The key to hash
  153. * @return int The index of the client object associated with the hash of the key
  154. */
  155. public function hash($key)
  156. {
  157. $needle = crc32($key);
  158. $server = $min = 0;
  159. $max = count($this->nodes) - 1;
  160. while ($max >= $min) {
  161. $position = (int)(($min + $max) / 2);
  162. $server = $this->nodes[$position];
  163. if ($needle < $server) {
  164. $max = $position - 1;
  165. } else if ($needle > $server) {
  166. $min = $position + 1;
  167. } else {
  168. break;
  169. }
  170. }
  171. return $this->ring[$server];
  172. }
  173. }