SpringBoot整合定时任务遇到的多实例问题-CSDN博客

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

唠嗑部分

是这样前几日完善了定时任务的日志记录今日切换了服务器多部署了一个节点使用nginx负载均衡但是查看日志却发现了如下情况

image-20231105153728831

那糟糕了传说中的多实例问题出现了今天我们就来聊聊项目实战中定时任务如何做首先我们看如下问题

1、什么是定时任务能帮我们解决什么实际问题

见名知意定时任务就是让程序指定时间去执行某段代码例如每日8点给女朋友发早安祝福

那么能给我们开发中解决什么问题呢

在实际开发中有许多需要定时任务的场景如每日定时去同步数据、缓存的预热、定时清理日志文件、定时统计榜单…

2、项目实战中哪些场景需要使用到定时任务

需求一产品经理要求实现系统的3天内热搜榜每日0点更新数据

需求二系统需要依赖第三方系统的数据而且请求并发较大第三方数据是每日更新的

需求三系统每天都会有大量操作日志产品经理要求只保留一个月的数据

需求四对于系统主页数据每日9-12点并发最大需要定时对缓存预热

以上需求都可以用定时任务实现

3、推荐使用的定时任务组件有哪些

Spring整合了Scheduled轻量级而且很好用无UI展示

xxl-Jobxxl是xxl-job的开发者大众点评的许雪里名称的拼音开头主要用于处理分布式的定时任务其主要由调度中心和执行器组成有良好的UI界面。

elastic-JobElastic-Job是当当网推出的分布式任务调度框架用于解决分布式任务的协调调度问题保证任务不重复不遗漏地执行无UI展示需要分布式协调工具Zookeeper的支持

4、如何实现分布式定时任务避免多实例问题

首先我们来说说什么是多实例问题在我们的项目开发中我们在部署定时任务时通常只部署一台机器如果部署多台机器时同一个任务会执行多次(每个机器都会执行互不影响)那如果有一些给用户计算收益定时任务每天定时给用户计算收益如果部署了多台同一个用户将重复计算多次收益那就芭比Q了那如果只部署一台则会有单点故障问题可用性无法保证

以上所说的xxl-jobelastic-Job均可以解决多实例问题保证任务不重复不遗漏地执行

那我们使用Spring自带的Scheduled如何避免多实例问题呢我们可以使用redis锁来保证具体逻辑如下

每个实例调用setnx命令插入一条数据插入成功后返回1的实例执行job返回0的不执行

言归正传

首先我们看下之前的代码逻辑我这里是整合的Scheduled自行封装的定时任务在执行时没有解决多实例问题

image-20231105153919796

那我们的逻辑是在此段代码执行时加入redis锁保证执行一次

1、redis加锁方法封装

/**
* 加锁
* @param key
* @param timeStamp
* @return
*/
public Boolean lock(String key, String timeStamp){
    if (redisTemplate.opsForValue().setIfAbsent(getKey(key), timeStamp)) {
        return true;
    }
    String currentLock = (String) redisTemplate.opsForValue().get(getKey(key));
    if (StringUtils.hasLength(currentLock) && Long.parseLong(currentLock) < System.currentTimeMillis()) {
        String preLock = (String) redisTemplate.opsForValue().getAndSet(getKey(key), timeStamp);

        if (StringUtils.hasLength(preLock) && preLock.equals(currentLock)) {
            return true;
        }
    }
    return false;
}

/**
* 解锁
* @param key
* @param timeStamp
*/
public void unLock(String key, String timeStamp){
    try {
        String currentValue = (String) redisTemplate.opsForValue().get(getKey(key));
        if (StringUtils.hasLength(currentValue) && currentValue.equals(timeStamp)) {
            redisTemplate.opsForValue().getOperations().delete(getKey(key));
        }
    } catch (Exception e) {
        log.error("解锁异常");
    }
}

2、多实例解决实现逻辑

public void run() {
    long startTime = System.currentTimeMillis();
    Map<String, Scheduled> scheduledMap = scheduledTaskService.getScheduledMap();
    ScheduledLog scheduledLog = new ScheduledLog();
    Scheduled scheduled = scheduledMap.get(beanName);
    Boolean flag = Boolean.TRUE;
    String timeStamp = String.valueOf(System.currentTimeMillis() + 300L);
    try {
        Boolean lock = redisUtil.lock(redisUtil.getCacheKey(CachePrefixContent.LOCK_PREFIX, beanName), timeStamp);
        if (lock) {
            BaseResult result = BaseResult.ok();
            scheduledLog.setTaskId(scheduled.getTaskId());
            scheduledLog.setExecuteTime(LocalDateTime.now());
            // 执行定时任务处理逻辑
            execute(result);
            if (result.resOk()) {
                scheduledLog.setExecuteStatus(Boolean.TRUE);
            } else {
                scheduledLog.setExecuteStatus(Boolean.FALSE);
            }
            scheduledLog.setExecuteDesc(result.getMsg());
            redisUtil.unLock(redisUtil.getCacheKey(CachePrefixContent.LOCK_PREFIX, beanName), timeStamp);
        } else {
            flag = Boolean.FALSE;
        }
    } catch (Exception e) {
        log.error("定时任务:{}执行失败,{}", scheduled.getTaskName(), e);
        scheduledLog.setExecuteStatus(Boolean.FALSE);
        scheduledLog.setExecuteDesc(e.getMessage());
    } finally {
        long endTime = System.currentTimeMillis();
        log.info("【{}】【】【{}ms】", "定时任务", scheduled.getTaskName(), endTime - startTime);
        if (flag) {
            completableFutureService.runAsyncTask(() -> {
                scheduledLogMapper.insert(scheduledLog);
            });
        }
    }
}

3、效果展示

每30秒两个示例只有单台节点执行成功

image-20231105162053036

结语

1、以上问题就解决了快去给你的代码加上吧

2、制作不易一键三连再走吧您的支持永远是我最大的动力

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Spring

“SpringBoot整合定时任务遇到的多实例问题-CSDN博客” 的相关文章