occaecatidicta
Version:
110 lines (91 loc) • 2.97 kB
text/typescript
import * as crypto from 'crypto';
import { RpcServerInfo } from '../rpc-client/mailstation';
let getKeysLength = function<T>(map: {[key: string]: T}) {
return Object.keys(map).length;
};
let hash = function (algorithm: string, str: string) {
return crypto.createHash(algorithm).update(str).digest('hex');
};
let compare = function (v1: string, v2: string): number {
return v1 > v2 ? 1 : v1 < v2 ? -1 : 0;
};
export class ConsistentHash {
ring: {[key: string]: any} = {};
keys: {[key: string]: any} = [];
nodes: Array<RpcServerInfo> = [];
opts: any;
replicas: number;
algorithm: string;
station: any;
constructor(nodes: Array<RpcServerInfo>, opts: any) {
this.opts = opts || {};
this.replicas = this.opts.replicas || 100;
this.algorithm = this.opts.algorithm || 'md5';
this.station = this.opts.station;
for (let i = 0; i < nodes.length; i++) {
this.addNode(nodes[i]);
}
this.station.on('addServer', this.addNode.bind(this));
this.station.on('removeServer', this.removeNode.bind(this));
}
addNode(node: RpcServerInfo) {
this.nodes.push(node);
for (let i = 0; i < this.replicas; i++) {
let key = hash(this.algorithm, (node.id || node) + ':' + i);
this.keys.push(key);
this.ring[key] = node;
}
this.keys.sort();
}
removeNode(node: RpcServerInfo) {
for (let i = 0; i < this.nodes.length; i++) {
if (this.nodes[i] === node) {
this.nodes.splice(i, 1);
i--;
}
}
for (let j = 0; j < this.replicas; j++) {
let key = hash(this.algorithm, (node.id || node) + ':' + j);
delete this.ring[key];
for (let k = 0; k < this.keys.length; k++) {
if (this.keys[k] === key) {
this.keys.splice(k, 1);
k--;
}
}
}
}
getNode(key: string) {
if (getKeysLength(this.ring) === 0) {
return 0;
}
let result = hash(this.algorithm, key);
let pos = this.getNodePosition(result);
return this.ring[this.keys[pos]];
}
getNodePosition(result: string) {
let upper = getKeysLength(this.ring) - 1;
let lower = 0;
let idx = 0;
let comp = 0;
if (upper === 0) {
return 0;
}
// binary search
while (lower <= upper) {
idx = Math.floor((lower + upper) / 2);
comp = compare(this.keys[idx], result);
if (comp === 0) {
return idx;
} else if (comp > 0) {
upper = idx - 1;
} else {
lower = idx + 1;
}
}
if (upper < 0) {
upper = getKeysLength(this.ring) - 1;
}
return upper;
}
}