MediaWiki
REL1_23
|
00001 <?php 00029 class HashRing { 00031 protected $sourceMap = array(); 00033 protected $ring = array(); 00034 00035 const RING_SIZE = 268435456; // 2^28 00036 00040 public function __construct( array $map ) { 00041 $map = array_filter( $map, function ( $w ) { 00042 return $w > 0; 00043 } ); 00044 if ( !count( $map ) ) { 00045 throw new UnexpectedValueException( "Ring is empty or all weights are zero." ); 00046 } 00047 $this->sourceMap = $map; 00048 // Sort the locations based on the hash of their names 00049 $hashes = array(); 00050 foreach ( $map as $location => $weight ) { 00051 $hashes[$location] = sha1( $location ); 00052 } 00053 uksort( $map, function ( $a, $b ) use ( $hashes ) { 00054 return strcmp( $hashes[$a], $hashes[$b] ); 00055 } ); 00056 // Fit the map to weight-proportionate one with a space of size RING_SIZE 00057 $sum = array_sum( $map ); 00058 $standardMap = array(); 00059 foreach ( $map as $location => $weight ) { 00060 $standardMap[$location] = (int)floor( $weight / $sum * self::RING_SIZE ); 00061 } 00062 // Build a ring of RING_SIZE spots, with each location at a spot in location hash order 00063 $index = 0; 00064 foreach ( $standardMap as $location => $weight ) { 00065 // Location covers half-closed interval [$index,$index + $weight) 00066 $this->ring[$location] = array( $index, $index + $weight ); 00067 $index += $weight; 00068 } 00069 // Make sure the last location covers what is left 00070 end( $this->ring ); 00071 $this->ring[key( $this->ring )][1] = self::RING_SIZE; 00072 } 00073 00080 public function getLocation( $item ) { 00081 $locations = $this->getLocations( $item, 1 ); 00082 00083 return $locations[0]; 00084 } 00085 00093 public function getLocations( $item, $limit ) { 00094 $locations = array(); 00095 $primaryLocation = null; 00096 $spot = hexdec( substr( sha1( $item ), 0, 7 ) ); // first 28 bits 00097 foreach ( $this->ring as $location => $range ) { 00098 if ( count( $locations ) >= $limit ) { 00099 break; 00100 } 00101 // The $primaryLocation is the location the item spot is in. 00102 // After that is reached, keep appending the next locations. 00103 if ( ( $range[0] <= $spot && $spot < $range[1] ) || $primaryLocation !== null ) { 00104 if ( $primaryLocation === null ) { 00105 $primaryLocation = $location; 00106 } 00107 $locations[] = $location; 00108 } 00109 } 00110 // If more locations are requested, wrap-around and keep adding them 00111 reset( $this->ring ); 00112 while ( count( $locations ) < $limit ) { 00113 list( $location, ) = each( $this->ring ); 00114 if ( $location === $primaryLocation ) { 00115 break; // don't go in circles 00116 } 00117 $locations[] = $location; 00118 } 00119 00120 return $locations; 00121 } 00122 00128 public function getLocationWeights() { 00129 return $this->sourceMap; 00130 } 00131 00138 public function newWithoutLocation( $location ) { 00139 $map = $this->sourceMap; 00140 unset( $map[$location] ); 00141 if ( count( $map ) ) { 00142 return new self( $map ); 00143 } 00144 00145 return false; 00146 } 00147 }