如何防止 Cron 任务中产生重复记录

如何防止 Cron 任务中产生重复记录

理解分布式系统中进程内调度器的陷阱

在软件开发中,确保任务顺利运行且不产生重复至关重要。我们将深入探讨分布式系统中的一个常见陷阱:进程内调度器如何导致夜间任务多次运行,从而在数据库中产生重复记录。随着越来越多的公司采用微服务架构(服务在其中水平扩展),这一话题尤为切合实际。

场景设置

设想你有一个夜间任务,用于在应用程序中为活跃实体创建记录。该任务本应每天运行一次,为每个实体创建一条新记录。在基于 NestJS(一个用于构建服务端应用程序的流行框架)的典型配置中,你可能会使用 @Cron 装饰器来调度该任务。以下是其简化版本的大致形态:

@Injectable()
export class WindowGenerationService {
    @Cron('0 8 * * *') // every day at 08:00
    async generateNextWindows() {
        const entities = await this.repo.findActiveEndingSoon();
        for (const entity of entities) {
            await this.repo.createNextWindow(entity);
        }
    }
}

当你的服务仅运行单个实例时,这段代码可以完美运行。然而,当你通过运行多个实例对应用程序进行水平扩展时,问题就可能出现。

问题所在

在扩展后的架构中,如果你有三个服务实例,每个实例都会在同一时间运行自己那份定时任务副本。因此,不再是只有一个任务在 08:00 运行,而是最终有三个任务同时触发。每个实例都在不检查记录是否已存在的情况下创建自己的记录。这会导致数据库中出现重复条目,即在数秒之内为同一个实体创建了两条完全相同的记录。

为什么这很重要

重复数据可能会引发各种问题,包括数据完整性问题和资源浪费。你不仅在创建不必要的记录,还在为做相同工作的三个容器的计算资源付费。

可能的解决方案

在明确了问题之后,有几种方法可以解决它:

方案 1:使用分布式锁

一种解决方案是实现分布式锁。这将允许多个实例竞争一个锁,仅让一个实例运行该任务。然而,这种方法也有其弊端。如果锁后端发生故障,或者持有锁的实例在持有锁时宕机,任务可能根本不会运行,从而导致执行遗漏。

方案 2:实现幂等性

另一种选择是添加幂等性检查。这意味着如果任务再次运行,若记录已经存在,它将直接跳过创建记录的操作。虽然这是一个成本更低廉的解决方案,但它仍然会唤醒所有实例,从而导致重复的工作。

方案 3:将调度移至应用外部

事实证明,最佳解决方案是将调度移出应用程序。与其让应用程序管理任务的运行时间,不如让外部调度器来处理它。这样,你可以创建一个 HTTP 端点,由外部调度器在预定时间触发一次。以下是它的大致实现:

@Post('jobs/run')
async runJob(@Body() body: RunJobDto) {
    this.assertValidSecret(body.secret);
    return this.jobs.run(body.jobKey);
}

采用这种配置后,将只有一个实例响应任务触发,从而防止创建重复记录。同时保留幂等性检查,以确保即使任务被多次触发,也不会产生重复项。

需要付出的代价

当将任务暴露为 HTTP 端点时,你需要考虑安全性。任何发现该端点的人都有可能触发它。为了防止未经授权的访问,可以使用共享密钥来验证请求。这确保了只有合法的触发器才能执行该任务。

另一个考量因素是调度时间。该任务应该在所有用户的一天开始之后运行,而在处理多个时区时这可能会变得很棘手。选择一个固定的 UTC 时间有助于在不同地区之间实现标准化。

经验教训

这次经历凸显了一个关键教训:在分布式系统中,进程内调度器可能会导致意外行为。在扩展应用程序时,将调度职责与应用程序逻辑分离开来至关重要。通过这样做,你可以避免重复写入的陷阱,并确保你的任务按预期运行。

总结

总而言之,在微服务架构中管理 cron 任务需要周密的规划。通过将调度移至应用程序外部并实施适当的检查,你可以防止重复记录等问题,并提高服务的效率。

优点

  • 防止数据库中产生重复记录。
  • 减少不必要的计算成本。
  • 通过将调度外部化来简化任务管理。

缺点

  • 需要为外部调度器进行额外的配置。
  • 在处理 HTTP 请求和安全性方面引入了复杂性。

注意事项

本文仅作为教学概述。在依赖这些内容之前,请务必将任何占位符值替换为你的实际配置,并向原始来源核实相关说法。

常见问题

  • 什么是 cron 任务? — cron 任务是在指定时间间隔自动运行的定时任务。
  • 为什么我的 cron 任务运行了多次? — 如果该任务在应用程序的多个实例中都被调度,就可能会发生这种情况。
  • 如何防止数据库中产生重复记录? — 实现幂等性检查,或将调度移至应用程序外部。
  • 编程中的幂等性是什么? — 幂等性意味着一个函数可以被多次调用,而不会改变初次调用之后的结果。
  • 什么是 NestJS? — NestJS 是一个用于构建高效、可扩展的 Node.js 服务端应用程序的框架。
  • 如何保障我的 HTTP 端点的安全? — 使用共享密钥或令牌等身份验证方法来验证请求。

标签

#cron #nestjs #microservices #scheduling #softwareengineering #dataintegrity #cloudcomputing #distributedsystems

Free field guide

Docker Security Checklist

Lock down your containers from build to runtime — 29 practical controls covering images, runtime flags, secrets, and the daemon. Enter your email — you'll get the PDF instantly, plus new posts on Docker, Linux & security.