SpringBoot定时任务及分布式锁

本文介绍了在SpringBoot中使用@EnableScheduling和Cron表达式实现定时任务,详细讲解了Cron表达式的使用,包括时间字段、时间段和特殊字符。在分布式环境中,由于并发问题,引入了分布式锁的概念,解释了单机和分布式锁的区别,并展示了基于Redis的分布式锁实现。此外,还分享了解决多服务器下定时任务并发问题的解决方案。
摘要由CSDN通过智能技术生成


前言

需求是写个同步方法,扫描任务表(关联表变动会记录在任务表),将数据库中改变数据同步到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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值