Flexihash下载地址:
https://github.com/pda/flexihash

一致性hash大多用于缓存集群中,为了使在缓存中由于一台或多台服务器宕机,导致后端数据库压力过大而崩溃,他对添加和减少缓存服务器迁移的数据量最小化,相对于取模来说

对于一致性hash的原理和介绍,网上有很多,我这里就不转帖了

<? php
 
/**
* A simple consistent hashing implementation with pluggable hash algorithms.
*
* @author Paul Annesley
* @package Flexihash
* @licence http://www.opensource.org/licenses/mit-license.php
*/
class  Flexihash
{
 
/**
* The number of positions to hash each target to.
*
* @var int
*/
private  $_replicas  =  64 ;
 
/**
* The hash algorithm, encapsulated in a Flexihash_Hasher implementation.
* @var object Flexihash_Hasher
*/
private  $_hasher ;
 
/**
* Internal counter for current number of targets.
* @var int
*/
private  $_targetCount  =  0 ;
 
/**
* Internal map of positions (hash outputs) to targets
* @var array { position => target, ... }
*/
private  $_positionToTarget  =  array ();
 
/**
* Internal map of targets to lists of positions that target is hashed to.
* @var array { target => [ position, position, ... ], ... }
*/
private  $_targetToPositions  =  array ();
 
/**
* Whether the internal map of positions to targets is already sorted.
* @var boolean
*/
private  $_positionToTargetSorted  =  false ;
 
/**
* Constructor
* @param object $hasher Flexihash_Hasher
* @param int $replicas Amount of positions to hash each target to.
*/
public  function  __construct ( Flexihash_Hasher  $hasher  =  null ,  $replicas  =  null )
{
$this -> _hasher  =  $hasher  ?  $hasher  :  new  Flexihash_Crc32Hasher ();
if  ( ! empty ( $replicas ))  $this -> _replicas  =  $replicas ;
}
 
/**
* Add a target.
* @param string $target
* @param float $weight
* @chainable
*/
public  function  addTarget ( $target ,  $weight = 1 )
{
if  ( isset ( $this -> _targetToPositions [ $target ]))
{
throw  new  Flexihash_Exception ( "Target ' $target ' already exists." );
}
 
$this -> _targetToPositions [ $target ]  =  array ();
 
// hash the target into multiple positions
for  ( $i  =  0 ;  $i  <  round ( $this -> _replicas * $weight );  $i ++ )
{
$position  =  $this -> _hasher -> hash ( $target  .  $i );
$this -> _positionToTarget [ $position ]  =  $target ;  // lookup
$this -> _targetToPositions [ $target ]  [] =  $position ;  // target removal
}
 
$this -> _positionToTargetSorted  =  false ;
$this -> _targetCount ++ ;
 
return  $this ;
}
 
/**
* Add a list of targets.
* @param array $targets
* @param float $weight
* @chainable
*/
public  function  addTargets ( $targets ,  $weight = 1 )
{
foreach  ( $targets  as  $target )
{
$this -> addTarget ( $target , $weight );
}
 
return  $this ;
}
 
/**
* Remove a target.
* @param string $target
* @chainable
*/
public  function  removeTarget ( $target )
{
if  ( ! isset ( $this -> _targetToPositions [ $target ]))
{
throw  new  Flexihash_Exception ( "Target ' $target ' does not exist." );
}
 
foreach  ( $this -> _targetToPositions [ $target ]  as  $position )
{
unset ( $this -> _positionToTarget [ $position ]);
}
 
unset ( $this -> _targetToPositions [ $target ]);
 
$this -> _targetCount -- ;
 
return  $this ;
}
 
/**
* A list of all potential targets
* @return array
*/
public  function  getAllTargets ()
{
return  array_keys ( $this -> _targetToPositions );
}
 
/**
* Looks up the target for the given resource.
* @param string $resource
* @return string
*/
public  function  lookup ( $resource )
{
$targets  =  $this -> lookupList ( $resource ,  1 );
if  ( empty ( $targets ))  throw  new  Flexihash_Exception ( 'No targets exist' );
return  $targets [ 0 ];
}
 
/**
* Get a list of targets for the resource, in order of precedence.
* Up to $requestedCount targets are returned, less if there are fewer in total.
*
* @param string $resource
* @param int $requestedCount The length of the list to return
* @return array List of targets
*/
public  function  lookupList ( $resource ,  $requestedCount )
{
if  ( ! $requestedCount )
throw  new  Flexihash_Exception ( 'Invalid count requested' );
 
// handle no targets
if  ( empty ( $this -> _positionToTarget ))
return  array ();
 
// optimize single target
if  ( $this -> _targetCount  ==  1 )
return  array_unique ( array_values ( $this -> _positionToTarget ));
 
// hash resource to a position
$resourcePosition  =  $this -> _hasher -> hash ( $resource );
 
$results  =  array ();
$collect  =  false ;
 
$this -> _sortPositionTargets ();
 
// search values above the resourcePosition
foreach  ( $this -> _positionToTarget  as  $key  =>  $value )
{
// start collecting targets after passing resource position
if  ( ! $collect  &&  $key  >  $resourcePosition )
{
$collect  =  true ;
}
 
// only collect the first instance of any target
if  ( $collect  &&  ! in_array ( $value ,  $results ))
{
$results  [] =  $value ;
}
 
// return when enough results, or list exhausted
if  ( count ( $results )  ==  $requestedCount  ||  count ( $results )  ==  $this -> _targetCount )
{
return  $results ;
}
}
 
// loop to start - search values below the resourcePosition
foreach  ( $this -> _positionToTarget  as  $key  =>  $value )
{
if  ( ! in_array ( $value ,  $results ))
{
$results  [] =  $value ;
}
 
// return when enough results, or list exhausted
if  ( count ( $results )  ==  $requestedCount  ||  count ( $results )  ==  $this -> _targetCount )
{
return  $results ;
}
}
 
// return results after iterating through both "parts"
return  $results ;
}
 
public  function  __toString ()
{
return  sprintf (
'%s{targets:[%s]}' ,
get_class ( $this ),
implode ( ',' ,  $this -> getAllTargets ())
);
}
 
// ----------------------------------------
// private methods
 
/**
* Sorts the internal mapping (positions to targets) by position
*/
private  function  _sortPositionTargets ()
{
// sort by key (position) if not already
if  ( ! $this -> _positionToTargetSorted )
{
ksort ( $this -> _positionToTarget ,  SORT_REGULAR );
$this -> _positionToTargetSorted  =  true ;
}
}
 
}




以下是测试用的code:

<?php

$hash = new Flexihash();

// bulk add
$hash->addTargets(array('cache-1', 'cache-2', 'cache-3'));

// simple lookup
$hash->lookup('object-a'); // "cache-1"
$hash->lookup('object-b'); // "cache-2"

// add and remove
$hash
  ->addTarget('cache-4')
  ->removeTarget('cache-1');

// lookup with next-best fallback (for redundant writes)
$hash->lookupList('object', 2); // ["cache-2", "cache-4"]

// remove cache-2, expect object to hash to cache-4
$hash->removeTarget('cache-2');
$hash->lookup('object'); // "cache-4"


?>