In this lesson, you will learn how to synchronize concurrent requests across multiple server instances using Redis to ensure that only one process modifies a shared resource at a time.
The Challenge of Distributed Scaling
When your Node.js application scales horizontally, you often run multiple instances of your server behind a load balancer. If two different instances receive a request to update the same record (like deducting inventory) at the exact same millisecond, they might both read the same "initial" state. This leads to a race condition where data becomes inconsistent because each instance is unaware of the other's operation.
Implementing a Distributed Lock
Since local memory locks only affect one specific process, we need a "global" synchronization point. Redis is perfect for this because it is centralized and supports atomic operations.
The strategy is simple: before performing a sensitive operation, your middleware attempts to set a unique "key" in Redis. If the key already exists, the process must wait or reject the request. If the key is successfully set, the process performs the task and then deletes the key to "release" the lock.
Here is a simplified look at how this logic fits into a middleware flow:
async function distributedLockMiddleware(req, res, next) {
const lockKey = `lock:${req.body.resourceId}`;
// Attempt to set the lock with a TTL to prevent deadlocks
const acquired = await redis.set(lockKey, 'locked', 'NX', 'EX', 10);
if (!acquired) {
return res.status(423).send('Resource is currently being modified.');
}
try {
await next(); // Proceed to the business logic
} finally {
await redis.del(lockKey); // Release the lock
}
}
Why This Works
By using the NX (Not eXists) flag in Redis, the operation only succeeds if the key is absent, guaranteeing that only one request acquires the "token." We also include an expiration (EX) time. This is a critical safety measure; if your Node.js instance crashes while holding the lock, the TTL ensures the lock will automatically expire, preventing your system from being permanently blocked.
This approach effectively turns your distributed environment into a single-file line for specific resources, maintaining data integrity without requiring a shared state within the Node.js application memory.
Key Takeaway: Use Redis atomic operations to create a global synchronization point that forces concurrent processes to wait, effectively preventing data corruption during distributed request handling.