当前位置: 首页 > news >正文

深入浅出,SpringBoot整合Quartz实现定时任务与Redis健康检测(二)

前言

在上一篇深入浅出,SpringBoot整合Quartz实现定时任务与Redis健康检测(一)_往事如烟隔多年的博客-CSDN博客

文章中对SpringBoot整合Quartz做了初步的介绍以及提供了一个基本的使用例子,因为实际各自的需求任务不尽相同因此并未对定时任务的代码做相关填充。本文将对Redis的健康检测进行进一步的实现,并且将尝试逐步缩减相关代码,一步步优化定时任务的创建流程。

Redis定时检测

由于项目本身是一个学习类型项目,因此实际的使用数据量并不会特别大。当然引入Redis是考虑到在对热点数据重复访问时提升响应速度,关于缓存的实现方式多种多样,此处就以Redis为例。

问题复现

在引入Redis之后,某些接口在使用时需要从Redis中存取数据,就需要保证Redis的存活状态,否则会抛出异常。而如果在项目使用之初并没有配置Redis也将直接导致项目无法启动。此外在长时间不使用Redis时其连接池资源被释也会导致第一次访问时数据非常缓慢,以上的情景在使用时是非常糟糕的。

解决方案

在一些实际的开发场景中,遇到如上情况时可能会使用Hystrix来进行熔断,或者采用限流等措施。然而对于我们来讲,能够清楚的预见访问流量的有限性,即使只使用单机的数据库也能支撑服务,那么能否在Redis不可用时切换到MySQL查询呢?答案是可以的。

这里使用Redis中的ping命令来实现对Redis存活状态的检测,由于本项目中对于字符串类操作较多,这里工具使用了StringRedisTemplate进行封装。RedisOperator即为其它类中操作Redis的工具类,这里将其交由Spring容器管理。

