Added support for optionally weighted nodes in the hashring.

This commit is contained in:
Daniele Alessandri
2010-03-31 11:47:33 +02:00
parent 435fdeab75
commit 6ea96dcfea
+57 -23
View File
@@ -1269,44 +1269,78 @@ namespace Predis\Utilities;
class HashRing {
const DEFAULT_REPLICAS = 128;
const DEFAULT_WEIGHT = 100;
private $_nodes, $_ring, $_ringKeys, $_replicas;
public function __construct($replicas = self::DEFAULT_REPLICAS) {
$this->_replicas = $replicas;
$this->_nodes = array();
$this->_ring = array();
$this->_ringKeys = array();
}
public function add($node) {
public function add($node, $weight = self::DEFAULT_WEIGHT) {
// NOTE: in case of collisions in the hashes of the nodes, the node added
// last wins, thus the order in which nodes are added is significant.
$this->_nodes[] = $node;
$nodeHash = (string) $node;
$replicas = $this->_replicas;
for ($i = 0; $i < $replicas; $i++) {
$key = crc32($nodeHash . ':' . $i);
$this->_ring[$key] = $node;
}
ksort($this->_ring, SORT_NUMERIC);
$this->_ringKeys = array_keys($this->_ring);
$this->_nodes[] = array('object' => $node, 'weight' => (int) $weight);
$this->reset();
}
public function remove($node) {
// NOTE: a node is removed by recreating the whole ring from scratch, in
// order to reassign possible hashes with collisions to the right node
// according to the order in which they were added in the first place.
$oldNodes = $this->_nodes;
$this->_nodes = array();
$this->_ring = array();
$this->_ringKeys = array();
foreach ($oldNodes as $oldNode) {
if ($oldNode !== $node) {
$this->add($oldNode);
// NOTE: a node is removed by resetting the ring so that it's recreated from
// scratch, in order to reassign possible hashes with collisions to the
// right node according to the order in which they were added in the
// first place.
for ($i = 0; $i < count($this->_nodes); ++$i) {
if ($this->_nodes[$i]['object'] === $node) {
array_splice($this->_nodes, $i, 1);
$this->reset();
break;
}
}
}
private function reset() {
unset($this->_ring);
unset($this->_ringKeys);
}
private function isInitialized() {
return isset($this->_ringKeys);
}
private function computeTotalWeight() {
// TODO: array_reduce + lambda for PHP 5.3
$totalWeight = 0;
foreach ($this->_nodes as $node) {
$totalWeight += $node['weight'];
}
return $totalWeight;
}
private function initialize() {
if ($this->isInitialized()) {
return;
}
if (count($this->_nodes) === 0) {
throw new \LogicException('Cannot initialize empty hashring');
}
$this->_ring = array();
$totalWeight = $this->computeTotalWeight();
$nodesCount = count($this->_nodes);
foreach ($this->_nodes as $node) {
$nodeObject = $node['object'];
$nodeHash = (string) $nodeObject;
$weightRatio = $node['weight'] / $totalWeight;
$replicas = (int) round($weightRatio * $nodesCount * $this->_replicas);
for ($i = 0; $i < $replicas; $i++) {
$key = crc32($nodeHash . ':' . $i);
$this->_ring[$key] = $nodeObject;
}
}
ksort($this->_ring, SORT_NUMERIC);
$this->_ringKeys = array_keys($this->_ring);
}
public function get($key) {
return $this->_ring[$this->getNodeKey($key)];
}
@@ -1315,10 +1349,10 @@ class HashRing {
// NOTE: binary search for the last item in _ringkeys with a value
// less or equal to the key. If no such item exists, return the
// last item.
$this->initialize();
$ringKeys = $this->_ringKeys;
$upper = count($ringKeys) - 1;
$lower = 0;
$index = 0;
while ($lower <= $upper) {
$index = ($lower + $upper) >> 1;