ZooKeeper JavaScript Implementation
Def from Apache: ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services.
ZooKeeper Node Class Structure:
class ZooNode {
key = null;
value = null;
watchers = [];
children = [];
constructor(key, value, watcherFn) {
this.key = key;
this.value = value;
if (watcherFn) {
this.watchers.push(watcherFn);
}
}
}
ZooKeeper Implementation:
class ZooKeeper {
root = new ZooNode('root');
/*get path segments*/
getPathSegments(path) {
const ps = path.split('/');
ps.shift();
return ps;
}
/*call watchers*/
callWatchers(fns) {
fns.forEach(fn => fn?.());
}
/*find a node and it's watchers*/
dfs(root, pathSegments, idx, watcherFns = []) {
if (!pathSegments.length) {
return { node: root, watcherFns: [...root.watchers] };
}
const key = pathSegments[idx];
watcherFns = watcherFns.concat(root.watchers);
const nodeIdx = root.children.map(p => p.key).indexOf(key);
if (nodeIdx > -1) {
const nextRoot = root.children[nodeIdx];
if (pathSegments.length === idx + 1) {
watcherFns = watcherFns.concat(nextRoot.watchers);
return { node: nextRoot, watcherFns };
}
return this.dfs(nextRoot, pathSegments, idx + 1, watcherFns);
}
}
create(path, value) {
const ps = this.getPathSegments(path, true);
const newChildKey = ps.pop();
const { node: parentNode, watcherFns } = this.dfs(this.root, ps, 0) || {};
if (parentNode) {
parentNode.children.push(new ZooNode(newChildKey, value));
this.callWatchers(watcherFns);
} else {
console.log(`unable to add, path is not exist: ${path}`);
}
}
setValue(path, value) {
const ps = this.getPathSegments(path, true);
const { node, watcherFns } = this.dfs(this.root, ps, 0) || {};
if (node) {
node.value = value;
this.callWatchers(watcherFns);
} else {
console.log(`unable to update, path is not exist: ${path}`);
}
}
getValue(path) {
const ps = this.getPathSegments(path, true);
const { node } = this.dfs(this.root, ps, 0) || {};
if (node) {
return node.value;
} else {
console.log(`unable to update, path is not exist: ${path}`);
}
}
watch(path, fn) {
const ps = this.getPathSegments(path, true);
const { node } = this.dfs(this.root, ps, 0) || {};
if (node) {
node.watchers = [fn];//concat for multiple listeners
} else {
console.log(`unable to update, path is not exist: ${path}`);
}
}
}
Execution:
let z = new ZooKeeper();
z.create('/a', 'foo');
z.create('/a/c', 'bar');
z.create('/a/d', 'foo_d');
z.setValue('/a/d', 'foo_d_update');
console.log(z.getValue('/a/d'));
//foo_d_update
z.watch('/a/d', () => { console.log('a watch at /a/d') });
z.watch('/a', () => { console.log('a watch at /a') });
z.setValue('/a/d', 'foo_d_update_again');
//1. a watch at /a
//2. a watch at /a/d
z.setValue('/a', 'foo_a_update_again');
//a watch at /a