@Component
public class RedisOperator {@Autowiredprivate StringRedisTemplate redisTemplate;/*** 07.19 新增ping,用于redis健康检测,连接失败的异常将不会返回给前端,也不会进入全局异常处理* @return*/public boolean ping(){boolean result;try{result = "PONG".equals(redisTemplate.getConnectionFactory().getConnection().ping());}catch (RedisConnectionFailureException e){log.error("捕获Redis连接异常,请检查服务运行状态!");result = false;}return result;}// 其它命令...}

上述代码通过使用ping命令的返回值判断Redis当前的存活状态,那么实际的使用场景如何呢?以下是一个简单例子:

可以看到如果不进行检测的话,此时Redis处于未连接状态时将造成阻塞,该方法会一直等待判断key是否存在解决状态的返回,最终达到超时时间后将抛出异常,体验非常糟糕。 

if (redisOperator.keyIsExist(shopCategoryCacheKey.toString())) {// 获取数据返回shopCategoryVOList = cacheOperator.readCache(shopCategoryCacheKey.toString(),new ArrayList<ShopCategoryVO>());return new PageVO(shopCategoryVOList, shopCategoryVOList.size());
}

那么如何检测呢?如上述代码不在少数,也就是说每次执行时都要发送ping命令执行验证Redis存活状态,而Redis只有在连接状态下才会及时返回,除此之外均需等待到超时结束返回异常。这里就可以用到前一篇文章的定时任务了,我们可以通过将ping命令执行交给定时任务进行检测,然后维持一个boolean类型的标识,每次判别标识即可。当Redis处于未连接状态时自然不会执行上述代码,进而执行数据库查询操作。

定时任务

由于上一篇文章中已经编写了配置类

@Configuration
@Slf4j
public class RedisCheckConfig {// 指定生成的Bean实例对象名称@Bean("redisCheck")public JobDetail jobDetail() {return JobBuilder.newJob(RedisCheckJob.class)// 任务名和任务分组.withIdentity("RedisCheckJob", "group").withDescription("任务描述:内存方式运行").storeDurably().build();}@Bean("redisTrigger")public Trigger trigger() {return TriggerBuilder.newTrigger()// 触发器名称和分组.withIdentity("redisCheck", "group").forJob(jobDetail()).startNow()// 使用SimpleSchedule构建定时任务.withSchedule(SimpleScheduleBuilder.simpleSchedule()// 每隔10s执行任务.withIntervalInSeconds(10)// 永不过期.repeatForever()).build();}
}

因此这里只需要编写实际的业务类即可,可以看到这里通过依赖注入获取到RedisOperator类,它用于获取ping命令执行后的结果。

@Slf4j
public class RedisCheckJob extends QuartzJobBean {@Autowiredprivate RedisOperator redisOperator;/*** Redis连接状态标识*/public static boolean redisConnected;@Overrideprotected void executeInternal(JobExecutionContext context) throws JobExecutionException {log.info("开始检测Redis连接状态");redisConnected = redisOperator.ping();log.info("Redis当前是否连接 "+ redisConnected);}
}

到这里就需要在进行Redis操作的代码前加入如下判断即可,这样就实现了Redis的一样定期保活和健康检测,一旦Redis处于未连接状态的将直接调用数据库查询,而定时器执行检测的间隔将由使用者自行设置。

// 判断Redis存活状态
if(RedisCheckJob.redisConnected){// Redis操作    if (redisOperator.keyIsExist(shopCategoryCacheKey.toString())) {// ....}
}

优化

虽然前文中已经对完成了文章开头所需要的解决的问题,但每一次创建新的定时任务时均需要编写JobDetail和Trigger代码,似乎有些冗余,能否对其对进一步优化呢? 实际上对于普通的定时任务使用上述操作即可,此处为个人学习探究内容,酌情观看。

抽象类 VS 接口

考虑到需要构建JobDetail和Trigger均需要使用name和group属性,因此考虑使用一个类进行管理,在SpringBoot中使用@Configuration和@Bean注解可以将定时任务配置类关联到Scheduler中,而若手动创建对象时则要考虑如何如何创建配置类?获取配置类?如何调用的问题?

在上一篇文中提到可以通过继承 QuartzJobBean 类并重写其excuteInternal方法,或实现 Job 接口的excute方法,从QuartzJobBean的源码可知,其实现了Job接口,因此以上的创建方式任选其一即可。  

这里就涉及到一个选择性的问题,最终编写的实现类应该具有任务名、分组名称、触发器这三个属性,它们将用于后续任务的构建。由于需要获取这三个属性,因此考虑使用抽象方法获取,因为最终的定时任务类为普通类,只负责信息的初始化,而目前来看若无需属性控制且均为抽象方法时,则可以将该类转为接口,这里无论选择抽象类还是接口,都需要确保最终实现了Job接口。

这里以接口为例构建,scheduleBuilder()方法用于接收实现类的定时器,通过实现该方法将由子类中自行决定使用哪种定时器。

public interface IntervalJobInterface extends Job {/*** 任务名称* @return*/String jobName();/*** 任务分组* @return*/String jobGroup();/*** 不同的任务定时器,由实现类构建* @return*/ScheduleBuilder scheduleBuilder();
}

编写定时任务类如下

@Slf4j
@Component
public class RedisCheckConfigForInterface implements IntervalJobInterface {@Autowiredprivate RedisOperator redisOperator;/*** 任务名称*/public String name = "redis-check";/*** 分组名称*/public String group = "redis";/*** 任务执行周期,单位s*/public Integer intervalTime = 60;/*** redis连接状态*/public static boolean redisConnected;@Overridepublic String jobName() {return name;}@Overridepublic String jobGroup() {return group;}/*** 不同定时器** @return*/@Overridepublic ScheduleBuilder scheduleBuilder() {return SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(intervalTime)// 永不过期.repeatForever();}/*** 定时任务逻辑**/@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {log.info("开始检测Redis连接状态,IntervalInterface");redisConnected = redisOperator.ping();log.info("Redis当前是否连接 "+ redisConnected);}
}

之后可以通过@Component或@Confiugration注解将其放入Spring的容器中,方便取用。 

@Component VS @Confiugration

如下为获取配置类信息的代码,其作用为获取 IntervalJobInterface 接口的所有实现类,然后通过实例获取其任务名和分组名,上一步中提到实现类的编写可以使用@Component或@Confiugration注解,那两者有什么区别呢?一般来说@Configuration注解与@Bean注解作用于配置类上,但目前代码中没有使用@Bean注解生成Bean实例,那么是否可以在任务类上直接使用@Configuration注解呢?

这里需要特别注意,若需要在其它地方通过反射机制获取如上任务类的属性时,@Configuration标注的类将使用cglib生成代理类,反射获取不能直接取得类信息获得属性,需要通过getClass().getSuperClass()的方式获取。而@Component注解作用的类生成的原有的类实例,可以直接getClass()获取类信息,再获取属性。

既然可以直接反射获取其相关属性,为什么还需要添加一个接口?由于反射获取的属性需要创建新对象重新组装,JobDetail和Trigger都需要一个实例类信息,添加一个接口可以获取信息的同时也能用作实例类的描述,详见如下代码:

@Component
@Slf4j
public class QuartzConfig {@Autowiredprivate ApplicationContext applicationContext;@Autowiredprivate Scheduler scheduler;/*** Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)* 获取所有实现了任务标记接口类,*/@PostConstructpublic void getInitBeans() {log.info("开始获取定时任务");Map<String, IntervalJobInterface> intervalJobInterfaceMap = applicationContext.getBeansOfType(IntervalJobInterface.class);intervalJobInterfaceMap.forEach((className, jobInstance) -> {IntervalJobInterface intervalJobs = (IntervalJobInterface) jobInstance;log.info("任务名称: " + intervalJobs.jobName());log.info("任务分组: " + intervalJobs.jobGroup());// 定时任务不存在,无法执行if (intervalJobs.scheduleBuilder() == null) {log.error(className + " 任务无法运行, 请指定任务的运行周期时间后再试!");return;}JobDetail jobDetail = jobDetail(intervalJobs);Trigger trigger = trigger(jobDetail, intervalJobs);try {scheduler.scheduleJob(jobDetail, trigger);// crontab 表达式的任务不会立即执行,如需立即执行则取消如下条件判断代码的注释//if (intervalJobs.scheduleBuilder() instanceof CronScheduleBuilder) {//    scheduler.triggerJob(jobDetail.getKey());//}log.info("已添加 " + intervalJobs.jobName() + " 任务 " + " jobKey" + jobDetail.getKey());} catch (SchedulerException e) {log.error("定时任务出现异常");e.printStackTrace();}});log.info("获取定时任务结束");}/*** 任务详情** @param intervalJobs* @return*/public JobDetail jobDetail(IntervalJobInterface intervalJobs) {return JobBuilder.newJob(intervalJobs.getClass()).withIdentity(intervalJobs.jobName(), intervalJobs.jobGroup()).withDescription("内存运行").storeDurably().build();}/*** 触发器** @return*/public Trigger trigger(JobDetail jobDetail, IntervalJobInterface intervalJobs) {return TriggerBuilder.newTrigger().withIdentity(intervalJobs.jobName(), intervalJobs.jobGroup()).forJob(jobDetail).startNow().withSchedule(intervalJobs.scheduleBuilder()).build();}}

此处的类使用了@Component注解,由于我们这个类中无需要获取的属性,这里使用@Configuration同样可以,甚至getInitBeans()也可以用@Bean注解。此处使用@PostConstruct注解是为了保证在容器加载完后会执行该方法,以完成定时任务的获取和后续执行。

立即执行

一般来讲,由CronScheduleBuilder构建的定时任务并不会启动后就立即执行(Trigger中添加了startNow(),但仅对SimpleScheduleBuilder生效),因此可以通过如下代码使其生效

// crontab 表达式的任务不会立即执行,如需立即执行则取消如下条件判断代码的注释
if (intervalJobs.scheduleBuilder() instanceof CronScheduleBuilder) {scheduler.triggerJob(jobDetail.getKey());
}

可以看到由CronScheduleBuilder构建的任务在SpringBoot启动后立即执行。

相关文章:

深入浅出,SpringBoot整合Quartz实现定时任务与Redis健康检测(二)

前言 在上一篇深入浅出&#xff0c;SpringBoot整合Quartz实现定时任务与Redis健康检测&#xff08;一&#xff09;_往事如烟隔多年的博客-CSDN博客 文章中对SpringBoot整合Quartz做了初步的介绍以及提供了一个基本的使用例子&#xff0c;因为实际各自的需求任务不尽相同因此并…...

小谈设计模式(22)—单例模式

小谈设计模式&#xff08;22&#xff09;—单例模式 专栏介绍专栏地址专栏介绍 单例模式点睛所在优缺点分析优点确保只有一个实例全局访问点节省资源线程安全 缺点难以扩展对象的生命周期单一职责原则隐藏依赖关系 Java程序实例实例a分析实例b&#xff0c;更安全分析优化 ——“…...

华为OD机考算法题:分班

题目部分 题目分班难度易题目说明幼儿园两个班的小朋友在排队时混在了一起&#xff0c;每位小朋友都知道自己是否与前面一位小朋友是否同班&#xff0c;请你帮忙把同班的小朋友找出来。 小朋友的编号为整数&#xff0c;与前一位小朋友同班用 Y 表示&#xff0c;不同班用 N 表示…...

【gcc】RtpTransportControllerSend学习笔记 3:gcc

本文是大神 https://www.cnblogs.com/ishen 的文章的学习笔记。大神的webrtc源码分析(8)-拥塞控制(上)-码率预估 详尽而具体,堪称神作。本文使用的代码是m79 ,与大神有不同。2.4 Probe只会在一些特殊的时候才会进行探测(链路刚开始时, 码率不正常暴跌时)2.5 : 对发送的吞吐量…...

CSP-J第二轮试题-2019年-3题

文章目录 参考&#xff1a;总结 [CSP-J2019] 纪念品题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 样例 #2样例输入 #2样例输出 #2 提示答案 现场真题注意事项 参考&#xff1a; P5662 CSP-J2019纪念品 总结 本系列为CSP-J/S算法竞赛真题讲解&#xff0c;会按照年…...

数据库:Hive转Presto(三)

继续上节代码。 import re import os import tkinter.filedialog from tkinter import *class Hive2Presto:def __int__(self):self.t_funcs [substr, nvl, substring, unix_timestamp] \[to_date, concat, sum, avg, abs, year, month, ceiling, floor]self.time_funcs [d…...

【AI视野·今日Robot 机器人论文速览 第四十八期】Thu, 5 Oct 2023

AI视野今日CS.Robotics 机器人学论文速览 Thu, 5 Oct 2023 Totally 32 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Robotics Papers LanguageMPC: Large Language Models as Decision Makers for Autonomous Driving Authors Hao Sha, Yao Mu, Yuxuan Jiang, Li…...

学信息系统项目管理师第4版系列20_风险管理

1. 针对不确定性的应对方法 1.1. 【高23上选58】 1.2. 收集信息 1.2.1. 可以对信息收集和分析工作进行规划&#xff0c;以便发现更多信息&#xff08;如进行研究、争取专家参与或进行市场分析&#xff09;来减少不确定性 1.3. 为多种结果做好准备 1.3.1. 制定可用的解决方…...

卷积神经网络的发展历史-VGG

VGG的产生 2014 年&#xff0c;Simonyan和Zisserman提出了VGG系列模型&#xff08;包括VGG-11/VGG-13/VGG-16/VGG-19&#xff09;&#xff0c;并在当年的ImageNet Challenge上作为分类任务第二名、定位&#xff08;Localization&#xff09;任务第一名的基础网络出现。 VGG的…...

qt解决信号和槽连接时传递额外参数的问题

解决信号和槽连接时传递额外参数的问题 QSignalMapper 是 Qt 框架中的一个类&#xff0c;用于解决信号和槽连接时传递额外参数的问题。当一个信号被触发时&#xff0c;QSignalMapper 可以将该信号与一个特定的参数关联起来&#xff0c;并将信号与对应的槽函数进行连接。 下面…...

『力扣每日一题14』:消失的数字

昨天忙过头&#xff0c;等想起来已经 12 点多了&#xff0c;于是乎断更了。在这里先跟广大读者说声抱歉&#xff0c;并且稍后我会再更一篇。 一、题目 数组nums包含从0到n的所有整数&#xff0c;但其中缺了一个。请编写代码找出那个缺失的整数。你有办法在O(n)时间内完成吗&…...

【b站韩顺平 快速学Java课】Java的JDK8(包括公共JRE8)安装教程 总结

最近开始学Java&#xff0c;如果是程序员的话要使用Java——需要安装JDK。 不同操作系统&#xff08;Win/Linux/Mac&#xff09;——需要安装不同的JDK。 1.JDK安装官网链接&#xff1a; Java Downloads | Oracle 我的电脑是win x64,以下笔记总结就暂且以本人电脑操作系统版本…...

Spark 弹性分布式数据集 RDD

1.RDD简介 `RDD` 全称为 Resilient Distributed Datasets,是 Spark 最基本的数据抽象,它是只读的、分区记录的集合,支持并行操作,可以由外部数据集或其他 RDD 转换而来,它具有以下特性: 一个 RDD 由一个或者多个分区(Partitions)组成。对于 RDD 来说,每个分区会被一个…...

电脑被删除的文件怎么恢复?2023年数据恢复方法分享

大多数人在使用电脑时都可能会遇到误删文件的情况。一不小心&#xff0c;重要的文件或数据就消失了&#xff0c;情急之下&#xff0c;大多会感到慌乱和无助。但其实&#xff0c;文件误删除并非不可挽回的灾难。本文将为大家介绍几种有效的文件恢复方法&#xff0c;以帮助大家在…...

李宏毅 2022机器学习 HW3 boss baseline 上分记录

作业数据是所有数据都有标签的版本。 李宏毅 2022机器学习 HW3 boss baseline 上分记录 1. 训练数据增强2. cross validation&ensemble3. test dataset augmentation4. resnet 1. 训练数据增强 结论&#xff1a;训练数据增强、更长时间的训练、dropout都证明很有效果&…...

SpringBatch适配不同数据库的两种方法

一、配置JobRepository Configuration EnableBatchProcessing public class TaskArrangeConfig extends DefaultBatchConfigurer {Autowiredprivate DataSource dataSource;Autowiredprivate JobLauncher jobLauncher;Autowiredprivate JobExplorer jobExplorer;Autowiredpriv…...

【ARM CoreLink 系列 5 -- CI-700 控制器介绍 】

文章目录 1.1 什么是 CI-700?1.1.1 关于 CI-7001.1.2 CI-700 特点1.2 全局配置参数1.2.1 寻址能力1.3 组件和配置1.3.1 CI-700 互联的结构1.3.2 Crosspoint(XP)1.3.3 外部接口1.4 组件(Components)1.1 什么是 CI-700? CI-700是一种AMBA 5 CHI互连,具有可定制的网状拓扑结构…...

找不到msvcp140_1.dll怎么办,快速解决msvcp140_1.dll问题的方法分享

在日常使用计算机的过程中&#xff0c;经常会遇到一些程序无法正常运行的问题&#xff0c;其中最常见的就是“msvcp140_1.dll丢失”。这是一个典型的DLL文件丢失问题&#xff0c;但背后的原因却往往并不简单。通过深入研究这个问题&#xff0c;我对其有了更深入的理解。 首先&a…...

华为云云耀云服务器L实例评测|部署私有网盘 Nextcloud

华为云云耀云服务器L实例评测&#xff5c;部署私有网盘 Nextcloud 一、云耀云服务器L实例介绍1.1 云服务器介绍1.2 产品规格1.3 应用场景 二、云耀云服务器L实例配置2.1 重置密码2.2 服务器连接2.3 安全组配置 三、部署 Nextcloud3.1 Nextcloud 介绍3.2 Docker 环境搭建3.3 Nex…...

vue3中使用插件vite-plugin-svg-icons

在vue3 vite 项目中使用svg图标 插件&#xff1a;vite-plugin-svg-icons 预加载 在项目运行时就生成所有图标,只需操作一次 dom高性能 内置缓存,仅当文件被修改时才会重新生成 安装 yarn add vite-plugin-svg-icons -D # or npm i vite-plugin-svg-icons -D # or pnpm inst…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用&#xff1a;作为微服务架构的网关&#xff0c;统一入口&#xff0c;处理所有外部请求。 核心能力&#xff1a; 路由转发&#xff08;基于路径、服务名等&#xff09;过滤器&#xff08;鉴权、限流、日志、Header 处理&#xff09;支持负…...

Python:操作 Excel 折叠

💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...

3.3.1_1 检错编码(奇偶校验码)

从这节课开始&#xff0c;我们会探讨数据链路层的差错控制功能&#xff0c;差错控制功能的主要目标是要发现并且解决一个帧内部的位错误&#xff0c;我们需要使用特殊的编码技术去发现帧内部的位错误&#xff0c;当我们发现位错误之后&#xff0c;通常来说有两种解决方案。第一…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述&#xff1a;iview使用table 中type: "index",分页之后 &#xff0c;索引还是从1开始&#xff0c;试过绑定后台返回数据的id, 这种方法可行&#xff0c;就是后台返回数据的每个页面id都不完全是按照从1开始的升序&#xff0c;因此百度了下&#xff0c;找到了…...

ESP32读取DHT11温湿度数据

芯片&#xff1a;ESP32 环境&#xff1a;Arduino 一、安装DHT11传感器库 红框的库&#xff0c;别安装错了 二、代码 注意&#xff0c;DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例

文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...

华为OD机试-食堂供餐-二分法

import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...

【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统

目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索&#xff08;基于物理空间 广播范围&#xff09;2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...

Docker 本地安装 mysql 数据库

Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker &#xff1b;并安装。 基础操作不再赘述。 打开 macOS 终端&#xff0c;开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...

推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)

推荐 github 项目:GeminiImageApp(图片生成方向&#xff0c;可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...