摘要:

事情是这样的前两周在做项目的时候碰到一个需求---要求每天晚上执行一个任务,公司统一使用的是 xxl-job 写定时任务的,我当时为了方便自己,然后就简单的使用了Spring的那个@Scheduled来定时,当时写完觉得这也太方便了吧,以后我就只使用这个方法定时了,方便又快捷,用什么 xxl-job 呢(要什么自行车呢),哈哈。

需求:

要求在当天11:58的时候需要生成当天任务的报告,然后在第二天12:10得时候生成第二天得任务,-----本来我使用了两个@Scheduled注解,想用两个定时任务来做的,结果生成任务的那一个定时任务居然不起作用。。。然后刚好起作用的这个任务在不起作用的那个之前,所以我就在第一个任务执行时发消息到MQ,使用MQ的延时队列来做了。

换一种阅读体验方式吧,嘿嘿

一、第一版代码

    @Scheduled(cron = "0 58 23 */1 * ?")
@Override
public void createReprotTimer() {
// 发送到 MQ 12分钟后(00:10)--- 生成第二天的任务
String activeProfile = SpringUtil.getActiveProfile();
String tag = "prod".equals(activeProfile) ? "CREATE_TASK" : "CREATE_TASK_DEV";
producerUtil.sendTimeMsg(
"TID_COMMON",
tag,
"生成任务".getBytes(),
"CREATE_TASK",
System.currentTimeMillis() + 720 * 1000
);
// 每天晚上 11:58 生成报告
this.createReprot();
}

是不是贼简单,哈哈,一切看着没什么毛病,当然在测试环境测试时也没有任何问题,但是后面上了生产问题就暴露出来了

到生产发现的问题:测试环境一切正常,在生产环境一直会出现报告和任务重复生成的情况

原因:我们测试环境只有一台服务器,所以这个执行完全没毛病,但是到了正式环境时每个服务都是以集群的形式部署的,当时我这个服务部署在两台服务器上,所以每天到了11:58的时候两台服务器都会检测到我的定时,所以它们会执行两次。

二、第二版代码

方案:基于这个问题我立刻就想到了跨服务器肯定的使用第三方工具了,于是就使用了Redis来解决。

思路:执行这个方法的时候,先判断Redis中是否存在我存放的指定的业务Key,如果里面已经存在了这个Key,那么就说明已经生成过任务--执行过这个方法了,那么就直接跳过,如果判断里面没有Key---说明今天还没有执行过定时任务,则直接执行,然后再把Key压到Redis中并且定时一分钟后过期。

    @Scheduled(cron = "0 58 23 */1 * ?")
@Override
public void createReprotTimer() {
if (!redisUtil.hasKey("CREATE_TASK_TIME")) {
// // 发送到 MQ 12分钟后(00:10)--- 生成第二天的任务
String activeProfile = SpringUtil.getActiveProfile();
String tag = "prod".equals(activeProfile) ? "CREATE_TASK" : "CREATE_TASK_DEV";
producerUtil.sendTimeMsg(
"TID_COMMON",
tag,
"生成任务".getBytes(),
"CREATE_TASK",
System.currentTimeMillis() + 720 * 1000
);
// 每天晚上 11:58 生成报告
this.createReprot();
redisUtil.setEx("CREATE_TASK_TIME", "CREATE_TASK_TIME", 1, TimeUnit.MINUTES);
}
}

第二版代码出现的问题:不起作用,依然会重复执行

分析:貌似代码没问题,思路也没问题了。。。

原因:看似代码没任何问题----当然对于一般业务性质的问题是没有太大的问题的,但是我们这个场景是定时场景,那就意味着两台服务器肯定是同一时刻执行到这段代码的----他们同时判断Redis中是否有这个业务Key,那么这个它们肯定得到的结果就是Redis中没有这个Key,那个它们两个就会执行这段代码,导致生成重复的任务;

解决方法:问题原因找到了,那么也就意味着问题已经几乎解决了,很明显,这里只需要加一个分布式锁就可以了,保证在这个时间点上这段代码是顺序执行的就可以了。

三、第三版代码

分布式锁我这里使用的是Redisson,用法很简单,开箱即用--------算了写一下吧

1、引入依赖:
<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.16.3</version>
</dependency>
2、业务代码:
    @Scheduled(cron = "0 58 23 */1 * ?")
@Override
public void createReprotTimer() {
// 幂等处理,防止生成重复报告和发送重复MQ消息, 加锁:防止两台服务同时执行这段代码
RLock lock = redissonClient.getLock("CREATE_REPORT");
lock.lock(1, TimeUnit.MINUTES);
try {
if (!redisUtil.hasKey("CREATE_TASK_TIME")) {
// // 发送到 MQ 12分钟后(00:10)--- 生成第二天的任务
String activeProfile = SpringUtil.getActiveProfile();
String tag = "prod".equals(activeProfile) ? "CREATE_TASK" : "CREATE_TASK_DEV";
producerUtil.sendTimeMsg(
"TID_COMMON",
tag,
"生成任务".getBytes(),
"CREATE_TASK",
System.currentTimeMillis() + 720 * 1000
);
// 每天晚上 11:58 生成报告
this.createReprot();
redisUtil.setEx("CREATE_TASK_TIME", "CREATE_TASK_TIME", 1, TimeUnit.MINUTES);
}
} catch (Exception e) {
throw new ServiceException(OrderExceptionEnum.ORDER_FILE_CANCEL);
} finally {
lock.unlock();
}
}

