This article talks about using Redis as a distributed lock provider in a NodeJs application.

Concurrent Access to Shared Resources

At each abstraction level, techniques vary for serializing access to critical section or business processes.

  • Among multiple threads in a process, the programming language's constructs itself can be used. In Java this would be the synchronized keywords or classes from java.util.concurrent
  • When it comes to synchronizing across process on an instance, a lock on a file is probably an option.
  • To co-ordinate among processes on multiple instances again there are many options
    • softlock on an aggregate root in the persistent store.
    • use a distributed co-ordinator like ZooKeeper or Redis

In the platform that I currently work on there are business processes that can be run started by any user. The limitation is only one process can be started at any given time. Since the platform is deployed in AWS on multiple instances, co-ordination is required so that race conditions are eliminated.

Redis as Distributed Coordination Service

Since the platform is built on NodeJs with Redis as session/cache store, it was the chosen option to use as cordination service. Specifically the redlock node module is used to implement the lock mechanism. Redlock uses Redis's volatile keys, that has an expiration time set on the keys.

Below snippet creates a key that expires in 30 secs.

let lock;
try {
    lock = await redlock.lock('subsytem1:locks:dataingestion-1', 30000);    
    //do work
} catch(e) {
    if (!lock) {
        //lock not available
    } else {
        //execution exception
    }
} finally {
    if (lock) {
        try {
            await lock.unlock();
        } catch {
            //ignore
        }
    }
}

Extending lock timeout

If the work is not completed in the timeout specified initially, there is an option to extend the timeout provided it is not already expired.

The native setInterval function is used to extend the timeout by 30 seconds every 25 seconds.

let lock;
let timeoutExtender;
try {
    lock = await redlock.lock('subsytem1:locks:dataingestion-1', 30000);    
    timeoutExtender = setInterval(() => {return lock.extend(30000);}, 25000);

    //do work
} catch(e) {
    if (!lock) {
        //lock not available
    } else {
        //execution exception
    }
} finally {
    clearInterval(timeoutExtender);
    try {
        await lock.unlock();
    } catch {/*ignore*/}
}

Other Options

Of course, ZooKeeper is most complete option as a distributed co-ordination service.