定时工作简直是每个业务零碎必不可少的性能,计算到期工夫、过期工夫等,定时触发某项工作操作。在应用单体利用时,根本应用 Spring 提供的注解即可实现定时工作,而在应用微服务集群时,这种形式就要思考增加分布式锁来避免多个微服务同时运行定时工作而导致同一个工作反复执行。
除了应用注解,当初还有一种形式,就是搭建分布式工作平台,所有的微服务注册到分布式工作平台,由分布式工作平台对立调度,这样防止了同一工作被反复执行。这里咱们抉择应用 XXL-JOB 作为分布式任务调度平台,XXL-JOB 外围设计指标是开发迅速、学习简略、轻量级、易扩大。
应用分布式任务调度平台的长处除了防止同一工作反复执行外,还有应用简略,能够手动执行、有具体的调度日志查看工作具体执行状况等长处。
XXL-JOB 官网架构设计图:
上面咱们依照步骤来介绍,如何联合咱们的微服务平台将分布式任务调度平台 XXL-JOB 集成进来,实现咱们须要的定时工作性能。
一、微服务框架整合 xxl-job-admin
1、XXL-JOB 开源网站下载源码,下载地址 https://github.com/xuxueli/xxl-job/releases , 下载下来的源码如下:
xxl-job-admin:调度核心
xxl-job-core:公共依赖
xxl-job-executor-samples:执行器 Sample 示例(抉择适合的版本执行器,可间接应用,也能够参考其并将现有我的项目革新成执行器):xxl-job-executor-sample-springboot:Springboot 版本,通过 Springboot 治理执行器,举荐这种形式;:xxl-job-executor-sample-frameless:无框架版本;
下载下来的开源包有三个目录:xxl-job-admin、xxl-job-core 和 xxl-job-executor-samples,顾名思义,xxl-job-admin 是分布式工作平台的服务端兼治理台,咱们须要部署的也是这个工程,咱们能够把整个工程集成到咱们的微服务中,对立打包部署;xxl-job-core 是公共依赖包,咱们其余须要实现定时工作的微服务须要引入这个包来实现定时工作执行器。xxl-job-executor-samples 为定时工作执行器的实例代码。
2、在根底平台 gitegg-platform 工程 gitegg-platform-bom 中引入 xxl-job-core 外围包,对立版本治理。
......
<!-- 分布式任务调度平台 XXL-JOB 外围包 -->
<xxl-job.version>2.3.1</xxl-job.version>
......
<!-- 分布式任务调度平台 XXL-JOB 外围包 -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>${xxl-job.version}</version>
</dependency>
3、将 xxl-job-admin 集成到微服务工程中,不便对立打包部署
依据咱们的微服务架构设计,gitegg-plugin 作为咱们零碎的插件工程,外面搁置咱们须要的插件服务。有些插件是必须的,而有些插件可能会用不到,此时咱们就能够依据本人的业务需要去抉择部署业务插件。
为和咱们的微服务深度集成就不是解耦的个性,咱们须要对 xxl-job-admin 的配置文件进行适当的批改:
-
首先批改 pom.xml,放弃各依赖库版本统一,批改 parent 标签,使其援用 GitEgg 工程的根底 jar 包和微服务配置注册性能,同时排除 logback,应用 log4j2 记录日志
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>gitegg-plugin</artifactId> <groupId>com.gitegg.cloud</groupId> <version>1.0.1.RELEASE</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>gitegg-job</artifactId> <name>${project.artifactId}</name> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <maven.compiler.encoding>UTF-8</maven.compiler.encoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <maven.test.skip>true</maven.test.skip> <netty-all.version>4.1.63.Final</netty-all.version> <gson.version>2.9.0</gson.version> <spring.version>5.3.20</spring.version> <spring-boot.version>2.6.7</spring-boot.version> <mybatis-spring-boot-starter.version>2.2.2</mybatis-spring-boot-starter.version> <mysql-connector-java.version>8.0.29</mysql-connector-java.version> <slf4j-api.version>1.7.36</slf4j-api.version> <junit-jupiter.version>5.8.2</junit-jupiter.version> <javax.annotation-api.version>1.3.2</javax.annotation-api.version> <groovy.version>3.0.10</groovy.version> <maven-source-plugin.version>3.2.1</maven-source-plugin.version> <maven-javadoc-plugin.version>3.4.0</maven-javadoc-plugin.version> <maven-gpg-plugin.version>3.0.1</maven-gpg-plugin.version> </properties> <dependencies> <!-- gitegg Spring Boot 自定义及扩大 --> <dependency> <groupId>com.gitegg.platform</groupId> <artifactId>gitegg-platform-boot</artifactId> </dependency> <!-- gitegg Spring Cloud 自定义及扩大 --> <dependency> <groupId>com.gitegg.platform</groupId> <artifactId>gitegg-platform-cloud</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis-spring-boot-starter.version}</version> <!-- 去除 springboot 默认的 logback 配置 --> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <!-- 去除 springboot 默认的 logback 配置 --> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </exclusion> </exclusions> </dependency> <!-- freemarker-starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> <!-- 去除 springboot 默认的 logback 配置 --> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </exclusion> </exclusions> </dependency> <!-- mail-starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> <!-- 去除 springboot 默认的 logback 配置 --> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </exclusion> </exclusions> </dependency> <!-- starter-actuator --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> <!-- 去除 springboot 默认的 logback 配置 --> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </exclusion> </exclusions> </dependency> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql-connector-java.version}</version> </dependency> <!-- 分布式任务调度平台 XXL-JOB 外围包 --> <dependency> <groupId>com.xuxueli</groupId> <artifactId>xxl-job-core</artifactId> <!-- 去除抵触的 slf4j 配置 --> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>com.google.cloud.tools</groupId> <artifactId>jib-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
- 批改 application.properties,依据咱们零碎的标准,新增 bootstrap.yml、bootstrap-dev.yml、bootstrap-prod.yml、bootstrap-test.yml 文件。将 application.properties 局部配置,移到 bootstrap.yml 配置中。因 xxl-job-admin 独自数据库,且其默认应用的是 Hikari 数据库连接池,这里咱们不打算改变,依然使其放弃原有的数据库配置,咱们将可配置的内容搁置在 Nacos 微服务配置核心上,同时在 bootstrap.yml 中增加多 yaml 文件配置(请留神,在咱们本地应用的是 yml 结尾的文件,Nacos 服务注册核心上应用的是 yaml 结尾的文件,两者是一样的,只是扩展名的不同)。
bootstrap.yml 配置:
server:
port: 8007
spring:
profiles:
active: '@spring.profiles.active@'
application:
name: '@artifactId@'
cloud:
inetutils:
ignored-interfaces: docker0
nacos:
discovery:
server-addr: ${spring.nacos.addr}
config:
server-addr: ${spring.nacos.addr}
file-extension: yaml
extension-configs:
# 必须带文件扩展名,此时 file-extension 的配置对自定义扩大配置的 Data Id 文件扩展名没有影响
- data-id: ${spring.nacos.config.prefix}.yaml
group: ${spring.nacos.config.group}
refresh: true
- data-id: ${spring.nacos.config.prefix}-xxl-job.yaml
group: ${spring.nacos.config.group}
refresh: true
### xxl-job-admin config
mvc:
servlet:
load-on-startup: 0
static-path-pattern: /static/**
resources:
static-locations: classpath:/static/
### freemarker
freemarker:
templateLoaderPath: classpath:/templates/
suffix: .ftl
charset: UTF-8
request-context-attribute: request
settings.number_format: 0.##########
### actuator
management:
server:
servlet:
context-path: /actuator
health:
mail:
enabled: false
### mybatis
mybatis:
mapper-locations: classpath:/mybatis-mapper/*Mapper.xml
Nacos 上 gitegg-cloud-config-xxl-job.yaml 配置:
server:
servlet:
context-path: /xxl-job-admin
spring:
datasource:
url: jdbc:mysql://127.0.0.1/xxl_job?useSSL=false&zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=GMT%2B8
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
### datasource-pool
type: com.zaxxer.hikari.HikariDataSource
hikari:
minimum-idle: 10
maximum-pool-size: 30
auto-commit: true
idle-timeout: 30000
pool-name: HikariCP
max-lifetime: 900000
connection-timeout: 10000
connection-test-query: SELECT 1
validation-timeout: 1000
### email
mail:
host: smtp.qq.com
port: 25
username: xxx@qq.com
from: xxx@qq.com
password: xxx
properties:
mail:
smtp:
auth: true
starttls:
enable: true
required: true
socketFactory:
class: javax.net.ssl.SSLSocketFactory
### xxl-job, access token
xxl:
job:
accessToken: default_token
### xxl-job, i18n (default is zh_CN, and you can choose "zh_CN", "zh_TC" and "en")
i18n: zh_CN
## xxl-job, triggerpool max size
triggerpool:
fast:
max: 200
slow:
max: 100
### xxl-job, log retention days
logretentiondays: 30
4、初始化 xxl-job-admin 须要的数据库脚本
初始化脚本寄存在下载的包目录的 \xxl-job-2.3.1\doc\db\tables_xxl_job.sql 中,一共须要 8 张表。咱们将 xxl-job-admin 的数据库和业务数据库离开,配置不同的数据源,在 Nacos 配置独自的 xxl-job-admin 配置文件。
- 新建 xxl_job 数据库
- 关上数据库执行建表语句
5、在 GitEgg 工程的父级 pom.xml 下增加动态文件过滤
xxl-job-admin 是 SpringMVC 我的项目,其前端页面由 ftl 文件和动态文件组成,默认状况下 maven 启用分环境读取配置时,会对 resource 目录下的 @进行替换,导致动态文件下的字体文件不能用,所以,这里须要进行和 jks 文件一样的过滤配置:
<resources>
<!-- 减少分环境读取配置 -->
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>**/*.jks</exclude>
<exclude>static/**</exclude>
</excludes>
</resource>
<!-- 解决 jks 被过滤掉的问题 -->
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<includes>
<include>**/*.jks</include>
<include>static/**</include>
</includes>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
6、在 Gateway 增加 xxl-job-admin 路由转发
xxl-job-admin 路由转发须要增加两方面内容,一个是 xxl-job-admin 注册到 Nacos 注册核心上的 gitegg-job 服务,一个是 xxl-job-admin 前端页面申请的动态文件转发。第一个是为了和咱们整体微服务保持一致,第二个是为了解决 xxl-job-admin 前端 ftl 页面在申请动态文件时,申请的是 /xxl-job-admin 根门路。新增 Gateway 路由转发配置如下:
- id: gitegg-job
uri: lb://gitegg-job
predicates:
- Path=/gitegg-job/**
filters:
- StripPrefix=1
- id: xxl-job-admin
uri: lb://gitegg-job
predicates:
- Path=/xxl-job-admin/**
filters:
- StripPrefix=0
7、减少 xxl-job-admin 拜访白名单
xxl-job-admin 有本人的权限访问控制,咱们不在网关对其进行鉴权,所以在 Nacos 配置中,减少白名单配置:
# 网关放行设置 1、whiteUrls 不须要鉴权的公共 url,白名单,配置白名单门路 2、authUrls 须要鉴权的公共 url
oauth-list:
......
whiteUrls:
......
- "/gitegg-job/**"
- "/xxl-job-admin/**"
......
8、启动 xxl-job-admin 微服务,查看是否启动胜利,默认用户名明码: admin/123456
二、测试 XXL-JOB 定时工作性能
咱们在下面的第一步中,实现了 xxl-job-admin 的整合和启动,xxl-job-admin 能够看做是分布式工作的服务注册核心和治理台,如果咱们须要实现定时工作,还须要具体实现执行器让 xxl-job-admin 调用执行。
XXL-JOB 反对多种形式的定时工作调用,能够将定时工作执行器写在业务代码中,也能够写在 xxl-job-admin 服务端:
- BEAN 模式(类模式): Bean 模式工作,反对基于类的开发方式,每个工作对应一个 Java 类。
- BEAN 模式(办法模式): Bean 模式工作,反对基于办法的开发方式,每个工作对应一个办法。
-
GLUE 模式 (Java/Shell/Python/NodeJS/PHP/PowerShell):工作以源码形式保护在调度核心,反对通过 Web IDE 在线更新,实时编译和失效,因而不须要指定 JobHandler。
1、减少 xxl-job 通用配置
新增 gitegg-platform-xxl-job 工程,减少通用配置 XxlJobConfig.java 通用配置,这样在须要应用定时工作的微服务中,只须要引入一次即可,不须要反复配置。
XxlJobConfig.java:
package com.gitegg.platform.xxl.job.config;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* xxl-job config
*
* @author xuxueli 2017-04-28
*/
@Slf4j
@Configuration
public class XxlJobConfig {@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
@Value("${xxl.job.accessToken}")
private String accessToken;
@Value("${xxl.job.executor.appname}")
private String appname;
@Value("${xxl.job.executor.address}")
private String address;
@Value("${xxl.job.executor.ip}")
private String ip;
@Value("${xxl.job.executor.port}")
private int port;
@Value("${xxl.job.executor.logpath}")
private String logPath;
@Value("${xxl.job.executor.logretentiondays}")
private int logRetentionDays;
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {log.info(">>>>>>>>>>> xxl-job config init.");
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
xxlJobSpringExecutor.setAppname(appname);
xxlJobSpringExecutor.setAddress(address);
xxlJobSpringExecutor.setIp(ip);
xxlJobSpringExecutor.setPort(port);
xxlJobSpringExecutor.setAccessToken(accessToken);
xxlJobSpringExecutor.setLogPath(logPath);
xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
return xxlJobSpringExecutor;
}
/**
* 针对多网卡、容器内部署等状况,可借助 "spring-cloud-commons" 提供的 "InetUtils" 组件灵便定制注册 IP;*
* 1、引入依赖:* <dependency>
* <groupId>org.springframework.cloud</groupId>
* <artifactId>spring-cloud-commons</artifactId>
* <version>${version}</version>
* </dependency>
*
* 2、配置文件,或者容器启动变量
* spring.cloud.inetutils.preferred-networks: 'xxx.xxx.xxx.'
*
* 3、获取 IP
* String ip_ = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
*/
}
Nacos 配置核心:
xxl:
job:
admin:
addresses: http://127.0.0.1/xxl-job-admin
accessToken: 'default_token'
executor:
appname: ${spring.application.name}
address:
ip:
port: 9999
logpath: D:\\log4j2_nacos\\xxl-job\\jobhandler
logretentiondays: 30
2、实现定时工作测试代码
咱们在 gitegg-service-system 中测试定时工作执行器,先在 pom.xml 中增加 gitegg-platform-xxl-job 依赖,而后新增 SystemJobHandler.java 测试类
SystemJobHandler.java:
package com.gitegg.service.system.jobhandler;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* 定时工作示例代码,其余更多示例请查看
* https://www.xuxueli.com/xxl-job
* @author GitEgg
*/
@Slf4j
@Component
public class SystemJobHandler {
/**
* 1、简略工作示例(Bean 模式)不带返回值
*/
@XxlJob("systemJobHandler")
public void systemJobHandler() throws Exception {XxlJobHelper.log("不带返回值:XXL-JOB, Hello World.");
for (int i = 0; i < 5; i++) {XxlJobHelper.log("beat at:" + i);
TimeUnit.SECONDS.sleep(2);
}
}
/**
* 2、简略工作示例(Bean 模式)带胜利或失败返回值
*/
@XxlJob("userJobHandler")
public ReturnT<String> userJobHandler() throws Exception {XxlJobHelper.log("带返回值:XXL-JOB, Hello World.");
for (int i = 0; i < 5; i++) {XxlJobHelper.log("beat at:" + i);
TimeUnit.SECONDS.sleep(2);
}
return ReturnT.SUCCESS;
}
}
3、配置 xxl-job-admin 新增执行器
- 新增时:
-
gitegg-service-system 服务启动后,主动注册:
4、新增 xxl-job-admin 工作
执行器能够看做是一组微服务,而工作是微服务具体执行的办法。工作新增后,默认是 STOP 状态,须要手动启动,当列表显示 RUNNING 时,示意该工作是运行状态,会依据配置的工夫执行。
5、查看执行器是否执行
在本地开发环境查看工作执行的形式有多种,间接 Debug 也能够,生产环境咱们能够查看 xxl-job 日志,在测试代码中记录的 log,在 xxl-job-admin 治理台都能够具体查看。
通过以上操作步骤,咱们将 xxl-job 和 xxl-job-admin 整合到了咱们的微服务架构中,只须要在有任务调度需要的微服务中实现执行器就能够满足咱们的需要了。
GitEgg-Cloud 是一款基于 SpringCloud 整合搭建的企业级微服务利用开发框架,开源我的项目地址:
Gitee: https://gitee.com/wmz1930/GitEgg
GitHub: https://github.com/wmz1930/GitEgg