MediaWiki  REL1_23
HashRing.php
Go to the documentation of this file.
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 }