目录
目录
前言
需求是写个同步方法,扫描任务表(关联表变动会记录在任务表),将数据库中改变数据同步到ES中,并定时调用。本文记录开发过程中遇到的问题及解决办法。
一、定时任务
定时任务是使用的SpringBoot中@EnableScheduling注解实现的,也就是在定时任务类上面添加此注解和@Component注解。@Scheduled(cron = "0 */5 * * * * ?")使用的cron表达式定义5分钟执行一次。
启动项目定时任务不执行,启动类配置文件都没有问题,在类上添加@Configuration注解解决此问题。网上找的:“主要用于标记配置类,兼备component的效果”。不清楚什么原因,很多定时任务的文章都没有加这个注解。
还有并发执行定时任务的方法,目前没用到,等实际用到了再来补。。。
二、Cron表达式
Cron表达式由6或7个空格分隔的时间字段组成:秒 分钟 小时 日期 月份 星期 年(可选),一般不使用年。
上述表达式中它的秒位为0,表示每个0秒,分位为*/5,意思是每5分钟。所以总的来说就是每5分钟(每5分0秒)时执行一次;
用短横线(-)表示时间段:
比如:我们的上班时间朝9晚6为,则cron表达式为:0 0 9-18 * *
用L表示最后,L是单词Last(最后的)的首字母:
比如:假设每个最后一天,下午2点发工资的时间,则cron表达式为:0 0 14 L * ?
如果没有具体说明是星期几,通常用问号代替
@Scheduled常用参数的差异
项目中没有使用Cron表达式,因为使用该表达式需要到达特定的时间才执行一次定时任务,这块使用的是(fixedDelay = 1000 * 60 * 60 * 1)
,使用此表达式能够在项目启动是调用一次。
1、fixedDelay控制方法执行的间隔时间,是以上一次方法执行完开始算起,如上一次方法执行阻塞住了,那么直到上一次执行完,并间隔给定的时间后,才执行下一次。
2、fixedRate是按照一定的速率执行,是从上一次方法执行开始的时间算起,如果上一次方法阻塞住了,下一次也是不会执行,但是在阻塞这段时间内累计应该执行的次数,当不再阻塞时,一下子把这些全部执行掉,而后再按照固定速率继续执行。
3、cron表达式可以定制化执行任务,但是执行的方式是与fixedDelay相近的,也是会按照上一次方法结束时间开始算起
三、分布式锁
定时任务写完了,测试也没有问题了。但是,写接口还需要考虑多方面,比如分布式部署情况下,定时任务会出现什么问题。
分布式锁:
当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。
如果是单机情况下(单JVM),线程之间共享内存,只要使用线程锁就可以解决并发问题。
如果是分布式情况下(多JVM),线程A和线程B很可能不是在同一JVM中,这样线程锁就无法起到作用了,这时候就要用到分布式锁来解决。
项目目中的问题及解决办法
项目部署在多台服务器上面,正常访问接口只有一台服务器发送请求,调用相应的接口。当我们使用定时任务时,每台服务器都会在同一时间调用同一接口,这样就会产生并发问题。而普通的线程锁是不能解决这个问题的,所以这块使用分布式锁保证接口的幂等性。
分布式锁的实现
1. 基于数据库(唯一索引)
2. 基于缓存(Redis,memcached,tair)
3. 基于Zookeeper
本项目中使用的是Redis实现分布式锁
@Slf4j
public class LockUtil {
private RedisTemplate<String, Long> redisTemplate;
private String key;
private Long overTime = new Long(1000 * 60 * 5);
private LockUtil(String key, RedisTemplate<String, Long> redisTemplate) {
this.key = key;
this.redisTemplate = redisTemplate;
}
public Boolean lock() {
BoundValueOperations<String, Long> lock = redisTemplate.boundValueOps(key);
log.info("加锁的key=====>{}", key);
long nowTime = System.currentTimeMillis();
log.info("加锁的时间=====>{}", nowTime);
Long expireTime = lock.get();
if (null != expireTime && (expireTime > nowTime)) {
return false;
}
Long newTime = nowTime + overTime;
Long oldTime = lock.getAndSet(newTime);
if (null == oldTime || Objects.equals(expireTime, oldTime)) {
return true;
}
return false;
}
public Boolean del() {
log.info("解锁的key=====>{}", key);
return redisTemplate.delete(key);
}
public static LockUtil get(String key, RedisTemplate<String, Long> redisTemplate) {
return new LockUtil(key, redisTemplate);
}
}