深入浅出,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启动后立即执行。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Neo4j深度学习

Neo4j的简介 Neo4j是用Java实现的开源NoSQL图数据库。从2003年开始开发&#xff0c;2007年正式发布第一版&#xff0c;其源码托管于GitHtb。Neo4j作为图数据库中的代表产品&#xff0c;已经在众多的行业项目中进行了应用&#xff0c;如&#xff1a;网络管理、软件分析、组织和…

电梯安全监测丨S271W无线水浸传感器用于电梯机房/电梯基坑水浸监测

城市化进程中&#xff0c;电梯与我们的生活息息相关。高层住宅、医院、商场、学校、车站等各种商业体建筑、公共建筑中电梯为我们生活工作提供了诸多便利。 保障电梯系统的安全至关重要&#xff01;特别是电梯机房和电梯基坑可通过智能化改造提高其安全性和稳定性。例如在暴风…

参与现场问题解决总结(Kafka、Hbase)

一. 背景 Kafka和Hbase在现场应用广泛&#xff0c;现场问题也较多&#xff0c;本季度通过对现场问题就行跟踪和总结&#xff0c;同时结合一些调研&#xff0c;尝试提高难点问题的解决效率&#xff0c;从而提高客户和现场满意度。非难点问题&#xff08;历史遇到过问题&#xf…

乌班图22.04 kubeadm简单搭建k8s集群

1. 我遇到的问题 任何部署类问题实际上对于萌新来说都不算简单&#xff0c;因为没有经验&#xff0c;这里我简单将部署的步骤和想法给大家讲述一下 2. 简单安装步骤 准备 3台标准安装的乌班图server22.04&#xff08;采用vm虚拟机安装&#xff0c;ip为192.168.50.3&#xff0…

Flutter横屏实践

1、Flutter设置横屏 // 强制横屏 SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft,DeviceOrientation.landscapeRight ]); // 强制竖屏 SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);另…

chromedriver下载与安装方法

下载与安装: 1.查看Chrome浏览器版本 首先&#xff0c;需要检查Chrome浏览器的版本。请按照以下步骤进行&#xff1a; 打开Chrome浏览器。 点击浏览器右上角的菜单图标&#xff08;三个垂直点&#xff09;。 选择“帮助”&#xff08;Help&#xff09;。 在下拉菜单中选择“…

el-table进阶(每条数据分行或合并)

最麻烦的还是css样式&#xff0c;表格样式自己调吧 <!-- ——————————————————————————————————根据数据拓展表格—————————————————————————————————— --> <div style"display: flex"&…

Python编程:创建图像浏览器应用程序

介绍&#xff1a; 图像浏览器应用程序是一种非常常见和实用的工具。它们使用户能够轻松地浏览和管理计算机中的图像文件。本文将介绍如何使用Python编程语言和wxPython库创建一个简单的图像浏览器应用程序。我们将学习如何利用Python的os模块进行文件和文件夹操作&#xff0c;以…

【探索AI潜能,连结现代通讯】相隔万里,我们与AI一同赏月。

1️⃣写在前面 近年来&#xff0c;AI得到了迅猛的发展&#xff0c;尤其是大模型的出现受到了广泛的关注和讨论&#x1f680;。ChatGPT、文心一言等纷纷登场&#xff0c;可谓是百家争鸣❗ 而AI大模型所延申出的子项目如AI绘画、AI写作等&#xff0c;在各自的领域展示出了惊人的…

Linux和Hadoop的学习

目录 1. Linux的常用快捷键2. Hadoop集群部署问题汇总 1. Linux的常用快捷键 复制&#xff1a;CtrlshiftC 粘贴&#xff1a;CtrlshiftV TAB&#xff1a;补全命令 编写输入&#xff1a;i 退出编写&#xff1a;esc 保存并退出&#xff1a;shift&#xff1a; 2. Hadoop集群部署问…

趣味工具箱小程序源码

趣味工具箱小程序源码&#xff0c;支持功能去水印&#xff0c;精选壁纸&#xff0c;图片压缩&#xff0c;文字生成二维码&#xff0c;图片加水印&#xff0c;模拟来电&#xff0c;手持弹幕&#xff0c;掷骰子…等 使用小工具&#xff0c;一个小程序有几十个功能。 源码下载&am…

2023全新小红书图集和视频解析去水印网站源码

2023全新小红书图集和视频解析去水印网站源码 小红书视频图集解析网站源码&#xff0c;在红书看到好看的图片以及好看的头像&#xff0c;但是直接下载又有水印就非常难受&#xff0c;这个可以一键解析去除水印&#xff0c;支持统计解析次数&#xff0c;本地接口。 源码下载&a…

【浅谈IDE宏指令录制】为加速chrome扩展国际化,我从vscode回归notepad++

vscode 的宏录制功能 —— 差强人意 安装vscode开源扩展&#xff1a;https://github.com/C10udburst/macros-vscode.git 可开启类似于 notetepad 的宏录制与回放功能&#xff01;比如录制字符串替换&#xff0c;能记录操作之时&#xff0c;替换对话框中的文本&#xff01;&am…

(论文调研) Multi-task的网络结构 在图像去噪问题中的应用

1.SNIDER: Single Noisy Image Denoising and Rectification for Improving License Plate Recognition 这是一篇用于实现端到端的车牌恢复 (LPR: License Plate Recognition) 网络, 其中使用去噪和校正网络来生成清晰的恢复图像, 以实现稳健的 LPR 性能. 这个网络的名称为SN…

知识图谱1_2——下载neo4j客户端

客户端下载 这里展现一种通过客户端进行操作的方法 https://neo4j.com/download/ 下载desktop客户端 填写完成后开始下载 下载完成后&#xff0c;在命令行输入 chmod x <文件名> #给予文件权限 sudo add-apt-repository universe #安装.appimage所需的包fuse&#x…

CSS点击切换或隐藏盒子的卷起、展开效果

<template><div class"main"><el-button click"onCllick">切换</el-button><transition name"slideDown"><div class"info" v-if"isShow">1111</div></transition></di…

【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…

select实现服务器并发

select的TCP服务器代码 #include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <sys/select.h> #include…

QChart使用说明

一.使用说明 Qt官网例程&#xff1a;https://doc.qt.io/qt-5/qtcharts-examples.html QChart&#xff1a;用于管理图表中的线、图例和轴的图形表示。可以简单理解为是一个画布。QChartView&#xff1a;视图组件&#xff0c;无法单独进行显示&#xff0c;需要依附其他组件进行…

SpringBoot的error用全局异常去处理

记录一下使用SpringBoot2.0.5的error用全局异常去处理 在使用springboot时&#xff0c;当访问的http地址或者说是请求地址输错后&#xff0c;会返回一个页面&#xff0c;如下&#xff1a; 这是因为请求的地址不存在&#xff0c;默认会显示error页面 但我们实际需要一个接口&a…