Published on

在nodejs中使用分布式锁

分布式锁是一种在分布式系统环境下,通过多个节点对共享资源进行访问控制的一种同步机制。它的主要目的是防止多个节点同时操作同一份数据,从而避免数据的不一致性。与线程锁和进程锁相比,分布式锁的实现更为复杂,因为它需要在网络中的多个节点之间进行协调,以保证锁的唯一性和一致性。


分布式锁的使用场景

分布式锁可以应用于所有需要在分布式环境中同步访问共享资源的场景。例如:

  • 电商秒杀活动:为了防止超卖,可以使用分布式锁来保证同一时刻只有一个请求可以操作库存。
  • 分布式计算:为了防止重复计算,可以使用分布式锁来保证同一时刻只有一个节点可以进行计算。
  • 分布式任务调度:在分布式环境中,多个服务实例可能同时调度同一个定时任务。为了避免任务重复执行,可以使用分布式锁保证同一时刻只有一个实例在执行该任务。

在Node.js中使用分布式锁

在Node.js中使用分布式锁,通常会依赖于外部系统(如Redis)来实现。以下是使用Redis在Node.js中实现分布式锁的基本步骤和示例代码:

  1. 安装依赖:需要安装redis和bluebird(用于Promise支持)等依赖包。
bash
npm install redis bluebird
  1. 实现分布式锁:可以使用Redis提供的SETNX命令来设置锁,同时使用Lua脚本来确保释放锁的安全性。
js
const redis = require('redis');
const bluebird = require('bluebird');
bluebird.promisifyAll(redis);
const client = redis.createClient();

// 获取锁
const acquireLock = async (lockKey, lockValue, expiration) => {
	const result = await client.set(lockKey, lockValue, 'NX', 'EX', expiration);
	return result === 'OK';
};

// 释放锁
const releaseLock = async (lockKey, lockValue) => {
	const script = `
		if redis.call('get', KEYS[1]) == ARGV[1] 
		then 
			return redis.call('del', KEYS[1]) 
		else 
			return 0 
		end
	`;
	const result = await client.eval(script, 1, lockKey, lockValue);
	return result === 1;
};

// 示例使用
(async () => {
	const lockKey = 'lock:resource';
	const lockValue = Date.now() + 10000; // 锁的值为当前时间 + 10秒
	const expiration = 10; // 锁的过期时间为10秒

	const lockAcquired = await acquireLock(lockKey, lockValue, expiration);
	if (lockAcquired) {
		console.log('获得了锁');
		// 执行需要保护的操作
		try {
			console.log('正在执行业务逻辑...');
			await new Promise(resolve => setTimeout(resolve, 5000)); // 模拟操作
		} finally {
			const released = await releaseLock(lockKey, lockValue);
			console.log(released ? '锁被释放' : '锁释放失败');
		}
	} else {
		console.log('无法获得锁');
	}
	client.quit();
})();
  1. 关键注意事项:
  • 锁的过期时间:应合理设置锁的过期时间(TTL),避免任务还未完成锁就被释放,或因节点崩溃后锁无法及时释放。
  • 唯一标识:在设置锁时,应使用唯一标识(如UUID或当前时间戳)来标识锁的持有者,以防止误删锁。
  • Lua脚本:为了确保释放锁的安全性,应使用Lua脚本来执行释放操作,这样可以确保在释放锁时检查锁的持有者。

通过正确地在Node.js中使用分布式锁,可以确保分布式系统中数据的一致性和避免竞争条件。

使用Redlock

redlock是一个基于Node.js的库,它实现了Redlock算法,提供了高可用且分布式的Redis锁服务。使用redlock库可以更方便、更安全地在Node.js中实现分布式锁。

以下是如何使用redlock库来替代上述例子的步骤:

  1. 安装redlock和redis依赖:首先,需要安装redlock和redis依赖包。
bash
npm install redis redlock
  1. 初始化Redlock实例:创建一个新的Redlock实例,需要至少一个Redis客户端连接。通常推荐使用ioredis作为Redis客户端。
js
const Redis = require('redis');
const Redlock = require('redlock').default;

const client = Redis.createClient();
const redlock = new Redlock([client], {
    mutexTimeout: 2000 // 锁超时时间,单位毫秒
});
  1. 加锁与解锁:使用redlock提供的acquire和release方法来获取和释放锁。
js
(async () => {
    const resourceId = 'lock:resource';
    const lockTtl = 1000; // 锁的持有时间,单位毫秒

    try {
        const lock = await redlock.acquire(resourceId, lockTtl);
        console.log('Lock acquired. Performing critical operation...');

        // 在此处执行临界区代码,确保只有持有锁的进程能够执行
        await new Promise(resolve => setTimeout(resolve, 5000)); // 模拟操作

        await redlock.release(lock);
        console.log('Lock released.');
    } catch (error) {
        console.error('Failed to acquire lock:', error);
    } finally {
        client.quit(); // 关闭Redis客户端连接
    }
})();
  1. 注意事项:
  • 在使用redlock时,应确保Redis实例的高可用性,以避免单点故障。
  • 锁的持有时间(lockTtl)应根据业务场景合理设置,避免锁过期过快或持有时间过长。
  • 在释放锁时,redlock库会自动处理锁的持有者验证,因此无需像上述例子中使用Lua脚本来确保释放锁的安全性。

使用redlock库可以更方便、更安全地在Node.js中实现分布式锁,同时避免了手动编写复杂的锁逻辑和错误处理代码。