一致性哈希算法——PHP实现代码
1 <?php 2 /** 3 * Flexihash - A simple consistent hashing implementation for PHP. 4 * 5 * The MIT License 6 * 7 * Copyright (c) 2008 Paul Annesley 8 * 9 * Permission is hereby granted, free of charge, to any person obtaining a copy 10 * of this software and associated documentation files (the "Software"), to deal 11 * in the Software without restriction, including without limitation the rights 12 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 * copies of the Software, and to permit persons to whom the Software is 14 * furnished to do so, subject to the following conditions: 15 * 16 * The above copyright notice and this permission notice shall be included in 17 * all copies or substantial portions of the Software. 18 * 19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 * THE SOFTWARE. 26 * 27 * @author Paul Annesley 28 * @link http://paul.annesley.cc/ 29 * @copyright Paul Annesley, 2008 30 * @comment by MyZ (http://blog.csdn.net/mayongzhan) 31 */ 32 33 /** 34 * A simple consistent hashing implementation with pluggable hash algorithms. 35 * 36 * @author Paul Annesley 37 * @package Flexihash 38 * @licence http://www.opensource.org/licenses/mit-license.php 39 */ 40 class Flexihash 41 { 42 43 /** 44 * The number of positions to hash each target to. 45 * 46 * @var int 47 * @comment 虚拟节点数,解决节点分布不均的问题 48 */ 49 private $_replicas = 64; 50 51 /** 52 * The hash algorithm, encapsulated in a Flexihash_Hasher implementation. 53 * @var object Flexihash_Hasher 54 * @comment 使用的hash方法 : md5,crc32 55 */ 56 private $_hasher; 57 58 /** 59 * Internal counter for current number of targets. 60 * @var int 61 * @comment 节点记数器 62 */ 63 private $_targetCount = 0; 64 65 /** 66 * Internal map of positions (hash outputs) to targets 67 * @var array { position => target, ... } 68 * @comment 位置对应节点,用于lookup中根据位置确定要访问的节点 69 */ 70 private $_positionToTarget = array(); 71 72 /** 73 * Internal map of targets to lists of positions that target is hashed to. 74 * @var array { target => [ position, position, ... ], ... } 75 * @comment 节点对应位置,用于删除节点 76 */ 77 private $_targetToPositions = array(); 78 79 /** 80 * Whether the internal map of positions to targets is already sorted. 81 * @var boolean 82 * @comment 是否已排序 83 */ 84 private $_positionToTargetSorted = false; 85 86 /** 87 * Constructor 88 * @param object $hasher Flexihash_Hasher 89 * @param int $replicas Amount of positions to hash each target to. 90 * @comment 构造函数,确定要使用的hash方法和需拟节点数,虚拟节点数越多,分布越均匀,但程序的分布式运算越慢 91 */ 92 public function __construct(Flexihash_Hasher $hasher = null, $replicas = null) 93 { 94 $this->_hasher = $hasher ? $hasher : new Flexihash_Crc32Hasher(); 95 if (!empty($replicas)) $this->_replicas = $replicas; 96 } 97 98 /** 99 * Add a target. 100 * @param string $target 101 * @chainable 102 * @comment 添加节点,根据虚拟节点数,将节点分布到多个虚拟位置上 103 */ 104 public function addTarget($target) 105 { 106 if (isset($this->_targetToPositions[$target])) 107 { 108 throw new Flexihash_Exception("Target '$target' already exists."); 109 } 110 111 $this->_targetToPositions[$target] = array(); 112 113 // hash the target into multiple positions 114 for ($i = 0; $i < $this->_replicas; $i++) 115 { 116 $position = $this->_hasher->hash($target . $i); 117 $this->_positionToTarget[$position] = $target; // lookup 118 $this->_targetToPositions[$target] []= $position; // target removal 119 } 120 121 $this->_positionToTargetSorted = false; 122 $this->_targetCount++; 123 124 return $this; 125 } 126 127 /** 128 * Add a list of targets. 129 * @param array $targets 130 * @chainable 131 */ 132 public function addTargets($targets) 133 { 134 foreach ($targets as $target) 135 { 136 $this->addTarget($target); 137 } 138 139 return $this; 140 } 141 142 /** 143 * Remove a target. 144 * @param string $target 145 * @chainable 146 */ 147 public function removeTarget($target) 148 { 149 if (!isset($this->_targetToPositions[$target])) 150 { 151 throw new Flexihash_Exception("Target '$target' does not exist."); 152 } 153 154 foreach ($this->_targetToPositions[$target] as $position) 155 { 156 unset($this->_positionToTarget[$position]); 157 } 158 159 unset($this->_targetToPositions[$target]); 160 161 $this->_targetCount--; 162 163 return $this; 164 } 165 166 /** 167 * A list of all potential targets 168 * @return array 169 */ 170 public function getAllTargets() 171 { 172 return array_keys($this->_targetToPositions); 173 } 174 175 /** 176 * Looks up the target for the given resource. 177 * @param string $resource 178 * @return string 179 */ 180 public function lookup($resource) 181 { 182 $targets = $this->lookupList($resource, 1); 183 if (empty($targets)) throw new Flexihash_Exception('No targets exist'); 184 return $targets[0]; 185 } 186 187 /** 188 * Get a list of targets for the resource, in order of precedence. 189 * Up to $requestedCount targets are returned, less if there are fewer in total. 190 * 191 * @param string $resource 192 * @param int $requestedCount The length of the list to return 193 * @return array List of targets 194 * @comment 查找当前的资源对应的节点, 195 * 节点为空则返回空,节点只有一个则返回该节点, 196 * 对当前资源进行hash,对所有的位置进行排序,在有序的位置列上寻找当前资源的位置 197 * 当全部没有找到的时候,将资源的位置确定为有序位置的第一个(形成一个环) 198 * 返回所找到的节点 199 */ 200 public function lookupList($resource, $requestedCount) 201 { 202 if (!$requestedCount) 203 throw new Flexihash_Exception('Invalid count requested'); 204 205 // handle no targets 206 if (empty($this->_positionToTarget)) 207 return array(); 208 209 // optimize single target 210 if ($this->_targetCount == 1) 211 return array_unique(array_values($this->_positionToTarget)); 212 213 // hash resource to a position 214 $resourcePosition = $this->_hasher->hash($resource); 215 216 $results = array(); 217 $collect = false; 218 219 $this->_sortPositionTargets(); 220 221 // search values above the resourcePosition 222 foreach ($this->_positionToTarget as $key => $value) 223 { 224 // start collecting targets after passing resource position 225 if (!$collect && $key > $resourcePosition) 226 { 227 $collect = true; 228 } 229 230 // only collect the first instance of any target 231 if ($collect && !in_array($value, $results)) 232 { 233 $results []= $value; 234 } 235 236 // return when enough results, or list exhausted 237 if (count($results) == $requestedCount || count($results) == $this->_targetCount) 238 { 239 return $results; 240 } 241 } 242 243 // loop to start - search values below the resourcePosition 244 foreach ($this->_positionToTarget as $key => $value) 245 { 246 if (!in_array($value, $results)) 247 { 248 $results []= $value; 249 } 250 251 // return when enough results, or list exhausted 252 if (count($results) == $requestedCount || count($results) == $this->_targetCount) 253 { 254 return $results; 255 } 256 } 257 258 // return results after iterating through both "parts" 259 return $results; 260 } 261 262 public function __toString() 263 { 264 return sprintf( 265 '%s{targets:[%s]}', 266 get_class($this), 267 implode(',', $this->getAllTargets()) 268 ); 269 } 270 271 // ---------------------------------------- 272 // private methods 273 274 /** 275 * Sorts the internal mapping (positions to targets) by position 276 */ 277 private function _sortPositionTargets() 278 { 279 // sort by key (position) if not already 280 if (!$this->_positionToTargetSorted) 281 { 282 ksort($this->_positionToTarget, SORT_REGULAR); 283 $this->_positionToTargetSorted = true; 284 } 285 } 286 287 } 288 289 290 /** 291 * Hashes given values into a sortable fixed size address space. 292 * 293 * @author Paul Annesley 294 * @package Flexihash 295 * @licence http://www.opensource.org/licenses/mit-license.php 296 */ 297 interface Flexihash_Hasher 298 { 299 300 /** 301 * Hashes the given string into a 32bit address space. 302 * 303 * Note that the output may be more than 32bits of raw data, for example 304 * hexidecimal characters representing a 32bit value. 305 * 306 * The data must have 0xFFFFFFFF possible values, and be sortable by 307 * PHP sort functions using SORT_REGULAR. 308 * 309 * @param string 310 * @return mixed A sortable format with 0xFFFFFFFF possible values 311 */ 312 public function hash($string); 313 314 } 315 316 317 /** 318 * Uses CRC32 to hash a value into a signed 32bit int address space. 319 * Under 32bit PHP this (safely) overflows into negatives ints. 320 * 321 * @author Paul Annesley 322 * @package Flexihash 323 * @licence http://www.opensource.org/licenses/mit-license.php 324 */ 325 class Flexihash_Crc32Hasher 326 implements Flexihash_Hasher 327 { 328 329 /* (non-phpdoc) 330 * @see Flexihash_Hasher::hash() 331 */ 332 public function hash($string) 333 { 334 return crc32($string); 335 } 336 337 } 338 339 340 /** 341 * Uses CRC32 to hash a value into a 32bit binary string data address space. 342 * 343 * @author Paul Annesley 344 * @package Flexihash 345 * @licence http://www.opensource.org/licenses/mit-license.php 346 */ 347 class Flexihash_Md5Hasher 348 implements Flexihash_Hasher 349 { 350 351 /* (non-phpdoc) 352 * @see Flexihash_Hasher::hash() 353 */ 354 public function hash($string) 355 { 356 return substr(md5($string), 0, 8); // 8 hexits = 32bit 357 358 // 4 bytes of binary md5 data could also be used, but 359 // performance seems to be the same. 360 } 361 362 } 363 364 365 /** 366 * An exception thrown by Flexihash. 367 * 368 * @author Paul Annesley 369 * @package Flexihash 370 * @licence http://www.opensource.org/licenses/mit-license.php 371 */ 372 class Flexihash_Exception extends Exception 373 { 374 }
From:http://blog.csdn.net/mayongzhan/article/details/4298834