npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

mlock-client

v0.2.0

Published

Multi-resource distributed lock client for Node.js

Readme

mlock-client

Multi-resource distributed lock client for Node.js

功能特性

  • 原子性多资源锁
  • 自动重连
  • 连接池复用
  • Promise/Async-await API
  • 锁超时和续期
  • 队列等待和超时控制

使用场景

电商下单防超卖

在电商系统中,订单创建接口通常需要加锁以防止库存超卖。传统的分布式锁方案存在以下问题:

方案一:全局锁

lock("order-create")  // 所有订单创建操作串行执行

问题:即使客户购买的商品完全不同,也无法并发处理,严重影响系统吞吐量。

方案二:按商品分锁

lock("product:1001")  // 商品 1001
lock("product:1002")  // 商品 1002

问题:一个订单包含多个商品时,无法保证原子性。可能在锁定商品 A 后,商品 B 被其他订单锁定,导致最终部分商品锁定失败或库存不一致。

mlock 解决方案 mlock 支持原子性地锁定多个资源,确保事务的完整性:

lock("product:1001|product:1002|product:1003")  // 原子性锁定多个商品

当所有涉及的商品资源可用时,才会成功锁定;如果任一商品已被锁定,则进入队列等待,直到所有资源同时可用。

多资源锁机制

原子性保证

多资源锁的核心是原子性:要么所有资源同时锁定成功,要么全部失败(进入等待队列)。

// 请求锁定 A、B、C 三个资源
lock("resource-a|resource-b|resource-c")

锁定流程

  1. 请求阶段:客户端请求锁定多个资源(用 | 分隔)
  2. 检查阶段:服务器检查每个资源的可用性
    • 如果所有资源都可用 → 立即锁定成功
    • 如果任一资源被锁定 → 进入队列等待
  3. 排队阶段:锁请求在所有相关资源的队列中排队
  4. 激活阶段:只有当所有资源同时可用时,锁才会被激活并返回 lockId

队列机制

每个资源维护一个独立的队列:

resource-a 队列: [Lock1(A|B), Lock3(A|C), ...]
resource-b 队列: [Lock1(A|B), Lock2(B|D), ...]
resource-c 队列: [Lock3(A|C), Lock4(C|E), ...]

激活条件:Lock1 请求 A+B,只有当 resource-a 和 resource-b 队列的首元素都是 Lock1 时才会激活。

示例场景

场景一:所有资源可用

时刻1: client1.lock("res-a|res-b|res-c")  // A、B、C 都可用
时刻1: lock 成功,返回 lockId

场景二:部分资源被占用

时刻1: client1.lock("res-a")               // client1 锁定 A
时刻2: client2.lock("res-a|res-b|res-c")  // client2 请求 A+B+C
时刻2: client2 进入队列等待(A 被占用)
时刻3: client1.unlock("res-a")          // client1 释放 A
时刻3: client2 激活成功

场景三:多个请求排队

时刻1: client1.lock("res-a")                    // 锁定 A
时刻2: client2.lock("res-a|res-b")             // 等待 A+B
时刻3: client3.lock("res-a|res-b")             // 等待 A+B(在队列中)
时刻4: client1.unlock("res-a")                 // 释放 A
时刻4: client2 激活成功(先到先得)
时刻5: client2.unlock("res-a|res-b")             // client2 释放
时刻6: client3 激活成功

安装

npm install mlock

快速开始

JavaScript

const Client = require('mlock');

const client = new Client({ host: 'localhost', port: 12340 });

// 锁定单个资源
const lockId = await client.lock('resource-1', 5000);

try {
  // 执行业务逻辑
  await doSomething();
} finally {
  await client.unlock(lockId);
}

TypeScript

import Client, { MlockError } from 'mlock';

const client = new Client({
  host: 'localhost',
  port: 12340,
  ttl: 5000,
  timeout: 10000
});

try {
  // 锁定多个资源(原子性)
  const lockId = await client.lock('product:1001|product:1002|product:1003');

  try {
    // 执行业务逻辑
    await processOrder();
  } finally {
    await client.unlock(lockId);
  }
} catch (error) {
  if (error instanceof MlockError) {
    console.error(`Lock error (${error.type}):`, error.message);
  }
}

配置选项

| 参数 | 类型 | 默认值 | 说明 | |------|------|--------|------| | uri | string | - | 连接 URI,格式如 mlock://localhost:12340?timeout=5000&prefix=lock: | | host | string | localhost | 服务器地址 | | port | number | 12340 | 服务器端口 | | prefix | string | - | 资源名称前缀,所有资源会自动加上此前缀 | | ttl | number | - | 默认锁生存时间(毫秒) | | timeout | number | - | 默认上锁超时时间(毫秒) | | tolerate | number | - | 默认容忍队列长度 | | socketId | string | - | Socket ID(用于断线重连后的身份识别) | | debug | boolean | false | 调试模式 |

