You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多实例Java应用Cron定时MongoDB清理任务冲突解决求助

单实例执行MongoDB清理任务的解决方案

方案1:基于MongoDB分布式锁实现

利用MongoDB的原子操作实现分布式锁,只有成功获取锁的实例才执行清理任务,避免并行冲突。

  • 实现步骤:

    1. 在MongoDB中创建锁集合task_locks,文档包含task_name(任务唯一标识,比如mongodb_cleanup)、expire_at(锁过期时间,防止实例挂死导致锁无法释放)、instance_id(持有锁的实例标识)。
    2. 每个实例生成唯一instance_id(可用主机名+进程ID拼接)。
    3. 任务触发时,用findAndModify原子操作尝试获取锁:仅当锁不存在或已过期时,才更新锁的持有实例和过期时间。
    4. 若操作返回的锁文档匹配当前实例ID,就执行清理;否则直接跳过。
  • 核心代码示例:

// 生成实例唯一标识
String instanceId = InetAddress.getLocalHost().getHostName() + "-" + ProcessHandle.current().pid();
// 设置锁过期时间(根据任务实际耗时调整,这里设1小时)
Date expireAt = new Date(System.currentTimeMillis() + 3600 * 1000);

MongoCollection<Document> lockCollection = mongoClient.getDatabase("admin").getCollection("task_locks");
// 查询条件:锁不存在,或锁已过期
Document query = new Document("task_name", "mongodb_cleanup")
        .append("$or", Arrays.asList(
                new Document("expire_at", new Document("$lt", new Date())),
                new Document("expire_at", null)
        ));
// 更新操作:设置当前实例为锁持有者,更新过期时间
Document update = new Document("$set", new Document("instance_id", instanceId).append("expire_at", expireAt));
FindOneAndUpdateOptions options = new FindOneAndUpdateOptions().upsert(true).returnDocument(ReturnDocument.AFTER);

Document result = lockCollection.findOneAndUpdate(query, update, options);
// 判断是否成功获取锁
if (result != null && instanceId.equals(result.getString("instance_id"))) {
    executeCleanup(); // 执行清理逻辑
} else {
    log.info("已有实例在执行清理任务,本次跳过");
}

方案2:配置中心指定执行实例

通过配置中心维护一个“专属执行实例”配置项,只有匹配该配置的实例才执行任务,简单直接。

  • 实现步骤:

    1. 在配置中心(如Nacos、Spring Cloud Config)添加配置task.cleanup.instance.id,值设为某个实例的唯一标识(比如主机名)。
    2. 每个实例启动时读取该配置,同时获取自身的唯一标识。
    3. Cron任务触发时,对比自身标识与配置值,一致则执行清理,否则跳过。
    4. 若执行实例故障,手动修改配置切换到其他实例即可。
  • 核心代码示例:

@Value("${task.cleanup.instance.id}")
private String designatedInstanceId;

// 获取当前实例标识
private String getCurrentInstanceId() {
    try {
        return InetAddress.getLocalHost().getHostName();
    } catch (UnknownHostException e) {
        return UUID.randomUUID().toString();
    }
}

@Scheduled(cron = "${task.cleanup.cron}")
public void cleanupTask() {
    if (designatedInstanceId.equals(getCurrentInstanceId())) {
        executeCleanup();
    } else {
        log.info("非指定执行实例,跳过清理任务");
    }
}

方案3:基于服务注册中心的领导选举

如果应用已接入服务注册中心(如Eureka、Nacos),可以用领导选举机制,让当选的Leader实例单独执行任务。

  • 实现步骤:

    1. 借助Curator(ZooKeeper)或注册中心自带的选举组件,每个实例启动后参与Leader选举。
    2. 只有当选Leader的实例才启用Cron清理任务,其他实例自动禁用任务。
    3. 当Leader实例故障时,注册中心会自动重新选举新Leader,保证任务持续可用。
  • 核心代码示例(Curator LeaderLatch):

private LeaderLatch leaderLatch;

@PostConstruct
public void initLeaderLatch() throws Exception {
    // 初始化Curator客户端连接ZooKeeper
    CuratorFramework client = CuratorFrameworkFactory.newClient("zk-host:2181", new ExponentialBackoffRetry(1000, 3));
    client.start();
    // 创建LeaderLatch,指定选举节点路径
    leaderLatch = new LeaderLatch(client, "/mongodb-cleanup-leader");
    // 监听Leader状态变化
    leaderLatch.addListener(() -> {
        if (leaderLatch.hasLeadership()) {
            enableCleanupTask(); // 当选Leader,启用任务
        } else {
            disableCleanupTask(); // 失去Leader身份,禁用任务
        }
    });
    leaderLatch.start();
}

内容的提问来源于stack exchange,提问作者J Harish

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 13:35:13