├── .gitignore ├── index.js ├── lib ├── index.js └── consistent_hashing.js ├── .travis.yml ├── examples └── app.js ├── package.json ├── LICENSE ├── README.md └── test └── consistent_hashing.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib'); 2 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./consistent_hashing'); 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: "node_js" 2 | node_js: 3 | - 0.6 4 | - 0.8 5 | - 0.10 6 | - 0.11 7 | - 0.12 8 | - "iojs" 9 | - 4.0 10 | - 4.1 11 | -------------------------------------------------------------------------------- /examples/app.js: -------------------------------------------------------------------------------- 1 | var ConsistentHashing = require('..'); 2 | var cons = new ConsistentHashing(["node1", "node2", "node3", "node4", "node5"]); 3 | 4 | var nodes = {}; 5 | var chars = [ 6 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 7 | 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 8 | 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' 9 | ]; 10 | 11 | chars.forEach(function(c) { 12 | var node = cons.getNode(c); 13 | 14 | if (nodes[node]) { 15 | nodes[node].push(c); 16 | } else { 17 | nodes[node] = []; 18 | nodes[node].push(c); 19 | } 20 | }); 21 | 22 | console.log(nodes); 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "consistent-hashing", 3 | "version": "0.3.0", 4 | "description": "A pure JavaScript implementation of Consistent Hashing", 5 | "author": "Dai Akatsuka ", 6 | "homepage": "https://github.com/dakatsuka/node-cosistent-hashing", 7 | "main": "./index.js", 8 | "scripts": { 9 | "test": "mocha -R spec" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/dakatsuka/node-consistent-hashing.git" 14 | }, 15 | "directories": { "lib": "./lib" }, 16 | "engines": { 17 | "node": ">= 0.6" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/dakatsuka/node-cosistent-hashing/issues" 21 | }, 22 | "keywords": ["consistent", "hashing"], 23 | "license": "MIT", 24 | "devDependencies": { 25 | "mocha": "~> 2.3", 26 | "should": "~> 7.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Dai Akatsuka 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-consistent-hashing [![Build Status](https://travis-ci.org/dakatsuka/node-consistent-hashing.svg)](https://travis-ci.org/dakatsuka/node-consistent-hashing) [![npm version](https://badge.fury.io/js/consistent-hashing.svg)](https://badge.fury.io/js/consistent-hashing) 2 | 3 | A pure JavaScript implementation of Consistent Hashing for Node.js. 4 | 5 | ## Installation 6 | 7 | ``` 8 | $ npm install consistent-hashing 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```javascript 14 | var ConsistentHashing = require('consistent-hashing'); 15 | var cons = new ConsistentHashing(["node1", "node2", "node3"]); 16 | 17 | var nodes = {}; 18 | var chars = [ 19 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 20 | 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 21 | 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' 22 | ]; 23 | 24 | chars.forEach(function(c) { 25 | var node = cons.getNode(c); 26 | 27 | if (nodes[node]) { 28 | nodes[node].push(c); 29 | } else { 30 | nodes[node] = []; 31 | nodes[node].push(c); 32 | } 33 | }); 34 | 35 | console.log(nodes); 36 | 37 | // { node3: [ 'A', 'F', 'H', 'J', 'N', 'S', 'U', 'W', 'X' ], 38 | // node1: [ 'B', 'C', 'E', 'G', 'L', 'M', 'Q', 'R', 'V', 'Y', 'Z' ], 39 | // node2: [ 'D', 'I', 'K', 'O', 'P', 'T' ] } 40 | ``` 41 | 42 | add nodes: 43 | 44 | ```javascript 45 | cons.addNode("node4"); 46 | ``` 47 | 48 | remove node: 49 | 50 | ```javascript 51 | cons.removeNode("node1"); 52 | ``` 53 | 54 | ## Copyright 55 | 56 | Copyright (C) 2011-2015 Dai Akatsuka, released under the MIT License. 57 | -------------------------------------------------------------------------------- /test/consistent_hashing.test.js: -------------------------------------------------------------------------------- 1 | var should = require('should'); 2 | var ConsistentHashing = require('../'); 3 | 4 | describe('ConsistentHashing', function() { 5 | var nodes = ["node1", "node2", "node3"]; 6 | 7 | before(function(done) { 8 | Array.prototype.contains = function(value) { 9 | for (var i in this) { 10 | if (this.hasOwnProperty(i) && this[i] == value) { 11 | return true; 12 | } 13 | } 14 | return false; 15 | }; 16 | done(); 17 | }); 18 | 19 | it('should be set default values', function(done) { 20 | var cons = new ConsistentHashing(nodes); 21 | cons.replicas.should.equal(160); 22 | cons.algorithm.should.equal('md5'); 23 | done(); 24 | }); 25 | 26 | it('should be able to change value of replicas', function(done) { 27 | var cons = new ConsistentHashing(nodes, { replicas: 300 }); 28 | cons.replicas.should.equal(300); 29 | done(); 30 | }); 31 | 32 | it('should be able to change algorithm', function(done) { 33 | var cons = new ConsistentHashing(nodes, { algorithm: 'sha1' }); 34 | cons.algorithm.should.equal('sha1'); 35 | done(); 36 | }); 37 | 38 | it('should add nodes', function(done) { 39 | var cons = new ConsistentHashing(nodes); 40 | cons.addNode("node4"); 41 | cons.nodes.contains("node4").should.be.true; 42 | cons.keys.length.should.equal(cons.replicas * 4); 43 | done(); 44 | }); 45 | 46 | it('should remove node', function(done) { 47 | var cons = new ConsistentHashing(nodes); 48 | cons.removeNode("node1"); 49 | cons.nodes.contains("node1").should.be.false; 50 | cons.keys.length.should.equal(cons.replicas * 2); 51 | done(); 52 | }); 53 | 54 | it('should create an array for sort', function(done) { 55 | var cons = new ConsistentHashing(nodes); 56 | cons.keys.length.should.equal(cons.replicas * nodes.length); 57 | done(); 58 | }); 59 | 60 | it('should get a node from key', function(done) { 61 | var cons = new ConsistentHashing(nodes); 62 | cons.getNode('A').should.equal("node3"); 63 | cons.getNode('B').should.equal("node1"); 64 | cons.getNode('C').should.equal("node1"); 65 | done(); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /lib/consistent_hashing.js: -------------------------------------------------------------------------------- 1 | var crypto = require('crypto'); 2 | 3 | 4 | var ConsistentHashing = function(nodes, options) { 5 | this.replicas = 160; 6 | this.algorithm = 'md5' 7 | this.ring = {}; 8 | this.keys = []; 9 | this.nodes = []; 10 | 11 | if (options && options.replicas) this.replicas = options.replicas; 12 | if (options && options.algorithm) this.algorithm = options.algorithm; 13 | 14 | for (var i = 0; i < nodes.length; i++) { 15 | this.addNode(nodes[i]); 16 | } 17 | }; 18 | 19 | 20 | ConsistentHashing.prototype.addNode = function(node) { 21 | this.nodes.push(node); 22 | 23 | for (var i = 0; i < this.replicas; i++) { 24 | var key = this.crypto((node.id || node) + ':' + i); 25 | 26 | this.keys.push(key); 27 | this.ring[key] = node; 28 | } 29 | 30 | this.keys.sort(); 31 | }; 32 | 33 | 34 | ConsistentHashing.prototype.removeNode = function(node) { 35 | for (var i = 0; i < this.nodes.length; i++) { 36 | if (this.nodes[i] == node) { 37 | this.nodes.splice(i, 1); 38 | i--; 39 | } 40 | } 41 | 42 | for (var i = 0; i < this.replicas; i++) { 43 | var key = this.crypto((node.id || node) + ':' + i); 44 | delete this.ring[key]; 45 | 46 | for (var j = 0; j < this.keys.length; j++) { 47 | if (this.keys[j] == key) { 48 | this.keys.splice(j, 1); 49 | j--; 50 | } 51 | } 52 | } 53 | }; 54 | 55 | 56 | ConsistentHashing.prototype.getNode = function(key) { 57 | if (this.getRingLength() == 0) return 0; 58 | 59 | var hash = this.crypto(key); 60 | var pos = this.getNodePosition(hash); 61 | 62 | return this.ring[this.keys[pos]]; 63 | }; 64 | 65 | 66 | ConsistentHashing.prototype.getNodePosition = function(hash) { 67 | var upper = this.getRingLength() - 1; 68 | var lower = 0; 69 | var idx = 0; 70 | var comp = 0; 71 | 72 | if (upper == 0) return 0; 73 | 74 | while (lower <= upper) { 75 | idx = Math.floor((lower + upper) / 2); 76 | comp = this.compare(this.keys[idx], hash); 77 | 78 | if (comp == 0) { 79 | return idx; 80 | } else if (comp > 0) { 81 | upper = idx - 1; 82 | } else { 83 | lower = idx + 1; 84 | } 85 | } 86 | 87 | if (upper < 0) { 88 | upper = this.getRingLength() - 1; 89 | } 90 | 91 | return upper; 92 | }; 93 | 94 | 95 | ConsistentHashing.prototype.getRingLength = function() { 96 | return Object.keys(this.ring).length; 97 | }; 98 | 99 | 100 | ConsistentHashing.prototype.compare = function(v1, v2) { 101 | return v1 > v2 ? 1 : v1 < v2 ? -1 : 0; 102 | }; 103 | 104 | 105 | ConsistentHashing.prototype.crypto = function(str) { 106 | return crypto.createHash(this.algorithm).update(str).digest('hex'); 107 | }; 108 | 109 | 110 | module.exports = ConsistentHashing; 111 | --------------------------------------------------------------------------------