稍微再解释一下吧:首先我们将这一段代码锁住,并且设置锁的超时时间为1分钟,防止出现所无法释放的情况,然后执行时去Redis中获取这个唯一的业务Key,如果没有就直接执行这个代码,并且执行完成之后将Key压入Redis中;如果已经有了我们要找的这个唯一的业务Key,那么就直接跳过即可。

完美解决---又是一个愉快的周末

关于为了一时方便,使用@Scheduled注解定时踩的坑的更多相关文章

  1. [MVC框架]利用@Scheduled注解创建定时执行的程序

    新写的项目中有一个地方要用到定时器,然后就用了spring的@Scheduled注解,顺手就给记录下来了,免得下次用的时候还要翻以前的项目,顺便分享出来,给没用过的兄弟姐妹们做个参考. 这次主要用的是 ...

  2. spring源码分析之定时任务Scheduled注解

    1. @Scheduled 可以将一个方法标识为可定时执行的.但必须指明cron(),fixedDelay(),或者fixedRate()属性. 注解的方法必须是无输入参数并返回空类型void的. @ ...

  3. spring @Scheduled注解执行定时任务

    以前框架使用quartz框架执行定时调度问题. 这配置太麻烦.每个调度都需要多加在spring的配置中. 能不能减少配置的量从而提高开发效率. 最近看了看spring的 scheduled的使用注解的 ...

  4. 使用spring @Scheduled注解执行定时任务

    以前框架使用quartz框架执行定时调度问题. 老大说这配置太麻烦.每个调度都需要多加在spring的配置中. 能不能减少配置的量从而提高开发效率. 最近看了看spring的 scheduled的使用 ...

  5. 使用轻量级Spring @Scheduled注解执行定时任务

    WEB项目中需要加入一个定时执行任务,可以使用Quartz来实现,由于项目就一个定时任务,所以想简单点,不用去配置那些Quartz的配置文件,所以就采用了Spring @Scheduled注解来实现了 ...

  6. 【转】使用spring @Scheduled注解执行定时任务

    http://blog.csdn.net/sd4000784/article/details/7745947 以前框架使用quartz框架执行定时调度问题. 老大说这配置太麻烦.每个调度都需要多加在s ...

  7. 使用spring @Scheduled注解运行定时任务、

    曾经框架使用quartz框架运行定时调度问题. 老大说这配置太麻烦.每一个调度都须要多加在spring的配置中. 能不能降低配置的量从而提高开发效率. 近期看了看spring的 scheduled的使 ...

  8. Spring 的@Scheduled注解实现定时任务运行和调度

    Spring 的@Scheduled注解实现定时任务运行和调度 首先要配置我们的spring.xml   ---  即spring的主配置文件(有的项目中叫做applicationContext.xm ...

  9. Spring Boot入门(三):使用Scheduled注解实现定时任务

    在程序开发的过程中,经常会使用定时任务来实现一些功能,比如: 系统依赖于外部系统的非核心数据,可以定时同步 系统内部一些非核心数据的统计计算,可以定时计算 系统内部的一些接口,需要间隔几分钟或者几秒执 ...

随机推荐

  1. 力扣 - 剑指 Offer 22. 链表中倒数第k个节点

    题目 剑指 Offer 22. 链表中倒数第k个节点 思路1(栈) 既然要倒数第k个节点,那我们直接把所有节点放到栈(先进后出)里面,然后pop弹出k个元素就可以了 代码 class Solution ...

  2. [bzoj1303]中位数图

    由于是排列,因此b一定只出现了一次,找到出现的位置并向左右扩展考虑如何判定是否满足条件,当且仅当$[左边比b小的数ls]+[右边比b小的数rs]=[左边比b大的数lb]+[右边比b大的数rb]$,暴力 ...

  3. [cf516E]Drazil and His Happy Friends

    令$d=\gcd(n,m)$,存在$x$和$y$使得$xn+i=ym+j$的充要条件是$i\equiv j(mod \ d)$,因此将$xd+i$(其中$0\le i<d$)作为一组,共有$d$ ...

  4. Trie树(字典树,单词查找树)详解+题目

    什么是字典树? 叫前缀树更容易理解 字典树的样子 Trie又被称为前缀树.字典树,所以当然是一棵树.上面这棵Trie树包含的字符串集合是{in, inn, int, tea, ten, to}.每个节 ...

  5. 全基因组选择育种(GS)简介

    全基因组选择(Genomic selection, GS)是一种利用覆盖全基因组的高密度标记进行选择育种的新方法,可通过早期选择缩短世代间隔,提高育种值(Genomic Estimated Breed ...

  6. nginx_日志切割脚本

    #!/bin/bash NGINX_LOG=/usr/loca/nginx/logs/access.log RE_LOG=/data/backup/`data +%Y%m%d` echo -e &qu ...

  7. MariaDB——显示所有数据库列表

    显示所有数据库列表:其中,information_schema.performance_schema.test.mysql,这4个库表是数据库系统自带的表,一般不放数据. 进入某个库 切换库,并显示库 ...

  8. R连接mysql数据库方法详解

    Warning messages: 1: In odbcDriverConnect("DSN=Rdata;UID=root") : [RODBC] ERROR: state IM0 ...

  9. MapReduce01 概述

    MapReduce 概述 目录 MapReduce 概述 1.定义 2.优缺点 优点 缺点 3.MapReduce核心思想 4.MapReduce进程 5.官方 WordCount 源码 6.常用数据 ...

  10. day12 函数嵌套

    day12 函数嵌套 一. args与kwargs def index(a,b,c): print(a,b,c) def wrapper(*args,**kwargs): # args=(1,2,3) ...