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

Spring Boot双节点项目中基于HazelCast实现定时邮件发送方法的单节点执行配置方案

针对你遇到的双节点定时任务重复发送邮件的问题,结合你已经配置好的HazelCast集群,这里有两种靠谱的解决方案,帮你实现任务仅在单个节点执行一次:

方案一:结合Spring @Scheduled + HazelCast分布式锁(最小改动)

这种方式几乎不需要改动现有定时任务的结构,只需要在任务执行前加一层分布式锁判断,确保只有拿到锁的节点才执行邮件发送逻辑。

步骤1:注入HazelCast实例

Spring Boot会自动识别你配置的HazelCast集群并创建HazelcastInstance实例,直接注入即可:

@Autowired
private HazelcastInstance hazelcastInstance;

@Autowired
private MailService mailService;

private static final Logger log = LoggerFactory.getLogger(YourSchedulerClass.class);

步骤2:修改定时任务方法,加入锁逻辑

给你的定时任务加上分布式锁判断,只有成功获取锁的节点才执行邮件发送:

@Scheduled(cron = "0 0 9 * * ?")
public void sendMail(MailDTO mailDTO) {
    // 定义唯一的锁名称,建议和任务业务强关联,避免和其他任务冲突
    ILock taskLock = hazelcastInstance.getLock("daily-mail-send-task-lock");
    
    try {
        // 尝试获取锁,设置5秒超时(可根据任务实际执行时间调整)
        if (taskLock.tryLock(5, TimeUnit.SECONDS)) {
            try {
                // 仅当前拿到锁的节点执行邮件发送
                log.info("Acquired lock, starting to send daily mail...");
                mailService.sendMail(mailDTO);
            } finally {
                // 无论执行成功与否,都要释放锁
                taskLock.unlock();
                log.info("Released lock after mail task execution.");
            }
        } else {
            // 未获取到锁,说明其他节点正在执行任务,直接跳过
            log.info("Another node is handling the daily mail task, skipping execution on this node.");
        }
    } catch (InterruptedException e) {
        log.error("Failed to acquire lock for daily mail task", e);
        Thread.currentThread().interrupt();
    }
}

方案二:使用HazelCast分布式调度器(更优雅的分布式任务管理)

如果希望更贴合分布式系统的任务调度规范,可以直接使用HazelCast提供的IScheduledExecutorService,它会自动在集群中选择一个节点执行任务,无需手动处理锁逻辑。

步骤1:配置分布式调度器Bean

先在Spring容器中配置HazelCast的分布式调度器:

@Configuration
public class HazelcastSchedulerConfig {
    @Bean
    public IScheduledExecutorService hazelcastScheduledExecutorService(HazelcastInstance hazelcastInstance) {
        // 创建一个名为"mail-scheduler"的分布式调度器实例
        return hazelcastInstance.getScheduledExecutorService("mail-scheduler");
    }
}

步骤2:初始化分布式定时任务

在你的调度类中,利用@PostConstruct初始化分布式任务:

@Autowired
private IScheduledExecutorService scheduledExecutorService;

@Autowired
private MailService mailService;

@PostConstruct
public void initDailyMailTask() {
    // 定义邮件发送任务
    Runnable dailyMailTask = () -> {
        log.info("Executing daily mail task on current node...");
        mailService.sendMail(new MailDTO()); // 传入你的MailDTO参数
    };

    // 配置Cron表达式(每天9点执行)
    CronScheduleConfig cronConfig = CronScheduleConfig.cron("0 0 9 * * ?");
    
    // 提交分布式定时任务
    scheduledExecutorService.schedule(dailyMailTask, cronConfig);
    log.info("Distributed daily mail task scheduled successfully.");
}

关键注意事项

  • 锁名称唯一性:方案一中的锁名称必须唯一,确保只针对当前邮件任务,避免和其他分布式任务的锁冲突。
  • 锁超时时间:方案一中的tryLock超时时间要根据任务实际执行时长调整,避免任务还没执行完锁就被释放,导致其他节点重复执行。
  • 集群通信:确保你的HazelCast集群节点之间通信正常,当前配置的multicast适合局域网环境,如果是云服务器集群,建议改用tcp-ip方式配置节点列表。
  • 日志排查:添加足够的日志,方便后续排查哪个节点执行了任务,哪个节点跳过了执行。

内容的提问来源于stack exchange,提问作者Artur Vartanyan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 04:22:37