Spring@Scheduled定时任务接入XXL-JOB的一种方案(基于SC Gateway)

背景

目前在职的公司,维护着Spring Cloud分布式微服务项目有25+个。其中有10个左右微服务都写有定时任务逻辑,采用Spring @Scheduled这种方式。

Spring @Scheduled定时任务的缺点:

  1. 不支持集群:为避免重复执行,需引入分布式锁
  2. 死板不灵活:不支持手动执行,单次执行,补偿执行,修改任务参数,暂停任务,删除任务,修改调度时间,失败重试
  3. 无报警机制:任务失败之后没有报警机制,逻辑执行异常记录ERROR日志接入Prometheus告警这种方式不算,这算是日志层面的告警,而不是任务层面的告警机制
  4. 不支持分片任务:处理有序数据时,多机器分片执行任务处理不同数据
  5. ……

基于此,考虑引入轻量级分布式定时调度框架XXL-JOB,即把定时任务迁到XXL-JOB平台。

关于XXL-JOB,可参考之前的blog。

设计方案

考虑到我们有10+个SC分布式应用,30+个定时任务。如果每个应用都需要迁移改造的话,则每个应用都需要配置XXL-JOB相关的信息。当然,这可以通过Apollo namespace共享继承机制来实现。题外话:有空的话,后面会写一篇Apollo namespace配置继承的blog。

也就是说,我可以在一个应用里(一个应用对应着一个Apollo namespace)的Apollo里维护好XXL-JOB的配置信息,其他应用通过复用此应用(的Apollo)来实现配置复用。

但是每个应用还得新增一个配置类,配置类怎么实现复用呢?这也能解决。解决方案就是在commons组件库里维护配置类(需要引入Spring @Configuration注解,即引入spring-context依赖包),然后每个应用的Spring Boot启动类里需要扫描到此配置类。

还得改造一下30+个定时任务对应的30+个@@Component定时任务类,所有的定时任务应用都需要引入maven依赖。

还得手动在XXL-JOB里新增定时任务类。

看起来还不错的方案,但是不排除不同的应用有同名的配置,遇到同名的配置,则需要修改配置命名。Spring Boot启动类改造可能会带来未知的问题。

最后的最后,考虑到我们所有的应用都需要经过Gateway网关服务来转发,不管是对内的应用,还是对外的应用,对外的应用有包括C端,B端,和第三方客户。故而有下面的最终方案。

实现方案

在对内的网关应用里,引入maven依赖:

<dependency><groupId>com.xuxueli</groupId><artifactId>xxl-job-core</artifactId><version>2.4.0</version>
</dependency>

新增如下XXL-JOB配置类:

@Slf4j
@Configuration
public class XxlJobConfig {@Value("${xxl.job.admin.addresses}")private String adminAddresses;@Value("${xxl.job.executor.appname}")private String appName;@Value("${xxl.job.executor.port:9999}")private int port;@Value("${xxl.job.accessToken:default_token}")private String accessToken;@Beanpublic XxlJobSpringExecutor xxlJobExecutor() {log.info(">>>>>>>>>>> xxl-job config init.");XxlJobSpringExecutor executor = new XxlJobSpringExecutor();executor.setAdminAddresses(adminAddresses);executor.setAppname(appName);executor.setPort(port);executor.setAccessToken(accessToken);return executor;}
}

对应的,需要在Apollo里新增如下配置。其中有些配置是固定不变的,可以放在本地配置文件里;未来有可能变化的,放在Apollo里。
在这里插入图片描述
这里的appname实际上就是XXL-JOB的执行器:
在这里插入图片描述
gateway服务是以pod形式运行在k8s集群里,不言而喻,采用自动注册这种方式。

网关服务里新增定时任务解析,请求转发配置类:

