Spring Schedule + Redis 分布式锁 实现任务调度

集群下每个应用都如下配置执行,但只有获取到 redis 锁的应用才回去执行真实的业务操作
用到 redis 的 SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

@Scheduled(cron = "0 */1 * * * ?")
public void closeOrderTaskV2 {
    logger.info("关闭订单定时任务启动");
    //redis 分布式锁的上锁时间,5 秒后过期失效
    long lockTimeOut = 5000;
    Boolean setIfAbsentResult = redisUtil.setIfAbsent(Const.RedisLock.CLOSE_ORDER_TASK_LOCK,
    String.valueOf(System.currentTimeMillis() + lockTimeOut));

    if (setIfAbsentResult) {
        //若返回值为true则说明获取到了分布式锁,原先没有服务器占用锁
        closeOrder(Const.RedisLock.CLOSE_ORDER_TASK_LOCK);
    } else {
        logger.info("未获取到分布式锁");
    }
    logger.info("关闭订单定时任务结束");
}

private void closeOrder(String lockName) {
    
    logger.info("获取{}, ThreadName:{}", Const.RedisLock.CLOSE_ORDER_TASK_LOCK, Thread.currentThread().getName());
    Integer hour = mallProperties.getTask().getHour();
    orderService.closeOrder(hour);
    //释放锁
    redisUtil.delete(Const.RedisLock.CLOSE_ORDER_TASK_LOCK);
    logger.info("释放{}, ThreadName", Const.RedisLock.CLOSE_ORDER_TASK_LOCK, Thread.currentThread().getName());
}

进阶优化 结合Redisson(redis操作框架)实现Redis分布式锁
本质还是使用了 redis 的 SETNX 特性

@Scheduled(cron = "0 */1 * * * ?")
public void closeOrderTaskWithRedisson() {

    logger.info("关闭订单定时任务启动
    RLock lock = redissonManager.getRedisson().getLock(Const.RedisLock.CLOSE_ORDER_TASK_LOCK);
    boolean getLock = false;
    //尝试获取锁
    try {
        //是否获取到锁
        //如果不设置waitTime为0的话如果一个逻辑或者sql执行的非常快的情况下,就会造成另一个Tomcat进程也会获取到锁执行一遍schedule
        if (getLock = lock.tryLock(2, 5, TimeUnit.SECONDS)) {
            logger.info("Redisson 获取到分布式锁:{} ThreadName:{}", Const.RedisLock.CLOSE_ORDER_TASK_LOCK,
                        Thread.currentThread().getName());
            Integer hour = mallProperties.getTask().getHour();
            orderService.closeOrder(hour);
        } else {
            logger.info("Redisson 没有获取到分布式锁:{} ThreadName:{}", Const.RedisLock.CLOSE_ORDER_TASK_LOCK,
                        Thread.currentThread().getName());
        }
    } catch (InterruptedException e) {
        logger.error("Redisson 分布式锁获取异常");
    } finally {
        //未获取到锁的话就不需要释放锁,判断getLock
        if (!getLock) {
            return;
        }
        lock.unlock();
        logger.info("Redisson 释放分布式锁");
    }
    logger.info("关闭订单定时任务结束");
}

quartz 实现分布式定时任务

quartz 是一个开源的分布式调度库,它基于 java 实现。
quartz 提供两种基本作业存储类型:
1.RAMJobStore :RAM也就是内存,默认情况下 Quartz 会将任务调度存在内存中
2.JDBC作业存储:表信息(定时任务信息)初始化到 mysql 或其他数据库,运行的信息不会丢失

各种数据库版本的 quartz 初始化脚本:
https://github.com/quartz-scheduler/quartz/tree/master/quartz-core/src/main/resources/org/quartz/impl/jdbcjobstore

elastic-job

xxl-job:老版本基于quartz,新版本基于时间轮,使用数据库实现注册中心,一般用mysql,oracle极少
elastic-job: 基于 quartz,依赖 zookeeper 实现注册中心

在这里插入图片描述

xxl-job 的使用

当xxl-job应用本身集群部署(实现高可用HA)时,如何避免集群中的多个服务器同时调度任务

通过 mysql 悲观锁实现分布式锁(for update语句)setAutoCommit(false) 关闭隐式自动提交事务,
1. 启动事务 select lock for update(显式排他锁,其他事务无法进入&无法实现for update)读 db 任务信息 
2. 拉任务到内存时间轮 
3. 更新db任务信息commit提交事务,同时会释放 for update 的排他锁(悲观锁)

xxl-job 源码及案例地址

https://github.com/xuxueli/xxl-job.git

大致步骤

1.将下载好的源码(任务调度中心管理平台)导入 idea 中,构建数据库表,启动
2.在需要使用xxljob的服务编写xxljob的执行器
(1) 导入 maven 包
(2) 编写 xxljob 执行器配置
(3) 载入配置文件(在项目中创建 XxlJobConfig.java 文件),配置管理平台(调度中心)地址,用来把项目注册到管理平台
(4) 创建任务JobHandler
3.在 xxl-job-admin 配置任务,corn表达式,路由规则(决定用哪种规则调用),启动 xxl-job-admin 调度中心

参考 https://blog.csdn.net/qq_40378034/article/details/89219451
参考 https://www.freesion.com/article/1072641398/

1.导入结构如下

在这里插入图片描述

2.xxl-job-admin 为任务调度管理应用,启动即可看到管理界面,

在这里插入图片描述

3.xxl-job-executor-samples 为案例项目,选择springboot案例启动即可

在这里插入图片描述

4.业务项目中配置管理平台(调度中心)地址

xxl:
  job:
    admin:
      #调度中心部署跟地址:如调度中心集群部署存在多个地址则用逗号分隔。
	  #执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调"。
      addresses: http://127.0.0.1:8080/xxl-job-admin
    
    #分别配置执行器的名称、ip地址、端口号
	#注意:如果配置多个执行器时,防止端口冲突
    executor:
      appname: ${spring.application.name}
      ip: 127.0.0.1 # 这个执行器的地址
      port: 9999
      
      #执行器运行日志文件存储的磁盘位置,需要对该路径拥有读写权限
      logpath: /data/applogs/xxl-job/jobhandler
      #执行器Log文件定期清理功能,指定日志保存天数,日志文件过期自动删除。限制至少保持3天,否则功能不生效;
      #-1表示永不删除
      logretentiondays: -1

4.业务项目中配置管理平台(调度中心)地址

路由策略:当执行器集群部署时,提供丰富的路由策略,包括:

FIRST(第一个):固定选择第一个机器;
LAST(最后一个):固定选择最后一个机器;
ROUND(轮询):轮询;
RANDOM(随机):随机选择在线的机器;
CONSISTENT_HASH(一致性HASH):每个任务按照Hash算法固定选择某一台机器,且所有任务均匀散列在不同机器上。
LEAST_FREQUENTLY_USED(最不经常使用):使用频率最低的机器优先被选举;
LEAST_RECENTLY_USED(最近最久未使用):最久为使用的机器优先被选举;
FAILOVER(故障转移):按照顺序依次进行心跳检测,第一个心跳检测成功的机器选定为目标执行器并发起调度;
BUSYOVER(忙碌转移):按照顺序依次进行空闲检测,第一个空闲检测成功的机器选定为目标执行器并发起调度;
SHARDING_BROADCAST(分片广播):广播触发对应集群中所有机器执行一次任务,同时系统自动传递分片参数;可根据分片参数开发分片任务;

或者双活系统中只配置一个做定时任务来避免定时任务多次执行问题

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