摘要:

事情是这样的前两周在做项目的时候碰到一个需求---要求每天晚上执行一个任务,公司统一使用的是 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. python接口之request测试:以json格式发送post请求,.json方法,查看响应结果的情况

    json和dict python中的dict类型要转换为json格式的数据需要用到json库: import json <json> = json.dumps(<dict>) ...

  2. [loj6203]可持久化队列

    对于每一个节点,我们只需要知道他上len次插入(len是这个队列的元素个数)时所插入的元素就可以了 那么只需要将所有插入建为一棵树,然后找len次祖先就可以了,这个用倍增维护即可 还有一种比较神奇的做 ...

  3. [luogu3292]幸运数字

    考虑点分治,将询问离线后计算重心到每一个点的线性基,然后再询问重心到每一个点的线性基,时间复杂度为$o(3600q)$,可以过(然而太菜的我写了倍增维护线性基,震惊于倍增和线性基常数之小) 1 #in ...

  4. ES6学习 第二章 变量的解构赋值

    前言 该篇笔记是第二篇 变量的解构赋值. 这一章原文链接: 变量的解构赋值 解构赋值 ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring). 解构 ...

  5. SSM整合小项目

    1.文件目录结构 2.MyBatis配置 创建数据库环境 CREATE DATABASE `ssmbuild`; USE `ssmbuild`; DROP TABLE IF EXISTS `books ...

  6. 实用QPS和TPS高的高效分析方法

    现在主库的MySQL的QPS一直在3K/s左右,想知道其到底执行了那些SQL,或者是那些SQL执行的次数比较多: 腾讯云的后台监控: 开启腾讯云的SQL审计后,下载几分钟SQL日志文件, 下列语句在M ...

  7. HDU 6987 - Cycle Binary(找性质+杜教筛)

    题面传送门 首先 mol 一发现场 AC 的 csy 神仙 为什么这题现场这么多人过啊啊啊啊啊啊 继续搬运官方题解( 首先对于题目中的 \(k,P\)​,我们有若存在字符串 \(k,P,P'\)​ 满 ...

  8. 洛谷 P6177 - Count on a tree II/【模板】树分块(树分块)

    洛谷题面传送门 好家伙,在做这道题之前我甚至不知道有个东西叫树分块 树分块,说白了就是像对序列分块一样设一个阈值 \(B\),然后在树上随机撒 \(\dfrac{n}{B}\) 个关键点,满足任意一个 ...

  9. pycurl报错: ImportError: pycurl: libcurl link-time ssl backend (openssl) is different from compile-time ssl backend

    报错: ImportError: pycurl: libcurl link-time ssl backend (openssl) is different from compile-time ssl ...

  10. Git 使用,本地项目上传到GitHub远程库

    Git 使用,本地项目上传到GitHub远程库 环境 GitHub账号 点此进入github官网 git客户端工具 点此进入git下载页 本地项目上传到 GitHub 在GitHub中创建一个仓库(远 ...