@Slf4j
@Component
public class XxlJobLogicConfig {private static final String URL = "url:";private static final String METHOD = "method:";private static final String DATA = "data:";private static final String GET = "GET";private static final String POST = "POST";@XxlJob("httpJobHandler")public void httpJobHandler() {// 参数解析及校验String jobParam = XxlJobHelper.getJobParam();if (StringUtils.isBlank(jobParam)) {XxlJobHelper.log("param[" + jobParam + "] invalid");XxlJobHelper.handleFail();return;}String[] httpParams = jobParam.split("\n");String url = "";String method = "";String data = "null";for (String httpParam : httpParams) {if (httpParam.startsWith(URL)) {url = httpParam.substring(httpParam.indexOf(URL) + URL.length()).trim();}if (httpParam.startsWith(METHOD)) {method = httpParam.substring(httpParam.indexOf(METHOD) + METHOD.length()).trim().toUpperCase();}if (httpParam.startsWith(DATA)) {data = httpParam.substring(httpParam.indexOf(DATA) + DATA.length()).trim();}}if (StringUtils.isBlank(url)) {XxlJobHelper.log("url[" + url + "] invalid");XxlJobHelper.handleFail();return;}if (!GET.equals(method) && !POST.equals(method)) {XxlJobHelper.log("method[" + method + "] invalid");XxlJobHelper.handleFail();return;}log.info("xxlJob调度请求url={},请求method={},请求数据data={}", url, method, data);// 判断是否为POST请求boolean isPostMethod = POST.equals(method);HttpURLConnection connection = null;BufferedReader bufferedReader = null;try {URL realUrl = new URL(url);connection = (HttpURLConnection) realUrl.openConnection();// 设置具体的方法,也就是具体的定时任务connection.setRequestMethod(method);// POST请求需要outputconnection.setDoOutput(isPostMethod);connection.setDoInput(true);connection.setUseCaches(false);connection.setReadTimeout(900 * 1000);connection.setConnectTimeout(600 * 1000);// connection:Keep-Alive 表示在一次http请求中,服务器进行响应后,不再直接断开TCP连接,而是将TCP连接维持一段时间。// 在这段时间内,如果同一客户端再次向服务端发起http请求,便可以复用此TCP连接,向服务端发起请求。connection.setRequestProperty("connection", "keep_alive");// Content-Type 表示客户端向服务端发送的数据的媒体类型(MIME类型)connection.setRequestProperty("content-type", "application/json;charset=UTF-8");// Accept-Charset 表示客户端希望服务端返回的数据的媒体类型(MIME类型)connection.setRequestProperty("Accept-Charset", "application/json;charset=UTF-8");// gateway请求转发到其他应用connection.connect();// 如果是POST请求,则判断定时任务是否含有执行参数if (isPostMethod && StringUtils.isNotBlank(data)) {DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream());// 写参数dataOutputStream.write(data.getBytes(Charset.defaultCharset()));dataOutputStream.flush();dataOutputStream.close();}int responseCode = connection.getResponseCode();// 判断请求转发、定时任务触发是否成功if (responseCode != 200) {throw new RuntimeException("Http Request StatusCode(" + responseCode + ") Invalid");}bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charset.defaultCharset()));StringBuilder stringBuilder = new StringBuilder();String line;while ((line = bufferedReader.readLine()) != null) {stringBuilder.append(line);}String responseMsg = stringBuilder.toString();log.info("xxlJob调度执行返回数据={}", responseMsg);XxlJobHelper.log(responseMsg);} catch (Exception e) {XxlJobHelper.log(e);XxlJobHelper.handleFail();} finally {try {if (bufferedReader != null) {bufferedReader.close();}if (connection != null) {connection.disconnect();}} catch (Exception e) {XxlJobHelper.log(e);}}}
}

稍微有点麻烦的是,每个Spring Cloud应用都需要手动新增一个ScheduleController:

/*** 定时任务入口,所有服务的@RequestMapping满足/schedule/appName这种格式,方便统一管理**/
@RestController
@RequestMapping("/schedule/search")
public class ScheduleController {@Resourceprivate ChineseEnglishStoreSchedule chineseEnglishStoreSchedule;@GetMapping("/chineseEnglishStoreSchedule")public Response<Boolean> chineseEnglishStoreSchedule() {chineseEnglishStoreSchedule.execute();return Response.success(true);}
}

另外,需要在gateway网关服务里新增路由转发规则:
在这里插入图片描述
每个有定时任务,且准备接入XXL-JOB平台的SC微服务,都需要新增类似上面截图里的4条配置信息。

优点:所有带有定时任务的服务一目了然,方便统一维护和管理。

这种方案无需改造具体的某个Schedule类:

@JobHander(value = "autoJobHandler")
public class AutoJobHandler extends IJobHandler {@Overridepublic ReturnT<String> execute(String... params) {try {// 既有的业务逻辑// 执行成功return ReturnT.SUCCESS;} catch (Exception e) {logger.error("execute error id:{}, error info:{}", id, e);return ReturnT.FAIL;}return ReturnT.SUCCESS;}
}

最后都省却不了的一个步骤,在XXL-JOB admin管理平台新增一个个任务:
在这里插入图片描述

验证

任务调度的执行日志:
在这里插入图片描述
ELK日志查询平台里也可以搜索到逻辑代码里打印的日志。

参考

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

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

相关文章

【VMware】CentOS 设置静态IP(Windows 宿主机)

文章目录 1. 更改网络适配器设置2. 配置虚拟网络编辑器3. 修改 CentOS 网络配置文件4. ping 测试结果 宿主机&#xff1a;Win11 22H2 虚拟机&#xff1a;CentOS-Stream-9-20230612.0 (Minimal) 1. 更改网络适配器设置 Win R&#xff1a;control 打开控制面板 依次点击&#x…

【应用层】网络基础 -- HTTPS协议

HTTPS 协议原理加密为什么要加密常见的加密方式对称加密非对称加密 数据摘要&&数据指纹 HTTPS 的工作过程探究方案1-只使用对称加密方案2-只使用非对称加密方案3-双方都使用非对称加密方案4-非对称加密对称加密中间人攻击-针对上面的场景 CA认证理解数据签名方案5-非对…

15-模型 - 一对多 多对多

一对多&#xff1a; 1. 在多的表里定义外键 db.ForeignKey(主键) 2. 增加字段 db.relationship 建立联系 ("关联表类名","反向引用名") from ext import db# 一 class User(db.Model):id db.Column(db.Integer, primary_keyTrue, autoincrementTrue)us…

Dart PowerTCP Emulation for .NET Crack

Dart PowerTCP Emulation for .NET Crack .NET CF上的PowerTCP Emulation为手持设备提供了高级的Internet通信组件。这些功能允许同步操作&#xff0c;这样可以消耗更少的资源&#xff0c;提供更大的灵活性&#xff0c;并生成易于维护的软件。带有.NET的PowerTCP仿真包括VT52、…

gpt-3.5-turbo微调图形界面;Hugging Face完成2.35亿美元融资

&#x1f989; AI新闻 &#x1f680; 人工智能初创公司Hugging Face完成2.35亿美元融资&#xff0c;估值达到45亿美元 摘要&#xff1a;总部位于纽约的人工智能初创公司Hugging Face完成了一轮2.35亿美元的融资&#xff0c;估值达到45亿美元。本轮融资的投资者包括谷歌、亚马…

几个nlp的小任务(多选问答)

@TOC 安装库 多选问答介绍 定义参数、导入加载函数 缓存数据集 随机选择一些数据展示 进行数据预处理部分(tokenizer) 调用t

Android全面屏下,默认不会全屏显示,屏幕底部会留黑问题

前些天发现了一个蛮有意思的人工智能学习网站,8个字形容一下"通俗易懂&#xff0c;风趣幽默"&#xff0c;感觉非常有意思,忍不住分享一下给大家。 &#x1f449;点击跳转到教程 公司以前的老项目&#xff0c;便出现了这种情况&#xff0c;网上搜索了各种资料&#xf…

Web Components详解-Custom Elements

目录 引言 演变过程 概述 使用方式 创建标签 定义标签 使用标签 获取标签 异步定义标签 升级标签 完整案例 结语 相关代码 参考文章 引言 随着项目体量的增大&#xff0c;组件化和模块化的优势也愈发明显了&#xff0c;构建可重复使用、独立、可互操作的组件变得…

【Java基础】Java注解与反射

文章目录 ⭐️写在前面的话⭐️1、什么是注解&#xff1f;注解的分类常用的Java注解 2、元注解TargetRetentionDocumentedInherited 3、自定义注解Override注解的基本格式 4、什么是反射&#xff1f;什么时候需要用到反射&#xff1f;反射的应用场合 5、反射的原理6、反射机制的…

基于AVR128单片机智能传送装置

一、系统方案 1、板载可变电阻&#xff08;电位器&#xff09;R29的电压作为处理器ATmega128的模数转换模块中单端ADC0的模拟信号输入&#xff08;跳线JP13短接&#xff09;。 2、调节电位器&#xff0c;将改变AD转换接口ADC0的模拟信号输入&#xff0c;由处理器完成ADC0的A/D转…

生态经济学领域里的R语言机器学(数据的收集与清洗、综合建模评价、数据的分析与可视化、数据的空间效应、因果推断等)

近年来&#xff0c;人工智能领域已经取得突破性进展&#xff0c;对经济社会各个领域都产生了重大影响&#xff0c;结合了统计学、数据科学和计算机科学的机器学习是人工智能的主流方向之一&#xff0c;目前也在飞快的融入计量经济学研究。表面上机器学习通常使用大数据&#xf…

光谱成像系统视觉均匀校准积分球光源

数字相机的光谱灵敏度是成像传感器、光学透镜、滤光片以及相机内部图像处理过程等诸多因素的综合结果。即使是同一台相机&#xff0c;采用不同的光学镜头和不同的滤光片&#xff0c;由于光学系统的结构和光学材料的透过率不同&#xff0c;导致整个成像系统的光谱灵敏度也有所差…

计算机竞赛 基于机器视觉的二维码识别检测 - opencv 二维码 识别检测 机器视觉

文章目录 0 简介1 二维码检测2 算法实现流程3 特征提取4 特征分类5 后处理6 代码实现5 最后 0 简介 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于机器学习的二维码识别检测 - opencv 二维码 识别检测 机器视觉 该项目较为新颖&#xff0c;适合作为竞赛课…

使用锐捷RG-EG210G-E路由器实现两个IP地址冲突的局域网互通

需求背景&#xff1a; 之前写过一篇博文使用路由器实现三个不同网段局域网内的计算机相互访问&#xff0c;链接如下 https://blog.csdn.net/agang1986/article/details/131862160 当前的需求又发生了变更&#xff0c;有两个独立的局域网&#xff0c;内部的计算机个数和配置的IP…

C语言(第三十三天)

3.1.2 画图推演 3.2 举例2&#xff1a;顺序打印一个整数的每一位 输入一个整数m&#xff0c;打印这个按照顺序打印整数的每一位。 比如&#xff1a; 输入&#xff1a;1234 输出&#xff1a;1 2 3 4 输入&#xff1a;520 输出&#xff1a;5 2 0 3.2.1 分析和代码实现 这个题目&a…

方案:AI边缘计算智慧工地解决方案

一、方案背景 在工程项目管理中&#xff0c;工程施工现场涉及面广&#xff0c;多种元素交叉&#xff0c;状况较为复杂&#xff0c;如人员出入、机械运行、物料运输等。特别是传统的现场管理模式依赖于管理人员的现场巡查。当发现安全风险时&#xff0c;需要提前报告&#xff0…

javaCV实现java图片ocr提取文字效果

引入依赖&#xff1a; <dependency><groupId>org.bytedeco</groupId><artifactId>javacv-platform</artifactId><version>1.5.5</version></dependency> 引入中文语言训练数据集&#xff1a;chi_sim GitHub - tesseract-ocr…

windows下如何搭建属于自己的git服务器

前一阵子公司需要&#xff0c;领导让我给我们技术部搭建一个git服务器。以前看过教程&#xff0c;但自己没动手做过&#xff0c;开始按照网上的教程来&#xff0c;但搭建过程中发现还是不够详细&#xff0c;今天给大家一个比较详细的&#xff0c;希望对大家有帮助。 高能预警&a…

容器化微服务:用Kubernetes实现弹性部署

随着云计算的迅猛发展&#xff0c;容器化和微服务架构成为了构建现代应用的重要方式。而在这个过程中&#xff0c;Kubernetes&#xff08;常简称为K8s&#xff09;作为一个开源的容器编排平台&#xff0c;正在引领着容器化微服务的部署和管理革命。本文将深入探讨容器化微服务的…

关于 Camera 预览和录像画质不一样的问题分析

1、问题背景 基于之前安卓平台的一个项目&#xff0c;客户有反馈过一个 Camera app 预览的效果&#xff0c;和录像效果不一致的问题。 这里的预览是指打开 Camera app 后直接出图的效果&#xff1b;录像的效果则是指打开 Camera app 开启录像功能&#xff0c;录制一段视频&…