UNPKG

consistent

Version:

Consistent hashing for Node.JS

150 lines (113 loc) 3.19 kB
var crypto = require("crypto"); var lru = require("lru-cache"); function Consistent(options) { if (!(this instanceof Consistent)) return new Consistent(options); options = options || {}; this.replicas = options.replicas || 120; this.members = {}; this.ring = []; if (options.members) { options.members.forEach(this.add.bind(this)); } if (options.hash == 'murmurhash') { this._hash = require("murmurhash").v3; } } Object.defineProperty(Consistent.prototype, "length", { get: function () { return Object.keys(this.members).length; } }); Consistent.prototype.add = function(member) { member = this._makeMember(member); this.members[member.key] = member; this._continuum(); }; Consistent.prototype.remove = function(member) { member = this._makeMember(member); delete this.members[member.key]; this._continuum(); }; Consistent.prototype.replace = function(oldmember, newmember) { oldmember = this._makeMember(oldmember); newmember = this._makeMember(newmember); if (!this.members[oldmember.key]) return; newmember.hash = oldmember.hash; this.members[newmember.key] = newmember; delete this.members[oldmember.key]; this._continuum(); } Consistent.prototype.getCached = function(val) { if (!this.cache) this.cache = lru(500); var ret = this.cache.get(val); if (ret) return ret; ret = this.get(val); this.cache.set(val, ret); return ret; } Consistent.prototype.get = function(val) { if (!this.ring.length) return null; var hash = this._hash(val) , ring = this.ring var low = 0, high = ring.length, mid; while (low <= high) { mid = low + ((high - low) >> 1); if (mid == 0 || mid == ring.length) return ring[0].key; if (ring[mid - 1].hash < hash && ring[mid].hash >= hash) return ring[mid].key; else if (ring[mid].hash < hash) low = mid + 1; else high = mid - 1; } return this.ring[0].key; }; Consistent.prototype.exists = function(member) { return this.members.hasOwnProperty(member); }; Consistent.prototype._makeMember = function(member) { if (typeof member == 'string') member = { key: member }; member.weight = member.weight || 1; member.hash = this._hash(member.key); return member; } Consistent.prototype._continuum = function() { var self = this; this.ring = []; var members = Object.keys(this.members); for (var i = 0; i < members.length; i++) { var m = this.members[members[i]]; for (var j = 0; j < Math.floor(this.replicas * m.weight); j++) { var hash = this._hash(m.hash + "-" + j); this.ring.push({ hash: hash, key: m.key }); }; }; // Sort ring in ascending order this.ring.sort(function (a, b) { return a.hash - b.hash; }); // Remove duplicate adjacent keys on the ring var i = 0; while(i < this.ring.length - 1) { if (this.ring[i].key == this.ring[i + 1].key) { this.ring.splice(i, 1); } else { i++; } } // Clear cache this.cache = null; }; Consistent.prototype._hash = function(val) { return crypto.createHash('md5').update(val).digest().readUInt32LE(0); } module.exports = Consistent;