Sfoglia il codice sorgente

Added support for optionally weighted nodes in the hashring.

Daniele Alessandri 15 anni fa
parent
commit
6ea96dcfea
1 ha cambiato i file con 57 aggiunte e 23 eliminazioni
  1. 57 23
      lib/Predis.php

+ 57 - 23
lib/Predis.php

@@ -1269,42 +1269,76 @@ 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) {
@@ -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;