@SchedulerLock注解配置不当导致数据重复问题解决方案

前言

在使用Spring Boot的分布式定时任务框架(如ShedLock)时,你是否遇到过明明加了@SchedulerLock注解,却依然出现任务重复执行、数据重复生成的诡异问题?本文将从真实案例出发,深入剖析lockAtLeastFor与lockAtMostFor的配置陷阱,并提供一整套解决方案,彻底告别数据重复!


一、问题现象:锁了,但没完全锁?

假设你的系统中有一个定时任务,每天凌晨自动统计订单数据并生成报表。为了防止多实例部署时重复执行,你使用了@SchedulerLock注解:

java 复制代码
@Scheduled(cron = "0 0 0 * * ?")
@SchedulerLock(name = "order_report_task", lockAtLeastFor = "300000") // 锁至少5分钟
public void generateOrderReport() {
    // 生成报表逻辑(偶尔出现重复数据!)
}

诡异现象‌:

  1. 日志显示多个实例几乎同时抢到锁!
  2. 数据库中出现完全相同的两份报表数据!
  3. 问题随机发生,尤其在任务执行时间较长时!

二、根因分析:你的锁可能是个"假锁"

1.被忽视的lockAtMostFor属性

致命误区‌:只配置lockAtLeastFor,未设置lockAtMostFor!

  • ShedLock默认的lockAtMostFor值为30s!
  • 当任务执行时间 > lockAtMostFor时,锁自动失效‌,其他实例将重新获取锁执行任务
java 复制代码
// 错误配置:lockAtMostFor使用默认30秒
@SchedulerLock(name = "order_report_task", lockAtLeastFor = "300000")

2.锁的"租期"模型解析

ShedLock的锁机制类似于租约:

  • lockAtLeastFor‌:最短租期,保证任务在此期间内不被其他节点抢占。
  • lockAtMostFor‌:最长租期,超时后强制释放锁(防止死锁)。

‌关键结论‌: 若任务实际执行时间超过lockAtMostFor,必然导致锁提前释放和任务重复!

三、解决方案:四步彻底消灭重复数据

1. 正确配置锁参数

公式‌:lockAtMostFor > 预估最大任务耗时 > lockAtLeastFor

java 复制代码
@SchedulerLock(
    name = "order_report_task",
    lockAtLeastFor = "5m",  // 最短持有5分钟
    lockAtMostFor = "24h"   // 最大允许24小时(必须大于任务最长时间!)
)

配置建议‌

  • 监控历史任务执行时间,取最大值的‌2倍‌作为lockAtMostFor。
  • 生产环境建议lockAtMostFor不低于1小时。

2. 添加任务幂等性校验

即使锁配置正确,仍需防范极端情况(如Full GC导致执行超时)。在任务逻辑中增加‌幂等性检查‌:

java 复制代码
public void generateOrderReport() {
    // 检查今天是否已生成报表
    LocalDate today = LocalDate.now();
    if (reportRepository.existsByReportDate(today)) {
        log.warn("今日报表已存在,跳过执行");
        return;
    }
    
    // 生成报表逻辑
}

3. 数据库唯一键兜底

在数据存储层添加‌唯一索引‌,彻底杜绝重复数据:

java 复制代码
ALTER TABLE order_report 
ADD UNIQUE INDEX udx_report_date (report_date);

4. 监控与告警

配置任务执行时长监控,及时发现异常任务:

java 复制代码
# Prometheus监控指标
shedlock_success_latest{name="order_report_task"} 
  - shedlock_success_latest{name="order_report_task"} offset 1h
  > 3600  # 执行时间超过1小时告警

四、避坑总结:锁的黄金法则

配置项 推荐值 错误示例
lockAtMostFor > 2倍最大任务耗时 默认30秒(导致重复)
lockAtLeastFor 预计任务耗时 + 缓冲时间(如5分钟) 不设置(可能过早释放)
幂等性检查 必须添加 依赖锁机制,无兜底
数据库唯一约束 强烈建议 无约束,依赖应用层校验

示例:

java 复制代码
@Scheduled(cron = "0 0 0 * * ?")
@SchedulerLock(
    name = "order_report_task",
    lockAtLeastFor = "5m",   // 保证短任务不被重复
    lockAtMostFor = "24h"    // 覆盖任务最大执行时间
)
public void generateOrderReport() {
    // 1. 幂等性检查
    if (checkReportExists()) return;
    
    // 2. 执行核心逻辑
    doGenerateReport();
    
    // 3. 记录执行成功(ShedLock自动更新)
}

总结

分布式定时任务的锁配置绝非简单的"加锁"即可,需要结合业务场景合理设计。记住:lockAtMostFor是防线,幂等性是底线,唯一约束是终极武器!用好这三板斧,从此告别重复数据!。

相关推荐
qq_124987075342 分钟前
基于SSM的动物保护系统的设计与实现(源码+论文+部署+安装)
java·数据库·spring boot·毕业设计·ssm·计算机毕业设计
Coder_Boy_1 小时前
基于SpringAI的在线考试系统-考试系统开发流程案例
java·数据库·人工智能·spring boot·后端
Mr_sun.1 小时前
Day06——权限认证-项目集成
java
瑶山1 小时前
Spring Cloud微服务搭建四、集成RocketMQ消息队列
java·spring cloud·微服务·rocketmq·dashboard
abluckyboy1 小时前
Java 实现求 n 的 n^n 次方的最后一位数字
java·python·算法
2301_818732061 小时前
前端调用控制层接口,进不去,报错415,类型不匹配
java·spring boot·spring·tomcat·intellij-idea
2501_941982051 小时前
深度对比:Java、Go、Python 实现企微外部群推送,哪个效率更高?
java·golang·企业微信
马猴烧酒.2 小时前
【面试八股|JAVA多线程】JAVA多线程常考面试题详解
java·服务器·数据库
sino爱学习2 小时前
高性能线程池实践:Dubbo EagerThreadPool 设计与应用
java·后端
风生u3 小时前
activiti7 详解
java