产品需求:需要一个任务调度模块,用户可以通过页面去新建任务,任务主要就是定时发送邮件,或者每周几去发送邮件,用户可以自定义发送的规则,且用户可以暂停任务、删除任务,也能知道任务的执行情况。
一. spring框架的@Scheduled 注解
传统的定时任务可以用spring框架的@Scheduled 注解来实现定时任务
如上图,我们在项目中就是利用@Scheduled注解配合cron表达式来实现每分钟同步数据的效果。这样也实现了定时任务的功能。
思考: 这样实现定时任务有什么弊端?
弊端一:如果单点架构要做负载均衡,则任务有可能会启动多次,严重的话可能会导致数据不一致问题,引发并发问题。分布式架构中如果任务模块只部署一个,虽然可以确保任务同一时间只运行一次,但无法保证高可用。
解决方式:引入分布式锁
如上图所示,在用户模块中写了同步浙政钉全量用户的脚本,通过定时任务启动。该任务模块中引入了redis分布式锁,保证同一时间只有一个定时任务会执行
弊端二:任务一旦启动,无法做到监管控制。例如:如果这个任务不想继续执行了,或者需要修改执行间隔,只能通过修改编码重新发布来达成。
二. Jdk自带的Timer、TimerTask实现定时任务
来看实现:
首先定义了Timer调度器,其次定义了TimerTask来定义任务,除了常规的执行定时任务以外,timer调度器还提供了cancel方法来取消执行,所以从一定程度上做到了对运行中任务的管控。
这种方式有何弊端呢?
分析源码:
Sched方法核心是将任务加入到一个队列中,且此方法加了锁,那么通过分析此段源码可以得出结论:timer线程安全,且是一个单线程操作。
接下来看核心代码:如何去触发定时任务
通过无限轮询(没有线程休眠)来实时判断任务是否需要执行。并且异常处理中只catch了
InterruptedException异常,这就意味着如果你的任务中抛出了运行时异常,就会杀死线程。
通过源码分析可以轻易的得出timer的弊端:
- 性能一般
- 定时器只有一个执行线程,如果遇见异常后面所有任务都会被取消掉(ScheduledThreadPoolExecutor实现了多线程解决了这个问题,有兴趣可以去了解下,不做详细解释了)
- 所有的任务都放在内存队列中,一旦服务重启或者任务失败,所有数据都会丢失,没有持久化。
三. Quartz框架
Quartz框架是一个开源的作业调度框架
它基于调度器(scheduler),job,jobdetail来实现任务调度
Scheduler调度器接口中提供了 增加工作,删除工作,暂停工作恢复工作的api接口,可以让开发人员自由地结合业务来实现各种各样的需求。
另外quartz也结合了多线程优化,持久化策略(可以结合jdbc),集群策略。
四.Xxl-job框架
是一个基于springboot的开源项目,提供了可视化界面,上手比较容易。
但个人感觉过度封装了,对于开发来讲,只需要提供底层能力即可,这种通过可视化界面去新增cron表达式,来触发定时任务的可能如果是纯技术的定时任务可以采用这个框架,如果是业务层面的定制化开发,那修改的成本会比较高。