Cluster.php 4.2 KB

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