一致性哈希算法——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

posted @ 2013-06-21 16:10  李秋  阅读(859)  评论(0编辑  收藏  举报