集群环境下的多定时任务处理
当我们在单体环境下写定时任务是一个简单的事情。但是当定时任务不止一个的时候就会出现一个问题,怎么管理这些定时任务?不进行管理,让他在代码中随意纷飞?如果过段时间出了问题就很尴尬,所以要想优雅的处理定时任务,我们需要达到以下几点。
- 统一处理
- 定时任务可配置化,即不能为了修改定时任务而重启系统
统一处理是为了便于定位解决问题和提高代码质量,定时任务可配置化是为了更优雅,举个栗子:当需求变更说定时任务的时间需要从一天一次变为一周一次时,我们去改动代码然后重新部署上线真的很繁琐。这样的话定时任务就最好不要写在业务代码中。这里来说一下常用的解决办法,使用xxl-job,这是一个常用的分布式任务调度平台,它的思路就是把专门有一个微服务去做任务监控与管理,项目则是另一个服务,当然当定时任务不超过十个的情况下用它还是有点过度引用的感觉,具体使用可以去看官方文档,作者是中国人文档易读。
当然如果定时任务比较少而不用一些三方平台的话,定时任务配置化这块就不太方便了。我们需要用再项目中将定时任务写到一个包里,切忌多人开发时分散的去写定时任务,这些应该是在项目开始就应该规划与约定好的。Java中对于实现定时任务的方式也有很多,比如说:Quartz
,Spring-Task
,还有Java
本身的Timer
。在SpringbBoot
下,由于Spring-Task
的以注解形式实现比较方便,所以我们一般用它实现定时任务。
这会我们说的定时任务都是在单机上执行的,如果说系统需要多实例运行,即一个nginx
负载着多个后端节点,那么这时的定时任务还能正常执行吗?那得看定时任务干什么了,定时任务删东西还好,多个定时任务同时删除一些数据,前一个定时任务删除完后,后一个定时任务可能判断好像没什么要删除了,有时候会造成资源浪费,并不会造成可视的问题,不过我们最好也需要做处理。但是比如说多个定时任务一起发邮件那可就出大问题了。用户会收到数份一模一样的邮件。
所以说写定时任务的前就要想到这里,那怎样去预防这个问题呢,答案就是上锁!我们需要设置一个锁,每次到约定时间时,仅有一个实例可以拿到锁去执行定时任务。常见的几种定时任务实现:
redis
分布式锁
我们使用redis
的setnx命令,我们可以通过setnx
将一个key
和value
写到redis
中,当这个key
不存在时,redis
会正常写入,返回1
。当在其中已经存在当前准备写入的key
时,redis
会插入失败,返回0
。
当多个实例准备执行定时任务时,通过setnx
用同一个key
去插入数据,仅有插入成功返回1的实例去执行任务。
我们简单写一下伪代码:
1 | //工具类 |
mysql
锁 我的思路是新建一张
TASK_LOCK
表。ID TASK_NAME LOCK_TMP 主键 定时任务标识 锁
LOCK_TMP
字段,默认为0。有几个定时任务就加几行数据,定时任务设置仅LOCK_TMP = 0
时执行定时任务。执行时根据TASK_NAME
查询LOCK_TMP
,当查到LOCK_TMP
字段为0
时,修改它为1
。再执行定时任务,执行完成后再将它的值改成0
。zookeeper
等其他调度中心既然都用上一些调度中心了,项目体量也算比较大了,这里经验不足,不做推荐,但感觉直接
xxl-job
挺好。