集群环境下的多定时任务处理

​ 当我们在单体环境下写定时任务是一个简单的事情。但是当定时任务不止一个的时候就会出现一个问题,怎么管理这些定时任务?不进行管理,让他在代码中随意纷飞?如果过段时间出了问题就很尴尬,所以要想优雅的处理定时任务,我们需要达到以下几点。

  • 统一处理
  • 定时任务可配置化,即不能为了修改定时任务而重启系统

​ 统一处理是为了便于定位解决问题和提高代码质量,定时任务可配置化是为了更优雅,举个栗子:当需求变更说定时任务的时间需要从一天一次变为一周一次时,我们去改动代码然后重新部署上线真的很繁琐。这样的话定时任务就最好不要写在业务代码中。这里来说一下常用的解决办法,使用xxl-job,这是一个常用的分布式任务调度平台,它的思路就是把专门有一个微服务去做任务监控与管理,项目则是另一个服务,当然当定时任务不超过十个的情况下用它还是有点过度引用的感觉,具体使用可以去看官方文档,作者是中国人文档易读。

​ 当然如果定时任务比较少而不用一些三方平台的话,定时任务配置化这块就不太方便了。我们需要用再项目中将定时任务写到一个包里,切忌多人开发时分散的去写定时任务,这些应该是在项目开始就应该规划与约定好的。Java中对于实现定时任务的方式也有很多,比如说:QuartzSpring-Task,还有Java本身的Timer 。在SpringbBoot下,由于Spring-Task的以注解形式实现比较方便,所以我们一般用它实现定时任务。

​ 这会我们说的定时任务都是在单机上执行的,如果说系统需要多实例运行,即一个nginx负载着多个后端节点,那么这时的定时任务还能正常执行吗?那得看定时任务干什么了,定时任务删东西还好,多个定时任务同时删除一些数据,前一个定时任务删除完后,后一个定时任务可能判断好像没什么要删除了,有时候会造成资源浪费,并不会造成可视的问题,不过我们最好也需要做处理。但是比如说多个定时任务一起发邮件那可就出大问题了。用户会收到数份一模一样的邮件。

​ 所以说写定时任务的前就要想到这里,那怎样去预防这个问题呢,答案就是上锁!我们需要设置一个锁,每次到约定时间时,仅有一个实例可以拿到锁去执行定时任务。常见的几种定时任务实现:

  • redis分布式锁

​ 我们使用redissetnx命令,我们可以通过setnx将一个keyvalue写到redis中,当这个key不存在时,redis会正常写入,返回1。当在其中已经存在当前准备写入的key时,redis会插入失败,返回0

​ 当多个实例准备执行定时任务时,通过setnx用同一个key去插入数据,仅有插入成功返回1的实例去执行任务。

​ 我们简单写一下伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//工具类
public class TaskUtil{

//引入RedisTemplate对象
@Autowired
private StringRedisTemplate redisTemplate;

//竞争锁方法
public Boolean getRedisLock(String key, String value, Long limitTime){
//尝试向redis写入key
if(redisTemplate.opsForValue().setIfAbsent(key,value)){
// 设置锁的有效时间,防止死锁
redisTemplate.expire(key,limitTime,TimeUnit.MILLISECONDS);
return true;
} else {
return false;
}
}
}


//定时任务类
public class ScheduledService {
//引入刚写的工具类
@Autowired
private TaskUtil util;

@Scheduled(cron = "0/5 * * * * *")
public void scheduled(){
if(util.getRedisLock("lock","lock1",1L)){
System.out.println("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挺好。