In this lesson, you will learn how to synchronize asynchronous tasks across multiple Node.js instances using a centralized "Distributed Lock."
The Problem: Multi-Node Race Conditions
When you run your application on a single server, you can manage concurrency with local variables or memory-based queues. However, when you scale to multiple nodes, each instance has its own memory. If two nodes attempt to process the same "once-only" task (like generating a monthly financial report or updating a shared database row), they might trigger it simultaneously. This leads to duplicate work or data corruption.
The Solution: Redis as the Arbiter
To coordinate these nodes, we use an external "Source of Truth"—typically Redis. A Distributed Lock is essentially a temporary key stored in Redis that acts as a "Stop" sign.
- Acquire: Before starting a task, a node attempts to create a key in Redis (e.g.,
process_lock) with an expiration time. If the key doesn't exist, the node successfully sets it and proceeds. - Block: If the key already exists, other nodes know someone else is currently handling the task and must wait (or abort).
- Release: Once the task finishes, the node deletes the key, allowing others to compete for the lock next time.
Implementation Concept
Using a library like ioredis, the logic looks like this:
async function performExclusiveTask(redis, lockKey) {
// SET with NX (Only set if not exists) and PX (Expiration in ms)
const isLocked = await redis.set(lockKey, 'locked', 'PX', 5000, 'NX');
if (!isLocked) {
console.log("Task is already being processed by another instance.");
return;
}
try {
await runImportantTask(); // Your async work
} finally {
await redis.del(lockKey); // Release lock
}
}
The PX 5000 is crucial: it ensures that if a node crashes mid-task, the lock automatically expires after 5 seconds, preventing a permanent "deadlock" where no node can ever run the task again. By offloading this coordination to Redis, you ensure that even across a fleet of containers, your asynchronous flows remain atomic and orderly.
Key Takeaway: Distributed locking uses an external, atomic shared state to ensure that only one node in a cluster executes a specific asynchronous task at any given time.