Metaparticle/Sync for Javascript is a library that implements distributed synchronization for cloud-native applications using a container side-car and Kubernetes primitives.
Metaparticle/Sync for Javascript can be used for locking or for leader election
To add the @metaparticle/sync
library to your code you need to do two things:
- Import the library, this is commonly done with:
var mp = require('@metaparticle/sync');
- Run the
elector
side-car container. This is typically done via a Kubernetes Deployment (see examples below)
The simplest usage is to deploy mutual exclusion locks between different distributed components
Here's the code for a simple locking example, that locks a lock named test
and holds it for 45 seconds.
// Import the library
var mp = require('@metaparticle/sync');
// Create a new lock.
var lock = new mp.Lock(
// The name of the lock.
'test',
// This handler is called when the lock is acquired.
() => {
console.log('I have the lock!');
console.log('Holding the lock for 45 seconds');
setTimeout(() => {
// Unlock after 45 seconds.
lock.unlock();
console.log('Unlocked');
}, 45 * 1000);
},
// [optional] this handler is called when the lock is lost
() => {
console.log('I lost the lock!');
});
// Kick off the lock, eventually this will call the callbacks above.
console.log('Attempting to lock');
lock.lock();
You'll notice that a lock is made up of three things:
- A name (this should be unique for a cluster)
- A callback function to be called when the lock is acquired.
- An optional callback function to be called when the lock is lost. If this is not supplied, the program will forcibly exit in the (unlikely) case that a lock is lost.
Simply creating a lock doesn't cause mutual exclusion. You also need to call lock.lock()
. When
you are done, you call lock.unlock()
to release the lock. Locks have a TTL (time to live) so
in the event of a failure, the lock will also be eventually lost.
To deploy code using the @metaparticle/sync
package, you need to also include a side-car that
does the heavy lifting for the lock. Your code and the sidecar should both be package as containers
and then deployed as a Kubernetes Pod.
Here is an example Kubernetes deployment:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
run: lock-js
name: lock-js
namespace: default
spec:
replicas: 2
selector:
matchLabels:
run: lock-js
template:
metadata:
labels:
run: lock-js
spec:
containers:
- image: brendanburns/elector
name: elector
- image: brendanburns/sync-js
name: example
You can create this with kubectl create -f lock-deploy.yaml
which will create two different Pods, both of which are trying to obtain a lock named test
.
An extension of locking is leader election where a leader is chosen from a group of replicas. This leader remains the leader for as long as it is healthy. If the leader ever fails, a new leader is chosen. This is an extremely useful pattern for implementing a variety of distributed systems. Generally leader election is performed for a named shard which represents some piece of data to be owned/maintained by the leader.
Implementing leader election in @metaparticle/sync
is simple, here is code that performs
leader election for a shard named test
.
var mp = require('@metaparticle/sync');
var election = new mp.Election(
// Name of the election shard
'test',
// Event handler, called when a program becomes the leader.
() => {
console.log('I am the leader');
},
// Event handler, called when a program that was leader is no longer leader.
() => {
console.log('I lost the leader');
});
election.run();
As with locking, you need to deploy the elector side-car to take advantage of @metaparticle/sync
elections. Here's an example Kubernetes Deployment which deploys three leader replicas:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
run: elector-js
name: elector-js
namespace: default
spec:
replicas: 3
selector:
matchLabels:
run: elector-js
template:
metadata:
labels:
run: elector-js
spec:
containers:
- image: brendanburns/elector
imagePullPolicy: Always
name: elector
resources: {}
# Replace the container below with your container.
- image: brendanburns/sync-js
name: example
command:
- node
- elector.js
If you are interested in the technical details of how this all works, please see the overview.