SpringBoot整合定时任务遇到的多实例问题

news2024/11/25 10:55:33

唠嗑部分

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

image-20231105153728831

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

以上所说的xxl-job,elastic-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、制作不易,一键三连再走吧,您的支持永远是我最大的动力!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1179556.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTML5的语义元素

HTML5语义元素&#xff1a; HTML5提供新的语义元素来明确一个web页面的不同部分&#xff1a;<head>、<nav>、<section>、<article>、<aside>、<figcation>、<figure>、<footer>。 1&#xff09;、<section>元素&#x…

11、Python文件操作:文件读写、文件对象方法、with语句

文章目录 文件读写模式文件对象方法with语句在Python中,文件操作是一项基本技能,它允许你读写文件,并与文件系统进行交互。这篇文章将详细介绍如何使用Python进行文件读写,涉及不同的文件模式,文件对象的方法,以及如何使用with语句来管理文件资源。 文件读写模式 在Pyt…

10、Python列表深入:列表推导式、列表常用方法、多维列表

文章目录 列表推导式列表常用方法多维列表列表是Python中非常灵活的内置数据类型,它们可以包含任意类型的对象,从数字到字符串甚至是其他列表。在这篇文章中,我们将深入探讨列表推导式、列表的常用方法以及多维列表的使用。 列表推导式 列表推导式提供了一种简洁的方法来创…

Django初窥门径-自定义用户模型

前言 自定义用户模型在Django应用中是一个重要的话题&#xff0c;它涉及到如何根据您的项目需求以及特定的用户身份验证和授权需求来调整用户模型。在以下前言中&#xff0c;我将讲述为什么自定义用户模型是如此重要以及其潜在的优势&#xff1a; 随着Web应用的不断发展&…

只改一行语句,锁这么多?

&#x1f449;导读 这篇文章我想来聊聊 MySQL 的锁是怎么加上的&#xff0c;为啥想聊这个呢&#xff1f;主要是因为业务中我们或多或少都会使用到锁&#xff0c;毕竟锁是保障我们数据安全性的关键法宝。但是由于不了解原理&#xff0c;往往可能导致我们在”刻意“或者”无意“的…

JavaScript从入门到精通系列第三十篇:详解JavaScript中的正则表达式语法

文章目录 前言 1&#xff1a;概念回顾 2&#xff1a;正则表达式 一&#xff1a;正则表达式 1&#xff1a;正则表达式字面量 2&#xff1a;检查是否有a或者b 3&#xff1a;检查是否有字母 4&#xff1a;检查是否有abc/aec/afc 5&#xff1a;检查除了ab 大神链接&#x…

Python教程:打印自己的名字

要打印的名字是&#xff1a;PYTHON …######… …#…#… …######… …#… …#… …#…#… …#…#… …##… …##… …##… …######… …##… …##… …##… …##… …#…#… …#…#… …######… …#…#… …#…#… …######… …#…#… …#…#… …#…#… …######… ……

【Spring实战——构建Spring Web应用程序】1.10 处理表单

引言 Web应用功能 ○ 提供内容 ○ 用户填写表单 ○ 提交数据 Spring MVC的控制器提供了 ○ 处理表单展示 ○ 用户提交数据的支持 在Spittr应用中&#xff0c;需要一个注册表单供新用户使用。SpitterController是一个新的控制器&#xff0c;目前只有一个请求处理方法用于展示…

LInux-0.11

文章目录 前言学习资料正文 前言 B站视频链接 linux 0.11 内核代码 学习资料 正文 一个山区512字节

稀土/铜催化剂电催化CO2制C2+或CH4

在电化学CO2还原反应&#xff08;CO2RR&#xff09;中&#xff0c;合理调控反应途径以生成所需产物是最重要的挑战之一。基于此&#xff0c;中国科学院化学研究所韩布兴院士和朱庆宫研究员等人报道了一系列稀土-铜混合相催化剂&#xff0c;通过调整催化剂的组成和结构&#xff…

Nodejs的安装以及配置(node-v12.16.1-x64.msi)

Nodejs的安装以及配置 1、安装 node-v12.16.1-x64.msi点击安装&#xff0c;注意以下步骤 本文设置nodejs的安装的路径&#xff1a;D:\soft\nodejs 继续点击next&#xff0c;选中Add to PATH &#xff0c;旁边的英文告诉我们会把 环境变量 给我们配置好 当然也可以只选择 Nod…

工业自动化工厂PLC远程控制网关物联网应用

远程控制网关在工厂自动化领域中起到了至关重要的作用&#xff0c;特别是在工厂PLC数据通讯方面。它充当着数据传输的桥梁&#xff0c;连接了工厂中的各类设备和系统&#xff0c;实现了远程监控和控制的功能。本文将详细介绍远程控制网关在工厂PLC数据通讯中的应用。 远程控制网…

Hadoop知识点全面总结

文章目录 什么是HadoopHadoop发行版介绍Hadoop版本演变历史Hadoop3.x的细节优化Hadoop三大核心组件介绍HDFS体系结构NameNode介绍总结 SecondaryNameNode介绍DataNode介绍DataNode总结 MapReduce介绍分布式计算介绍MapReduce原理剖析MapReduce之Map阶段MapReduce之Reduce阶段 实…

Langchain-Chatchat-win10本地安装部署成功笔记(CPU)

Langchain-Chatchat&#xff08;原Langchain-ChatGLM&#xff09;基于 Langchain 与 ChatGLM 等语言模型的本地知识库问答 | Langchain-Chatchat (formerly langchain-ChatGLM), local knowledge based LLM (like ChatGLM) QA app with langchain。 开源网址&#xff1a;https:…

leetcode周赛 第 370 场周赛

2923. 找到冠军 I 一场比赛中共有 n 支队伍&#xff0c;按从 0 到 n - 1 编号。 给你一个下标从 0 开始、大小为 n * n 的二维布尔矩阵 grid 。对于满足 0 < i, j < n - 1 且 i ! j 的所有 i, j &#xff1a;如果 grid[i][j] 1&#xff0c;那么 i 队比 j 队 强 &…

第五章:java构造方法与对象创建

系列文章目录 文章目录 系列文章目录前言一、构造方法&#xff08;构造器&#xff09;二、对象创建流程总结 前言 构造方法由程序自动调用&#xff0c;完成对象初始化。 一、构造方法&#xff08;构造器&#xff09; 构造方法又叫构造器(constructor)&#xff0c; 是类的一种…

将字符串转换为日期型对象date.fromisoformat(str)

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 将字符串转换为日期型对象 date.fromisoformat(str) 选择题 下列代码执行后&#xff0c;变量d的数据类型是? s 2023-11-01 d date.fromisoformat(s) print(f"【显示】s {s}") p…

大数据毕业设计选题推荐-家具公司运营数据分析平台-Hadoop-Spark-Hive

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

SLAM从入门到精通(车道线检测)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 对于slam而言&#xff0c;大家一般想到的就是去通过传感器找特征点&#xff0c;进而借助于特征点去定位机器人的位置。但是对于用户或者厂家来说&a…

Linux常用命令——cd命令

在线Linux命令查询工具 cd 切换用户当前工作目录 补充说明 cd命令用来切换工作目录至dirname。 其中dirName表示法可为绝对路径或相对路径。若目录名称省略&#xff0c;则变换至使用者的home directory(也就是刚login时所在的目录)。另外&#xff0c;~也表示为home directo…