背景:公司项目使用的是SpringCloud版本是Hoxton,Springboot版本是2.3.4,而定时任务框架定为Saturn,但是Saturn支持的Springboot的最新版本为1.5.16,不兼容目前的项目,所以对Saturn进行改造。此文使用的是Saturn内嵌的方式。
一、安装Mysql和Zookeeper
JDK : 1.8
MySQL:5.7.23
ZooKeeper: 3.4.8
二、执行SQL
2.1、创建数据库
CREATE DATABASE saturn CHARACTER SET utf8 COLLATE utf8_general_ci;
2.2、schema创建
从这里获取最新的schema.sql。如果希望获得其他版本的schema,可以在源代码的其他tag上获取。
执行schema.sql。
三、安装Console
3.1、下载
从Releases · vipshop/Saturn · GitHub 中点击最新版本的“Console Zip File”,下载得到saturn-console-{version}-exec.jar,将之放到合适的目录。
3.2、启动
java -DSATURN_CONSOLE_DB_URL=jdbc:mysql://localhost:3306/saturn -DSATURN_CONSOLE_DB_USERNAME=root -DSATURN_CONSOLE_DB_PASSWORD=123456 -jar saturn-console-3.5.1-exec.jar
注:数据库账号密码为root 123456
3.3、访问
四、配置ZK集群
五、添加域,绑定ZK集群
六、配置console绑定ZK集群
注:default是console的id,test是zk集群的id
七、Maven配置
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.darren.center</groupId>
<artifactId>saturn-springboot2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>saturn-springboot2</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<saturn.version>3.5.1</saturn.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.vip.saturn</groupId>
<artifactId>saturn-embed-spring</artifactId>
<version>${saturn.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
八、application.properties配置
saturnConsoleURI=http://localhost:9088
saturnHome=F:/saturn/saturn-executor-3.5.1
saturnAppNamespace=www.abc.com
saturnStdout=true
multiMode=true
注:执行器地址要使用反斜杠
九、配置Saturn
package com.darren.center.saturnspringboot2.configuration;
import com.vip.saturn.embed.EmbeddedSaturn;
import com.vip.saturn.job.spring.AbstractSpringSaturnApplication;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import java.lang.management.ManagementFactory;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
/**
* Author: Darren
* Date: 2021-12-18 08:50:43
* Version: 1.0
* Description:
* 因为嵌入式使用Saturn,其打包方式、运行方式、运行jvm参数都依赖于工程的主框架(比如Spring、Tomcat等),造成Saturn ClassLoader不能与业务ClassLoader分离,从而带来的日志分离、包冲突等问题,
* 而且导致Executor一键重启、自动升级等功能失效。所以,我们不建议使用嵌入式。
*/
@Component
public class SaturnConfig extends AbstractSpringSaturnApplication implements ApplicationListener {
protected final Log logger = LogFactory.getLog(this.getClass());
private EmbeddedSaturn embeddedSaturn;
/**
* 如果设置true,那么当启动或停止Executor时出现异常,将只打印Warn日志,不抛出异常,不影响Spring容器的运行;
* 如果设置false,则不仅打印日志,而且会抛出异常,影响Spring容器的启动和停止
*/
private boolean ignoreExceptions = false;
private static final String DELIMITER = "@-@";
/**
* Saturn console 地址
*/
@Value("${saturnConsoleURI}")
private String saturnConsoleURI;
/**
* Satrun执行器位置
*/
@Value("${saturnHome}")
private String saturnHome;
/**
* 命名空间
*/
@Value("${saturnAppNamespace}")
private String saturnAppNamespace;
/**
* 是否将日志输出至标准输出
*/
@Value("${saturnStdout}")
private boolean saturnStdout;
/**
* 是否开启单机多事例模式
*/
@Value("${multiMode}")
private boolean multiMode;
public SaturnConfig() {
}
@Override
public void init() {
}
@Override
public void destroy() {
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
try {
if (event instanceof ContextRefreshedEvent) {
ContextRefreshedEvent contextRefreshedEvent = (ContextRefreshedEvent)event;
this.applicationContext = contextRefreshedEvent.getApplicationContext();
if (this.embeddedSaturn == null) {
String executorName = this.getExecutorName(multiMode);
System.setProperty("VIP_SATURN_CONSOLE_URI", saturnConsoleURI);
System.setProperty("saturn.home", saturnHome);
System.setProperty("saturn.app.namespace", saturnAppNamespace);
System.setProperty("saturn.app.executorName", executorName);
// 注意,开启saturn-executor的日志输出到控制台,只用于开发环境
if (saturnStdout) {
System.setProperty("saturn.stdout", "true");
}
logger.info("---统一定时任务[UJP-CORE-EXECUTOR]启动ing----");
this.embeddedSaturn = new EmbeddedSaturn();
this.embeddedSaturn.setSaturnApplication(this);
this.embeddedSaturn.start();
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
logger.info("停止执行器中...");
try {
String s = String.format("%s/rest/v1/namespaces/%s/executors/%s", saturnConsoleURI,
saturnAppNamespace, executorName);
URL url = new URL(s);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("DELETE");
con.setConnectTimeout(3000);
con.setReadTimeout(3000);
con.getResponseCode();
con.disconnect();
} catch (Exception e) {
logger.error("停止执行器时清理执行器出错了", e);
}
}
}));
logger.info("---统一定时任务[UJP-CORE-EXECUTOR]启动成功----");
}
} else if (event instanceof ContextClosedEvent && this.embeddedSaturn != null) {
this.embeddedSaturn.stopGracefully();
this.embeddedSaturn = null;
}
} catch (Exception var3) {
logger.error("---统一定时任务[UJP-CORE-EXECUTOR]启动失败----", var3);
if (!this.ignoreExceptions) {
throw new RuntimeException(var3);
}
}
}
/**
* 获取执行器名称
* @param multiMode
* @return
*/
private final String getExecutorName(boolean multiMode) {
StringBuilder sb = new StringBuilder(this.getHostName());
if (multiMode) {
String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
sb.append(DELIMITER).append(pid);
}
return sb.toString();
}
/**
* 获取主机名
* @return
*/
private final String getHostName() {
String hn = "UNKNOWN_HOST_NAME";
try {
hn = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
logger.warn("获取主机名失败,请使用`hostnamectl`命令配置", e);
}
return hn;
}
public boolean isIgnoreExceptions() {
return this.ignoreExceptions;
}
public void setIgnoreExceptions(boolean ignoreExceptions) {
this.ignoreExceptions = ignoreExceptions;
}
public String getSaturnConsoleURI() {
return saturnConsoleURI;
}
public void setSaturnConsoleURI(String saturnConsoleURI) {
this.saturnConsoleURI = saturnConsoleURI;
}
public String getSaturnHome() {
return saturnHome;
}
public void setSaturnHome(String saturnHome) {
this.saturnHome = saturnHome;
}
public String getSaturnAppNamespace() {
return saturnAppNamespace;
}
public void setSaturnAppNamespace(String saturnAppNamespace) {
this.saturnAppNamespace = saturnAppNamespace;
}
public boolean isSaturnStdout() {
return saturnStdout;
}
public void setSaturnStdout(boolean saturnStdout) {
this.saturnStdout = saturnStdout;
}
public boolean isMultiMode() {
return multiMode;
}
public void setMultiMode(boolean multiMode) {
this.multiMode = multiMode;
}
/*
@Bean
public EmbeddedSpringSaturnApplication embeddedSpringSaturnApplication() {
EmbeddedSpringSaturnApplication embeddedSpringSaturnApplication = new EmbeddedSpringSaturnApplication();
embeddedSpringSaturnApplication.setIgnoreExceptions(false);
return embeddedSpringSaturnApplication;
}
*/
}
十、编写JOB
package com.darren.center.saturnspringboot2.job;
import com.darren.center.saturnspringboot2.service.DemoService;
import com.vip.saturn.job.AbstractSaturnJavaJob;
import com.vip.saturn.job.SaturnJobExecutionContext;
import com.vip.saturn.job.SaturnJobReturn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* Author: Darren
* Date: 2021-12-18 08:24:37
* Version: 1.0
* Description:
*/
@Component
public class DemoJob extends AbstractSaturnJavaJob {
private static final Logger log = LoggerFactory.getLogger(DemoJob.class);
@Resource
private DemoService demoService;
@Override
public SaturnJobReturn handleJavaJob(String jobName, Integer shardItem, String shardParam,
SaturnJobExecutionContext shardingContext) throws InterruptedException {
log.info("{} is running, item is {}", jobName, shardItem);
demoService.doing();
return new SaturnJobReturn();
}
}
package com.darren.center.saturnspringboot2.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
/**
* Author: Darren
* Date: 2021-12-18 08:25:06
* Version: 1.0
* Description:
*/
@Service
public class DemoService {
private static final Logger log = LoggerFactory.getLogger(DemoService.class);
public void doing() {
log.info("DemoService is doing...");
}
}
十一、在Console添加自己的JOB
11.1、填写配合的域名www.abc.com
11.2、配置JOB
11.3、启动JOB任务调度
十二、本地启动自己的JOB
十三、优化点
- 当executor下线后,自动删除;
- 支持单机多实例模式;