Node.js:用 mkdir 做一个简单的进程锁

67 次浏览2 条回复

有些本地脚本会被 cron、手动命令或者队列重复拉起。Node 里不想额外装依赖的话,可以用 fs.mkdir() 创建锁目录:创建成功表示拿到锁,目录已存在就直接退出。

环境:Node.js 20+,Linux/macOS/Windows,只用内置模块。保存为 lock-demo.mjs:

import { mkdir, rm, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { tmpdir } from 'node:os';

const lockDir = join(tmpdir(), 'node-mkdir-lock-demo.lock');

async function withLock(fn) {
  try {
    await mkdir(lockDir);
  } catch (error) {
    if (error?.code === 'EEXIST') {
      console.error('another process is running');
      process.exitCode = 75;
      return;
    }
    throw error;
  }

  try {
    await writeFile(join(lockDir, 'pid'), `${process.pid}\n`, 'utf8');
    await fn();
  } finally {
    await rm(lockDir, { recursive: true, force: true });
  }
}

await withLock(async () => {
  console.log(`start pid=${process.pid}`);
  await new Promise((resolve) => setTimeout(resolve, Number(process.argv[2] ?? 2000)));
  console.log(`done pid=${process.pid}`);
});

验证一下第二个进程会拿不到锁:

node lock-demo.mjs 3000 &
sleep 0.2
node lock-demo.mjs 100
wait

这个办法适合单机上的小脚本。真正跑生产任务时还要想一下“进程被 kill 后锁目录残留”的清理策略,比如记录 pid 和时间戳,启动时判断是不是过期锁。

这个思路挺适合本机小任务。补一个小坑:如果要自动清理残留锁,最好别只看 pid 是否存在,pid 可能复用。可以在锁目录里同时写 pid 和 createdAt,启动时先判断锁年龄,比如超过任务最大预期时长再尝试接管;能查进程启动时间的平台再顺手比一下会更稳。

还有一点是锁目录尽量放在同一台机器的本地磁盘上,别放到一些语义不太稳定的共享盘里,不然 mkdir 的原子性假设可能就没那么舒服了。

阿不知道呢Lv1#1

这个思路挺适合本机小任务。补一个小坑:如果要自动清理残留锁,最好别只看 pid 是否存在,pid 可能复用。可以在锁目录里同时写 pid 和 createdAt,启动时先判断锁年龄,比如超过任务最大预期时长再尝试接管;能查进程启动时间的平台再顺手比一下会更稳。

还有一点是锁目录尽量放在同一台机器的本地磁盘上,别放到一些语义不太稳定的共享盘里,不然 mkdir 的原子性假设可能就没那么舒服了。

这个例子里 finally 能处理正常异常,但收到 SIGINT/SIGTERM 时有些人会想明确做一次收尾。可以在脚本入口顺手挂一下信号,还是只用内置模块:

let shuttingDown = false;

async function releaseAndExit(signal) {
  if (shuttingDown) return;
  shuttingDown = true;
  try {
    await rm(lockDir, { recursive: true, force: true });
  } finally {
    process.kill(process.pid, signal);
  }
}

process.once('SIGINT', () => releaseAndExit('SIGINT'));
process.once('SIGTERM', () => releaseAndExit('SIGTERM'));

小脚本够用了。要注意的是 SIGKILL、断电这种还是没机会跑清理,所以楼上说的过期锁判断仍然要留着。