URI 配置

支持通过 URI 配置所有选项:

const client = new Client('mlock://localhost:12340?timeout=5000&ttl=3000&prefix=order:');

// 等价于
const client = new Client({
  host: 'localhost',
  port: 12340,
  timeout: 5000,
  ttl: 3000,
  prefix: 'order:'
});

API

constructor(options: string | ClientOptions)

创建客户端实例。

// 使用对象配置
const client = new Client({
  host: 'localhost',
  port: 12340,
  ttl: 5000
});

// 使用 URI 配置
const client = new Client('mlock://localhost:12340');

lock(resource: string, ttl?, timeout?, tolerate?): Promise

上锁。如果资源已被锁定,会进入队列等待直到超时或获取到锁。

参数:

  • resource: 资源描述字符串,多个资源用 | 分隔
  • ttl: 锁生存时间(毫秒),未指定使用配置的默认值
  • timeout: 上锁超时时间(毫秒),未指定使用配置的默认值
  • tolerate: 容忍队列长度,未指定使用配置的默认值

返回: 锁 ID

示例:

// 锁定单个资源
const lockId = await client.lock('product:1001', 5000, 10000, 10);

// 锁定多个资源(原子性,只有所有资源都可用时才会成功)
const lockId = await client.lock('product:1001|product:1002|product:1003');

try {
  // 执行业务逻辑
  await updateInventory(['1001', '1002', '1003']);
} finally {
  await client.unlock(lockId);
}

extend(lockId: string, ttl?): Promise

续期锁,延长锁的过期时间。

参数:

  • lockId: 锁 ID
  • ttl: 续期时间(毫秒),未指定使用配置的默认值

返回: 新的过期时间戳

示例:

const lockId = await client.lock('resource', 5000);

// 长时间任务,定期续期
const renewInterval = setInterval(async () => {
  const newExpiredAt = await client.extend(lockId, 5000);
  console.log('Lock extended to:', new Date(newExpiredAt));
}, 4000);

try {
  await longRunningTask();
} finally {
  clearInterval(renewInterval);
  await client.unlock(lockId);
}

unlock(lockId: string): Promise

解锁,释放指定的锁。

示例:

const lockId = await client.lock('resource', 5000);
try {
  await doSomething();
} finally {
  await client.unlock(lockId);
}

ping(): Promise<'pong'>

Ping 服务器,检测连接是否正常。

try {
  const pong = await client.ping();
  console.log('Connection is alive:', pong);
} catch (error) {
  console.error('Connection error:', error);
}

status(): Promise

获取服务器状态。

返回: 服务器状态对象

const status = await client.status();
console.log('Socket count:', status.socketCount);
console.log('Current locks:', status.currentLocks);
console.log('Live time:', status.liveTime, 'ms');

destroy(): void

销毁客户端,释放连接资源。

client.destroy();

错误处理

mlock 使用自定义的 MlockError 类。

错误类型:

| 类型 | 说明 | |------|------| | connection | 连接错误(网络问题、服务器不可达等) | | request | 请求错误(参数错误、资源名称无效等) | | tolerate | 队列溢出(等待队列超过容忍值) | | timeout | 超时错误(获取锁超时) |

示例:

import Client, { MlockError } from 'mlock';

const client = new Client({ host: 'localhost', port: 12340 });

try {
  const lockId = await client.lock('resource', 5000);
  // ...
} catch (error) {
  if (error instanceof MlockError) {
    switch (error.type) {
      case 'timeout':
        console.error('获取锁超时');
        break;
      case 'tolerate':
        console.error('队列已满,无法等待');
        break;
      case 'connection':
        console.error('连接服务器失败');
        break;
      case 'request':
        console.error('请求参数错误:', error.message);
        break;
    }
  }
}

使用场景

电商订单

const client = new Client({ prefix: 'order:' });

// 原子性锁定多个商品
const lockId = await client.lock('product:1001|product:1002|product:1003', 5000);

try {
  // 减少库存
  await deductInventory(['1001', '1002', '1003']);
  // 创建订单
  await createOrder([...]);
} finally {
  await client.unlock(lockId);
}

定时任务防重

const client = new Client({ prefix: 'task:' });

const taskName = 'daily-report';
const lockId = await client.lock(taskName, 60000); // 1分钟

if (lockId) {
  try {
    await generateDailyReport();
  } finally {
    await client.unlock(lockId);
  }
} else {
  console.log('Task is already running');
}

连接池

同一 host:port 的客户端会自动复用连接:

const client1 = new Client({ host: 'localhost', port: 12340 });
const client2 = new Client({ host: 'localhost', port: 12340 });
// client1 和 client2 共享同一个 TCP 连接

许可证

MIT