在 Nestjs 中,我有一个 cron 作业,如果使用 Redlock 存在应用程序的多个实例,我想执行一次:
@Cron('*/1 * * * * *')
async test(): Promise<void> {
try {
const lockKey = 'test-cron-job-lock';
const lock = await this.redisService.acquireLock(lockKey, 1000); // 1 second
this.logger.log(`Cron job at ${this.getTimeWithoutMilliseconds()}`);
await this.redisService.releaseLock(lock);
} catch (e) {
this.logger.error('Error in test cron job');
}
}
它可以工作,但有时
lockKey
会同时被多个实例获取。
应用程序A:
[Nest] 8872 - 10/04/2024, 4:06:48 PM LOG Cron job at 2024-10-04T13:06:48Z
[Nest] 8872 - 10/04/2024, 4:06:49 PM ERROR Error in test cron job
[Nest] 8872 - 10/04/2024, 4:06:50 PM ERROR Error in test cron job
[Nest] 8872 - 10/04/2024, 4:06:51 PM LOG Cron job at 2024-10-04T13:06:51Z
[Nest] 8872 - 10/04/2024, 4:06:52 PM LOG Cron job at 2024-10-04T13:06:52Z
[Nest] 8872 - 10/04/2024, 4:06:53 PM LOG Cron job at 2024-10-04T13:06:53Z
应用程序B:
[Nest] 8886 - 10/04/2024, 4:06:48 PM ERROR Error in test cron job
[Nest] 8886 - 10/04/2024, 4:06:49 PM LOG Cron job at 2024-10-04T13:06:49Z
[Nest] 8886 - 10/04/2024, 4:06:50 PM LOG Cron job at 2024-10-04T13:06:50Z
[Nest] 8886 - 10/04/2024, 4:06:51 PM ERROR Error in test cron job
[Nest] 8886 - 10/04/2024, 4:06:52 PM ERROR Error in test cron job
[Nest] 8886 - 10/04/2024, 4:06:53 PM LOG Cron job at 2024-10-04T13:06:53Z
如您所见,两个实例都在
2024-10-04T13:06:53Z
处运行作业。为什么?
通常,
Redlock
的竞争条件很少见,但由于您在同一时间设置 cron 并且锁定 TTL 很短,因此偶尔发生一次竞争是可能的。
解决这个问题的方法很少,各有利弊 -
SET
和NX
以及EX
和TTL作为参数,类似SET "running" NX EX 10
——如果key已经设置,你会得到null,否则你会得到OK
,这是原子的操作,因此不可能进行比赛,但在这种情况下,我会保留锁并单独使用该装置。 EX
确保按键在一段时间后释放。一般来说,我会说
Redlock
适合您无法预测行为的情况,并且您希望确保始终只有一个客户拥有它。
但是,如果它们都同时开始,执行时间大约相同,并且您需要完全原子的东西,我会使用 SET key NX EX sec
并稍微抖动,您就是非常出色的子弹教授。