模块链接
昨天正式发布了用nodejs 重写的 Telegram抽奖机器人 @fengdrawbot 后, 马上在第一次被使用中就发现了一超级大BUG。
由于机器人有人数到了就会自动开奖的机制, 所以在每次有人加入抽奖的时候就会判断是否达到人数, 达到人数后就会开奖。
当即将开奖的时候, 如果有几人同时参加抽奖, 由于是几乎同时参加的, 所以有机率同时触发抽奖代码使机器人开奖多次。
原来机器人是在Google App Script (GAS) 上运行,使用的也是GAS上面的锁 (LockService),除了慢点并没有什么问题。当我改用nodejs 后, 我使用了 async-lock 这个模块。 但是其实我在使用的过程中,代码没有使用正确,而我也没有模拟多人同时参与的情况进行测试,所以出现了一个大BUG。
上一篇博客我写了如何使用fiddler 进行BUG的复原。这一篇博客我想讲讲 async-lock 的具体用法, 官方的说明文档太简短,我当时没有完全看懂, 所以才会犯错。
async-lock 的安装和导入请直接看官方文档。 我这里给出示例代码来讲讲它的用法。
[javascript]
// 设置一个用来锁的对像,我这里使用抽奖码来锁
// 用抽奖码的好处是,每个抽奖都有独立的锁
// 当有好几个抽奖同时开奖的时候,它们互不干涉
let key = draw.joinCode;
// 这个只是我用来测试的,标记当前的线程
let uuid = uuidv4();
// 拿锁, 只有拿到锁才能执行里面的代码
// 没拿到锁的线程会在这里卡住,直到拿到锁为止
lock.acquire(key, function(done) {
// async work
// 拿到锁后, 这个匿名函数里面的代码才会被执行
console.log("got lock" + uuid);
// Draw the result and modify payloads array
let task = DrawCommon.drawResult(draw, payloads, payload5, uuid);
task.then(r => {
// I previous used done in a wrong place, so the lock didn't work
console.log("release lock" + uuid);
// 这里使用 done 这个 callback 来释放锁, 释放后,别的线程才能进来
// 这个时候,之前的线程已经开奖完毕, 新进来的进程会重新查数据库,如果
// 发现开奖完毕则不会做任何事, 避免了多次开奖
// 如果没有锁的话, 所以进程一起进到这里面, 当它们查询数据库的时候发现还没有开奖,
// 就会一起执行开奖的代码
done("no error", "ok");
});
tasks.push(task);
}, function(err, ret) {
// lock released
}, null);
// 用抽奖码的好处是,每个抽奖都有独立的锁
// 当有好几个抽奖同时开奖的时候,它们互不干涉
let key = draw.joinCode;
// 这个只是我用来测试的,标记当前的线程
let uuid = uuidv4();
// 拿锁, 只有拿到锁才能执行里面的代码
// 没拿到锁的线程会在这里卡住,直到拿到锁为止
lock.acquire(key, function(done) {
// async work
// 拿到锁后, 这个匿名函数里面的代码才会被执行
console.log("got lock" + uuid);
// Draw the result and modify payloads array
let task = DrawCommon.drawResult(draw, payloads, payload5, uuid);
task.then(r => {
// I previous used done in a wrong place, so the lock didn't work
console.log("release lock" + uuid);
// 这里使用 done 这个 callback 来释放锁, 释放后,别的线程才能进来
// 这个时候,之前的线程已经开奖完毕, 新进来的进程会重新查数据库,如果
// 发现开奖完毕则不会做任何事, 避免了多次开奖
// 如果没有锁的话, 所以进程一起进到这里面, 当它们查询数据库的时候发现还没有开奖,
// 就会一起执行开奖的代码
done("no error", "ok");
});
tasks.push(task);
}, function(err, ret) {
// lock released
}, null);
[/javascript]
上面的这个例子是使用了传进来的done 这个callback函数来释放锁的, 如果我的开奖函数不需要返回值则使用这面的方法就可以。 但是我的 drawResult 这个函数会返回一个 promise, 而且我要反这个promise 再返回到上一级函数, 所以我需要 lock.acquire 能够返回 promise. 幸运的是 async-lock 也有 promise 模式, 看下面的例子。
[javascript]
// promise 模式会返回一个 promise, 在 promise 模式中, 不需要传入
// done 这个 callback 去释放锁, 锁会自动被释放
return lock.acquire(key, function () {
// return value or promise
// async work
console.log("got lock" + uuid);
// Draw the result and modify payloads array
// 调用 drawResult 函数, 这个函数返回一个 promise
// 这样 promise 里面的 payloads 可以被传到上一级函数
return DrawCommon.drawResult(draw, payloads, payload5, uuid);
//这里timeout 设定了多久超时, 如果5秒内拿不到锁就超时
}, {timeout: 5000}).catch(function(err) {
// 超时后会跑这里面的代码
console.log(err.message); // output: error
});
// done 这个 callback 去释放锁, 锁会自动被释放
return lock.acquire(key, function () {
// return value or promise
// async work
console.log("got lock" + uuid);
// Draw the result and modify payloads array
// 调用 drawResult 函数, 这个函数返回一个 promise
// 这样 promise 里面的 payloads 可以被传到上一级函数
return DrawCommon.drawResult(draw, payloads, payload5, uuid);
//这里timeout 设定了多久超时, 如果5秒内拿不到锁就超时
}, {timeout: 5000}).catch(function(err) {
// 超时后会跑这里面的代码
console.log(err.message); // output: error
});
[/javascript]
Feng
没有评论:
发表评论