1、需要锁的场景
例如:nodejs写了一个定时任务,如下:
const schedule = require('node-schedule')
schedule.scheduleJob("0 * * * * *", () => {
runTask()
})
上述代码,每分钟执行一次 runTask 方法。
如果将上代码部署两个实例服务器上,那么实际效果是两份实例都会执行定时任务。实际上就会变成每分钟执行两次。
2、乐观锁
乐观锁用于做并发的控制。大致的设计思路为:
① 设计一张乐观锁表如下:
lockName | version |
测试锁 | 0 |
表中只有两个字段:锁的名字和版本号。
② 并发操作时,多个并发去抢数据库里面的锁,保证有且仅有一个能抢到锁,抢到锁的执行,其他不执行。
抢锁的规则如下:
a. 用lockName 查询出 lockName和version
b. 用lockName和version为条件去更新version (更新操作:version 加1)
c. 更新成功的即为抢到锁的。
上面的思路很精妙,一旦有个进程更新成功,其他进程手里的version就会失效,那么更新就会失败。
3、mongodb的findOneAndUpdate
利用mongodb中的findOneAndUpdate操作,findOneAndUpdate是查出符合条件的更新,这个是一个原子操作。
代码大致如下:
db.lock.findOneAndUpdate(
{ lockName: '测试锁', version: 0 },
{ $set: { version: 1 } },
)
4、node中代码实现
我们将2中的原理写出来即可。(下面代码仅保留主要功能,未对异常处理)
async function getLock(lockName) {
const { version, lockName } = await db.lock.findOne({ lockName })
const res = await db.lock.findOneAndUpdate(
{ lockName, version },
{ $set: { version: version + 1 } }
)
return res.lastErrorObject.updatedExisting
}
使用锁的时候我们下面使用,这样保证了仅仅只有一个进程能拿到锁。
const schedule = require('node-schedule')
schedule.scheduleJob("0 * * * * *", async () => {
const lock = await getLock('测试锁')
if (lock) {
runTask()
}
})
5、补充注意
需要注意的是上述的例子,仅具有思路参考性。因为上述思路的并发仅仅是限制了“同时”的情况。
实际上并发定时任务时间是存在时间差的,如果是有时间差,那么所有的执行都能先后拿到锁。此时是需要加入时间的判断。