mlock-client 0.1.9 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +401 -4
- package/index.d.ts +66 -14
- package/lib/index.js +12 -5
- package/package.json +12 -8
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2020
|
|
3
|
+
Copyright (c) 2020 Liang Xingchen https://github.com/liangxingchen
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -1,10 +1,407 @@
|
|
|
1
|
-
#
|
|
1
|
+
# mlock-client
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Multi-resource distributed lock client for Node.js
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 功能特性
|
|
6
6
|
|
|
7
|
+
- 原子性多资源锁
|
|
8
|
+
- 自动重连
|
|
9
|
+
- 连接池复用
|
|
10
|
+
- Promise/Async-await API
|
|
11
|
+
- 锁超时和续期
|
|
12
|
+
- 队列等待和超时控制
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## 使用场景
|
|
16
|
+
|
|
17
|
+
### 电商下单防超卖
|
|
18
|
+
|
|
19
|
+
在电商系统中,订单创建接口通常需要加锁以防止库存超卖。传统的分布式锁方案存在以下问题:
|
|
20
|
+
|
|
21
|
+
**方案一:全局锁**
|
|
22
|
+
```
|
|
23
|
+
lock("order-create") // 所有订单创建操作串行执行
|
|
24
|
+
```
|
|
25
|
+
问题:即使客户购买的商品完全不同,也无法并发处理,严重影响系统吞吐量。
|
|
26
|
+
|
|
27
|
+
**方案二:按商品分锁**
|
|
28
|
+
```
|
|
29
|
+
lock("product:1001") // 商品 1001
|
|
30
|
+
lock("product:1002") // 商品 1002
|
|
31
|
+
```
|
|
32
|
+
问题:一个订单包含多个商品时,无法保证原子性。可能在锁定商品 A 后,商品 B 被其他订单锁定,导致最终部分商品锁定失败或库存不一致。
|
|
33
|
+
|
|
34
|
+
**mlock 解决方案**
|
|
35
|
+
mlock 支持原子性地锁定多个资源,确保事务的完整性:
|
|
36
|
+
```
|
|
37
|
+
lock("product:1001|product:1002|product:1003") // 原子性锁定多个商品
|
|
38
|
+
```
|
|
39
|
+
当所有涉及的商品资源可用时,才会成功锁定;如果任一商品已被锁定,则进入队列等待,直到所有资源同时可用。
|
|
40
|
+
|
|
41
|
+
## 多资源锁机制
|
|
42
|
+
|
|
43
|
+
### 原子性保证
|
|
44
|
+
|
|
45
|
+
多资源锁的核心是**原子性**:要么所有资源同时锁定成功,要么全部失败(进入等待队列)。
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
// 请求锁定 A、B、C 三个资源
|
|
49
|
+
lock("resource-a|resource-b|resource-c")
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 锁定流程
|
|
53
|
+
|
|
54
|
+
1. **请求阶段**:客户端请求锁定多个资源(用 `|` 分隔)
|
|
55
|
+
2. **检查阶段**:服务器检查每个资源的可用性
|
|
56
|
+
- 如果所有资源都可用 → 立即锁定成功
|
|
57
|
+
- 如果任一资源被锁定 → 进入队列等待
|
|
58
|
+
3. **排队阶段**:锁请求在所有相关资源的队列中排队
|
|
59
|
+
4. **激活阶段**:只有当所有资源同时可用时,锁才会被激活并返回 lockId
|
|
60
|
+
|
|
61
|
+
### 队列机制
|
|
62
|
+
|
|
63
|
+
每个资源维护一个独立的队列:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
resource-a 队列: [Lock1(A|B), Lock3(A|C), ...]
|
|
67
|
+
resource-b 队列: [Lock1(A|B), Lock2(B|D), ...]
|
|
68
|
+
resource-c 队列: [Lock3(A|C), Lock4(C|E), ...]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**激活条件**:Lock1 请求 A+B,只有当 resource-a 和 resource-b 队列的首元素都是 Lock1 时才会激活。
|
|
72
|
+
|
|
73
|
+
### 示例场景
|
|
74
|
+
|
|
75
|
+
**场景一:所有资源可用**
|
|
76
|
+
```
|
|
77
|
+
时刻1: client1.lock("res-a|res-b|res-c") // A、B、C 都可用
|
|
78
|
+
时刻1: lock 成功,返回 lockId
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**场景二:部分资源被占用**
|
|
82
|
+
```
|
|
83
|
+
时刻1: client1.lock("res-a") // client1 锁定 A
|
|
84
|
+
时刻2: client2.lock("res-a|res-b|res-c") // client2 请求 A+B+C
|
|
85
|
+
时刻2: client2 进入队列等待(A 被占用)
|
|
86
|
+
时刻3: client1.unlock("res-a") // client1 释放 A
|
|
87
|
+
时刻3: client2 激活成功
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**场景三:多个请求排队**
|
|
91
|
+
```
|
|
92
|
+
时刻1: client1.lock("res-a") // 锁定 A
|
|
93
|
+
时刻2: client2.lock("res-a|res-b") // 等待 A+B
|
|
94
|
+
时刻3: client3.lock("res-a|res-b") // 等待 A+B(在队列中)
|
|
95
|
+
时刻4: client1.unlock("res-a") // 释放 A
|
|
96
|
+
时刻4: client2 激活成功(先到先得)
|
|
97
|
+
时刻5: client2.unlock("res-a|res-b") // client2 释放
|
|
98
|
+
时刻6: client3 激活成功
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
## 安装
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
npm install mlock
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 快速开始
|
|
109
|
+
|
|
110
|
+
### JavaScript
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
const Client = require('mlock');
|
|
114
|
+
|
|
115
|
+
const client = new Client({ host: 'localhost', port: 12340 });
|
|
116
|
+
|
|
117
|
+
// 锁定单个资源
|
|
118
|
+
const lockId = await client.lock('resource-1', 5000);
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
// 执行业务逻辑
|
|
122
|
+
await doSomething();
|
|
123
|
+
} finally {
|
|
124
|
+
await client.unlock(lockId);
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### TypeScript
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import Client, { MlockError } from 'mlock';
|
|
132
|
+
|
|
133
|
+
const client = new Client({
|
|
134
|
+
host: 'localhost',
|
|
135
|
+
port: 12340,
|
|
136
|
+
ttl: 5000,
|
|
137
|
+
timeout: 10000
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
// 锁定多个资源(原子性)
|
|
142
|
+
const lockId = await client.lock('product:1001|product:1002|product:1003');
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
// 执行业务逻辑
|
|
146
|
+
await processOrder();
|
|
147
|
+
} finally {
|
|
148
|
+
await client.unlock(lockId);
|
|
149
|
+
}
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (error instanceof MlockError) {
|
|
152
|
+
console.error(`Lock error (${error.type}):`, error.message);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## 配置选项
|
|
158
|
+
|
|
159
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
160
|
+
|------|------|--------|------|
|
|
161
|
+
| uri | string | - | 连接 URI,格式如 `mlock://localhost:12340?timeout=5000&prefix=lock:` |
|
|
162
|
+
| host | string | localhost | 服务器地址 |
|
|
163
|
+
| port | number | 12340 | 服务器端口 |
|
|
164
|
+
| prefix | string | - | 资源名称前缀,所有资源会自动加上此前缀 |
|
|
165
|
+
| ttl | number | - | 默认锁生存时间(毫秒) |
|
|
166
|
+
| timeout | number | - | 默认上锁超时时间(毫秒) |
|
|
167
|
+
| tolerate | number | - | 默认容忍队列长度 |
|
|
168
|
+
| socketId | string | - | Socket ID(用于断线重连后的身份识别) |
|
|
169
|
+
| debug | boolean | false | 调试模式 |
|
|
170
|
+
|
|
171
|
+
### URI 配置
|
|
172
|
+
|
|
173
|
+
支持通过 URI 配置所有选项:
|
|
174
|
+
|
|
175
|
+
```javascript
|
|
176
|
+
const client = new Client('mlock://localhost:12340?timeout=5000&ttl=3000&prefix=order:');
|
|
177
|
+
|
|
178
|
+
// 等价于
|
|
179
|
+
const client = new Client({
|
|
180
|
+
host: 'localhost',
|
|
181
|
+
port: 12340,
|
|
182
|
+
timeout: 5000,
|
|
183
|
+
ttl: 3000,
|
|
184
|
+
prefix: 'order:'
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## API
|
|
189
|
+
|
|
190
|
+
### constructor(options: string | ClientOptions)
|
|
191
|
+
|
|
192
|
+
创建客户端实例。
|
|
193
|
+
|
|
194
|
+
```javascript
|
|
195
|
+
// 使用对象配置
|
|
196
|
+
const client = new Client({
|
|
197
|
+
host: 'localhost',
|
|
198
|
+
port: 12340,
|
|
199
|
+
ttl: 5000
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// 使用 URI 配置
|
|
203
|
+
const client = new Client('mlock://localhost:12340');
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### lock(resource: string, ttl?, timeout?, tolerate?): Promise<string>
|
|
207
|
+
|
|
208
|
+
上锁。如果资源已被锁定,会进入队列等待直到超时或获取到锁。
|
|
209
|
+
|
|
210
|
+
**参数:**
|
|
211
|
+
- `resource`: 资源描述字符串,多个资源用 `|` 分隔
|
|
212
|
+
- `ttl`: 锁生存时间(毫秒),未指定使用配置的默认值
|
|
213
|
+
- `timeout`: 上锁超时时间(毫秒),未指定使用配置的默认值
|
|
214
|
+
- `tolerate`: 容忍队列长度,未指定使用配置的默认值
|
|
215
|
+
|
|
216
|
+
**返回:** 锁 ID
|
|
217
|
+
|
|
218
|
+
**示例:**
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
// 锁定单个资源
|
|
222
|
+
const lockId = await client.lock('product:1001', 5000, 10000, 10);
|
|
223
|
+
|
|
224
|
+
// 锁定多个资源(原子性,只有所有资源都可用时才会成功)
|
|
225
|
+
const lockId = await client.lock('product:1001|product:1002|product:1003');
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
// 执行业务逻辑
|
|
229
|
+
await updateInventory(['1001', '1002', '1003']);
|
|
230
|
+
} finally {
|
|
231
|
+
await client.unlock(lockId);
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### extend(lockId: string, ttl?): Promise<number>
|
|
236
|
+
|
|
237
|
+
续期锁,延长锁的过期时间。
|
|
238
|
+
|
|
239
|
+
**参数:**
|
|
240
|
+
- `lockId`: 锁 ID
|
|
241
|
+
- `ttl`: 续期时间(毫秒),未指定使用配置的默认值
|
|
242
|
+
|
|
243
|
+
**返回:** 新的过期时间戳
|
|
244
|
+
|
|
245
|
+
**示例:**
|
|
246
|
+
|
|
247
|
+
```javascript
|
|
248
|
+
const lockId = await client.lock('resource', 5000);
|
|
249
|
+
|
|
250
|
+
// 长时间任务,定期续期
|
|
251
|
+
const renewInterval = setInterval(async () => {
|
|
252
|
+
const newExpiredAt = await client.extend(lockId, 5000);
|
|
253
|
+
console.log('Lock extended to:', new Date(newExpiredAt));
|
|
254
|
+
}, 4000);
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
await longRunningTask();
|
|
258
|
+
} finally {
|
|
259
|
+
clearInterval(renewInterval);
|
|
260
|
+
await client.unlock(lockId);
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### unlock(lockId: string): Promise<void>
|
|
265
|
+
|
|
266
|
+
解锁,释放指定的锁。
|
|
267
|
+
|
|
268
|
+
**示例:**
|
|
269
|
+
|
|
270
|
+
```javascript
|
|
271
|
+
const lockId = await client.lock('resource', 5000);
|
|
272
|
+
try {
|
|
273
|
+
await doSomething();
|
|
274
|
+
} finally {
|
|
275
|
+
await client.unlock(lockId);
|
|
276
|
+
}
|
|
7
277
|
```
|
|
8
|
-
const mlock = require('mlock');
|
|
9
278
|
|
|
279
|
+
### ping(): Promise<'pong'>
|
|
280
|
+
|
|
281
|
+
Ping 服务器,检测连接是否正常。
|
|
282
|
+
|
|
283
|
+
```javascript
|
|
284
|
+
try {
|
|
285
|
+
const pong = await client.ping();
|
|
286
|
+
console.log('Connection is alive:', pong);
|
|
287
|
+
} catch (error) {
|
|
288
|
+
console.error('Connection error:', error);
|
|
289
|
+
}
|
|
10
290
|
```
|
|
291
|
+
|
|
292
|
+
### status(): Promise<any>
|
|
293
|
+
|
|
294
|
+
获取服务器状态。
|
|
295
|
+
|
|
296
|
+
**返回:** 服务器状态对象
|
|
297
|
+
|
|
298
|
+
```javascript
|
|
299
|
+
const status = await client.status();
|
|
300
|
+
console.log('Socket count:', status.socketCount);
|
|
301
|
+
console.log('Current locks:', status.currentLocks);
|
|
302
|
+
console.log('Live time:', status.liveTime, 'ms');
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### destroy(): void
|
|
306
|
+
|
|
307
|
+
销毁客户端,释放连接资源。
|
|
308
|
+
|
|
309
|
+
```javascript
|
|
310
|
+
client.destroy();
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## 错误处理
|
|
314
|
+
|
|
315
|
+
mlock 使用自定义的 `MlockError` 类。
|
|
316
|
+
|
|
317
|
+
**错误类型:**
|
|
318
|
+
|
|
319
|
+
| 类型 | 说明 |
|
|
320
|
+
|------|------|
|
|
321
|
+
| connection | 连接错误(网络问题、服务器不可达等) |
|
|
322
|
+
| request | 请求错误(参数错误、资源名称无效等) |
|
|
323
|
+
| tolerate | 队列溢出(等待队列超过容忍值) |
|
|
324
|
+
| timeout | 超时错误(获取锁超时) |
|
|
325
|
+
|
|
326
|
+
**示例:**
|
|
327
|
+
|
|
328
|
+
```javascript
|
|
329
|
+
import Client, { MlockError } from 'mlock';
|
|
330
|
+
|
|
331
|
+
const client = new Client({ host: 'localhost', port: 12340 });
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
const lockId = await client.lock('resource', 5000);
|
|
335
|
+
// ...
|
|
336
|
+
} catch (error) {
|
|
337
|
+
if (error instanceof MlockError) {
|
|
338
|
+
switch (error.type) {
|
|
339
|
+
case 'timeout':
|
|
340
|
+
console.error('获取锁超时');
|
|
341
|
+
break;
|
|
342
|
+
case 'tolerate':
|
|
343
|
+
console.error('队列已满,无法等待');
|
|
344
|
+
break;
|
|
345
|
+
case 'connection':
|
|
346
|
+
console.error('连接服务器失败');
|
|
347
|
+
break;
|
|
348
|
+
case 'request':
|
|
349
|
+
console.error('请求参数错误:', error.message);
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## 使用场景
|
|
357
|
+
|
|
358
|
+
### 电商订单
|
|
359
|
+
|
|
360
|
+
```javascript
|
|
361
|
+
const client = new Client({ prefix: 'order:' });
|
|
362
|
+
|
|
363
|
+
// 原子性锁定多个商品
|
|
364
|
+
const lockId = await client.lock('product:1001|product:1002|product:1003', 5000);
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
// 减少库存
|
|
368
|
+
await deductInventory(['1001', '1002', '1003']);
|
|
369
|
+
// 创建订单
|
|
370
|
+
await createOrder([...]);
|
|
371
|
+
} finally {
|
|
372
|
+
await client.unlock(lockId);
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### 定时任务防重
|
|
377
|
+
|
|
378
|
+
```javascript
|
|
379
|
+
const client = new Client({ prefix: 'task:' });
|
|
380
|
+
|
|
381
|
+
const taskName = 'daily-report';
|
|
382
|
+
const lockId = await client.lock(taskName, 60000); // 1分钟
|
|
383
|
+
|
|
384
|
+
if (lockId) {
|
|
385
|
+
try {
|
|
386
|
+
await generateDailyReport();
|
|
387
|
+
} finally {
|
|
388
|
+
await client.unlock(lockId);
|
|
389
|
+
}
|
|
390
|
+
} else {
|
|
391
|
+
console.log('Task is already running');
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## 连接池
|
|
396
|
+
|
|
397
|
+
同一 `host:port` 的客户端会自动复用连接:
|
|
398
|
+
|
|
399
|
+
```javascript
|
|
400
|
+
const client1 = new Client({ host: 'localhost', port: 12340 });
|
|
401
|
+
const client2 = new Client({ host: 'localhost', port: 12340 });
|
|
402
|
+
// client1 和 client2 共享同一个 TCP 连接
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
## 许可证
|
|
406
|
+
|
|
407
|
+
MIT
|
package/index.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 客户端配置选项
|
|
3
|
+
*/
|
|
1
4
|
export interface ClientOptions {
|
|
2
5
|
/**
|
|
3
|
-
*
|
|
6
|
+
* 连接 URI,格式如 `mlock://localhost:12340?timeout=5000&prefix=lock:`
|
|
7
|
+
* URI 参数会覆盖 host、port、timeout、prefix 等选项
|
|
4
8
|
*/
|
|
5
9
|
uri?: string;
|
|
6
10
|
/**
|
|
7
|
-
* 服务器主机地址,默认localhost
|
|
11
|
+
* 服务器主机地址,默认 localhost
|
|
8
12
|
*/
|
|
9
13
|
host?: string;
|
|
10
14
|
/**
|
|
@@ -12,60 +16,108 @@ export interface ClientOptions {
|
|
|
12
16
|
*/
|
|
13
17
|
port?: number;
|
|
14
18
|
/**
|
|
15
|
-
*
|
|
19
|
+
* 资源名称前缀,所有资源名称会自动加上此前缀
|
|
20
|
+
* 前缀中不能包含 `|` 或空格
|
|
16
21
|
*/
|
|
17
22
|
prefix?: string;
|
|
18
23
|
/**
|
|
19
|
-
*
|
|
24
|
+
* 默认锁的生存时间(TTL),单位毫秒
|
|
25
|
+
* 锁过期后会被自动释放
|
|
20
26
|
*/
|
|
21
27
|
ttl?: number;
|
|
22
28
|
/**
|
|
23
29
|
* 默认上锁超时时间,单位毫秒
|
|
30
|
+
* 如果在指定时间内无法获取锁,则返回超时错误
|
|
24
31
|
*/
|
|
25
32
|
timeout?: number;
|
|
26
33
|
/**
|
|
27
|
-
*
|
|
34
|
+
* 默认容忍锁队列中等待的个数
|
|
35
|
+
* 如果队列中等待的锁数量超过此值,直接报错,不等待到 timeout
|
|
36
|
+
* 用于避免大量请求堆积
|
|
28
37
|
*/
|
|
29
38
|
tolerate?: number;
|
|
39
|
+
/**
|
|
40
|
+
* 客户端 Socket ID,用于断线重连后的身份识别
|
|
41
|
+
*/
|
|
30
42
|
socketId?: string;
|
|
43
|
+
/**
|
|
44
|
+
* 是否开启调试模式,开启后会输出详细日志
|
|
45
|
+
*/
|
|
31
46
|
debug?: boolean;
|
|
32
47
|
}
|
|
33
48
|
|
|
49
|
+
/**
|
|
50
|
+
* 分布式锁客户端
|
|
51
|
+
*
|
|
52
|
+
* 支持连接到 mlock-server 进行资源的分布式锁管理
|
|
53
|
+
* 支持自动重连、连接池、多路复用等功能
|
|
54
|
+
*/
|
|
34
55
|
export default class Client {
|
|
56
|
+
/**
|
|
57
|
+
* 创建客户端实例
|
|
58
|
+
* @param options 客户端配置选项,或连接 URI 字符串
|
|
59
|
+
*/
|
|
35
60
|
constructor(options: string | ClientOptions);
|
|
36
61
|
|
|
37
62
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* @param
|
|
41
|
-
* @param
|
|
42
|
-
* @param
|
|
63
|
+
* 上锁
|
|
64
|
+
* 如果资源已被锁定,会进入队列等待,直到超时或获取到锁
|
|
65
|
+
* @param resource 资源描述字符串,同时锁定多个资源用 `|` 分隔
|
|
66
|
+
* @param ttl 锁的生存时间,单位毫秒,未指定则使用配置的默认值
|
|
67
|
+
* @param timeout 上锁超时时间,单位毫秒,未指定则使用配置的默认值
|
|
68
|
+
* @param tolerate 容忍队列长度,未指定则使用配置的默认值
|
|
69
|
+
* @returns Promise<string> 返回锁ID
|
|
70
|
+
* @throws {MlockError} 超时、队列溢出、请求错误等情况下抛出异常
|
|
43
71
|
*/
|
|
44
72
|
lock(resource: string, ttl?: number, timeout?: number, tolerate?: number): Promise<string>;
|
|
45
73
|
|
|
46
74
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* @param
|
|
75
|
+
* 续期锁
|
|
76
|
+
* 延长锁的过期时间
|
|
77
|
+
* @param lock 锁ID
|
|
78
|
+
* @param ttl 续期时间,单位毫秒,未指定则使用配置的默认值
|
|
79
|
+
* @returns Promise<number> 返回新的过期时间戳
|
|
80
|
+
* @throws {MlockError} 锁不存在或未上锁时抛出异常
|
|
50
81
|
*/
|
|
51
82
|
extend(lock: string, ttl?: number): Promise<number>;
|
|
52
83
|
|
|
53
84
|
/**
|
|
54
85
|
* 解锁
|
|
86
|
+
* 释放指定的锁
|
|
87
|
+
* @param lock 锁ID
|
|
55
88
|
*/
|
|
56
89
|
unlock(lock: string): Promise<void>;
|
|
57
90
|
|
|
58
91
|
/**
|
|
59
|
-
*
|
|
92
|
+
* Ping 服务器
|
|
93
|
+
* 用于检测连接是否正常
|
|
94
|
+
* @returns Promise<'pong'> 连接正常返回 'pong'
|
|
60
95
|
*/
|
|
61
96
|
ping(): Promise<'pong'>;
|
|
62
97
|
|
|
63
98
|
/**
|
|
64
99
|
* 获取服务器状态
|
|
100
|
+
* 返回服务器的统计信息和当前锁状态
|
|
101
|
+
* @returns Promise<any> 服务器状态对象,包含 socketCount、currentLocks 等信息
|
|
65
102
|
*/
|
|
66
103
|
status(): Promise<any>;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 销毁客户端,释放连接资源
|
|
107
|
+
*/
|
|
108
|
+
destroy(): void;
|
|
67
109
|
}
|
|
68
110
|
|
|
111
|
+
/**
|
|
112
|
+
* mlock 自定义错误类
|
|
113
|
+
*/
|
|
69
114
|
export class MlockError extends Error {
|
|
115
|
+
/**
|
|
116
|
+
* 错误类型
|
|
117
|
+
* - connection: 连接错误(网络问题、服务器不可达等)
|
|
118
|
+
* - request: 请求错误(参数错误、资源名称无效等)
|
|
119
|
+
* - tolerate: 队列溢出(等待队列超过容忍值)
|
|
120
|
+
* - timeout: 超时错误(获取锁超时)
|
|
121
|
+
*/
|
|
70
122
|
type?: 'connection' | 'request' | 'tolerate' | 'timeout';
|
|
71
123
|
}
|
package/lib/index.js
CHANGED
|
@@ -42,6 +42,9 @@ class Client {
|
|
|
42
42
|
if (parseInt(url.searchParams.get('tolerate'))) {
|
|
43
43
|
this.options.tolerate = parseInt(url.searchParams.get('tolerate'));
|
|
44
44
|
}
|
|
45
|
+
if (parseInt(url.searchParams.get('ttl'))) {
|
|
46
|
+
this.options.ttl = parseInt(url.searchParams.get('ttl'));
|
|
47
|
+
}
|
|
45
48
|
if (url.searchParams.get('prefix')) {
|
|
46
49
|
this.options.prefix = url.searchParams.get('prefix');
|
|
47
50
|
}
|
|
@@ -88,7 +91,7 @@ class Client {
|
|
|
88
91
|
await new Promise((resolve, reject) => {
|
|
89
92
|
this.lockCallbacks[lockId] = (error) => {
|
|
90
93
|
delete this.lockCallbacks[lockId];
|
|
91
|
-
error ? reject(error) : resolve();
|
|
94
|
+
error ? reject(error) : resolve(null);
|
|
92
95
|
};
|
|
93
96
|
});
|
|
94
97
|
return lockId;
|
|
@@ -146,7 +149,7 @@ class MultiplexSocket extends events_1.default.EventEmitter {
|
|
|
146
149
|
case 'connected':
|
|
147
150
|
this.connected = true;
|
|
148
151
|
if (!this.pingTimer) {
|
|
149
|
-
this.pingTimer = setInterval(this.ping, 20000);
|
|
152
|
+
this.pingTimer = setInterval(() => this.ping(), 20000);
|
|
150
153
|
}
|
|
151
154
|
this._onConnect();
|
|
152
155
|
break;
|
|
@@ -171,9 +174,6 @@ class MultiplexSocket extends events_1.default.EventEmitter {
|
|
|
171
174
|
}
|
|
172
175
|
setImmediate(this.onPacket);
|
|
173
176
|
};
|
|
174
|
-
this.ping = () => {
|
|
175
|
-
return this.send('ping', []);
|
|
176
|
-
};
|
|
177
177
|
this.host = host;
|
|
178
178
|
this.port = port;
|
|
179
179
|
this.debug = debug;
|
|
@@ -334,9 +334,16 @@ class MultiplexSocket extends events_1.default.EventEmitter {
|
|
|
334
334
|
return this.send('unlock', [lockId]);
|
|
335
335
|
}
|
|
336
336
|
async status() {
|
|
337
|
+
if (!this.connected)
|
|
338
|
+
await this.connect();
|
|
337
339
|
let status = await this.send('status', []);
|
|
338
340
|
return JSON.parse(status);
|
|
339
341
|
}
|
|
342
|
+
async ping() {
|
|
343
|
+
if (!this.connected)
|
|
344
|
+
await this.connect();
|
|
345
|
+
return (await this.send('ping', []));
|
|
346
|
+
}
|
|
340
347
|
}
|
|
341
348
|
function generateId() {
|
|
342
349
|
return Math.random().toString(16).substr(2);
|
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mlock-client",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
5
|
-
"author":
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Multi-resource distributed lock client for Node.js",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Liang",
|
|
7
|
+
"email": "liang@miaomo.cn",
|
|
8
|
+
"url": "https://github.com/liangxingchen"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/liangxingchen/mlock/tree/master/packages/mlock#readme",
|
|
7
11
|
"license": "MIT",
|
|
8
12
|
"main": "lib/index.js",
|
|
9
13
|
"types": "index.d.ts",
|
|
@@ -13,19 +17,19 @@
|
|
|
13
17
|
],
|
|
14
18
|
"repository": {
|
|
15
19
|
"type": "git",
|
|
16
|
-
"url": "git+https://github.com/
|
|
20
|
+
"url": "git+https://github.com/liangxingchen/mlock.git"
|
|
17
21
|
},
|
|
18
22
|
"scripts": {
|
|
19
23
|
"build": "tsc"
|
|
20
24
|
},
|
|
21
25
|
"bugs": {
|
|
22
|
-
"url": "https://github.com/
|
|
26
|
+
"url": "https://github.com/liangxingchen/mlock/issues"
|
|
23
27
|
},
|
|
24
28
|
"dependencies": {
|
|
25
29
|
"packet-wrapper": "^0.1.0"
|
|
26
30
|
},
|
|
27
31
|
"devDependencies": {
|
|
28
|
-
"typescript": "^
|
|
32
|
+
"typescript": "^5.9.3"
|
|
29
33
|
},
|
|
30
|
-
"gitHead": "
|
|
34
|
+
"gitHead": "fc9fbe81475b3fbdc8a54aff9015f152aa6f4edb"
|
|
31
35
|
}
|