JobThread ------> run 方法
1. 分布式任务调度平台XXL-JOB
最新架构图
海量数据的处理:
首先从GitHub上面将项目clone下来,如果网络问题导致速度慢也可以从Gitee上面拉取
GitHub地址:https://github.com/xuxueli/xxl-job
https://github.com/xuxueli/xxl-job
启动调度中心:
http://localhost:8080/xxl-job-admin/
默认账号密码: admin/123456
① 启动任务调度中心项目
配置如下:
### web
server.port=8080
server.context-path=/xxl-job-admin
### resources
spring.mvc.static-path-pattern=/static/**
spring.resources.static-locations=classpath:/static/
### freemarker
spring.freemarker.templateLoaderPath=classpath:/templates/
spring.freemarker.suffix=.ftl
spring.freemarker.charset=UTF-8
spring.freemarker.request-context-attribute=request
spring.freemarker.settings.number_format=0.##########
### mybatis
mybatis.mapper-locations=classpath:/mybatis-mapper/*Mapper.xml
### xxl-job, datasource
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl-job?Unicode=true&characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource
spring.datasource.tomcat.max-wait=10000
spring.datasource.tomcat.max-active=30
spring.datasource.tomcat.test-on-borrow=true
spring.datasource.tomcat.validation-query=SELECT 1
spring.datasource.tomcat.validation-interval=30000
### xxl-job email
spring.mail.host=smtp.qq.com
spring.mail.port=25
spring.mail.username=xxx@qq.com
spring.mail.password=xxx
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
### xxl-job login ,登录 注册中心(调度中心) 的账号和密码
xxl.job.login.username=admin
xxl.job.login.password=123456
### xxl-job, access token
xxl.job.accessToken=
### xxl-job, i18n (default empty as chinese, "en" as english)
xxl.job.i18n=
② 准备任务执行器项目:
创建 springboot 项目:
# web port 任务执行器 端口
server.port=8081
# log config
logging.config=classpath:logback.xml
## 往注册中心(任务调度中心)注册任务
### xxl-job admin address list, such as "http://address" or "http://address01,http://address02"
xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin
### xxl-job executor address(执行器 名称, ip 和端口)
xxl.job.executor.appname=xxl-job-executor-helloworld
xxl.job.executor.ip=
# 随机指定一个端口
#xxl.job.executor.port=${random.int[9000,10000]}
xxl.job.executor.port=9999
### xxl-job, access token
xxl.job.accessToken=
### xxl-job log path 执行结果的日志
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
### xxl-job log retention days
xxl.job.executor.logretentiondays=-1
引入依赖:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-executor-samples</artifactId>
<version>2.0.2-SNAPSHOT</version>
</parent>
<artifactId>xxl-job-executor-sample-springboot</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>Example executor project for spring boot.</description>
<url>http://www.xuxueli.com/</url>
<properties>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot (依赖管理:继承一些默认的依赖,工程需要依赖的jar包的管理,申明其他dependency的时候就不需要version) -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- spring-boot-starter-web (spring-webmvc + tomcat) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- xxl-job-core 执行器需要的依赖-->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>${project.parent.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- spring-boot-maven-plugin (提供了直接运行项目的插件:如果是通过parent方式继承spring-boot-starter-parent则不用此插件) -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
读取配置内容:
package com.xxl.job.executor.core.config;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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
*/
@Configuration
public class XxlJobConfig {
private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);
@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
@Value("${xxl.job.executor.appname}")
private String appName;
@Value("${xxl.job.executor.ip}")
private String ip;
@Value("${xxl.job.executor.port}")
private int port;
@Value("${xxl.job.accessToken}")
private String accessToken;
@Value("${xxl.job.executor.logpath}")
private String logPath;
@Value("${xxl.job.executor.logretentiondays}")
private int logRetentionDays;
@Bean(initMethod = "start", destroyMethod = "destroy")
public XxlJobSpringExecutor xxlJobExecutor() {
logger.info(">>>>>>>>>>> xxl-job config init.");
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
xxlJobSpringExecutor.setAppName(appName);
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();
*/
}
任务 1:
/**
* 任务Handler示例(Bean模式)
*
* 开发步骤: 1、继承"IJobHandler":“com.xxl.job.core.handler.IJobHandler”;
* 2、注册到Spring容器:添加“@Component”注解,被Spring容器扫描为Bean实例;
* 3、注册到执行器工厂:添加“@JobHandler(value="自定义jobhandler名称")”注解,
* 注解value值对应的是调度中心新建任务的JobHandler属性的值。 4、执行日志:需要通过 "XxlJobLogger.log" 打印执行日志;
*
* @author xuxueli 2015-12-19 19:43:36
*/
@JobHandler(value = "demoJobHandler1")
@Component
public class DemoJobHandler1 extends IJobHandler {
@Override
public ReturnT<String> execute(String param) throws Exception {
XxlJobLogger.log("XXL-JOB------> demoJobHandler1------> Hello World ------>"+"param------>"+param);
return SUCCESS;
}
}
任务2:
package com.xxl.job.executor.service.jobhandler;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.handler.annotation.JobHandler;
import com.xxl.job.core.log.XxlJobLogger;
import org.springframework.stereotype.Component;
/**
* 任务Handler示例(Bean模式)
*
* 开发步骤: 1、继承"IJobHandler":“com.xxl.job.core.handler.IJobHandler”;
* 2、注册到Spring容器:添加“@Component”注解,被Spring容器扫描为Bean实例;
* 3、注册到执行器工厂:添加“@JobHandler(value="自定义jobhandler名称")”注解,
* 注解value值对应的是调度中心新建任务的JobHandler属性的值。 4、执行日志:需要通过 "XxlJobLogger.log" 打印执行日志;
*
* @author xuxueli 2015-12-19 19:43:36
*/
@JobHandler(value = "demoJobHandler2")
@Component
public class DemoJobHandler2 extends IJobHandler {
@Override
public ReturnT<String> execute(String param) throws Exception {
XxlJobLogger.log("XXL-JOB------> demoJobHandler2------> Hello World2 ------>"+"param------>"+param);
return SUCCESS;
}
}
进入任务调度中心:
创建执行器:
创建任务1:
在线 cron 表达式生成器:
启动任务执行器 项目
启动执行器里的任务 2:
查看日志发现也正常执行
表结构:
对应:
对应:
说明:
下面是最新的表结构:
demo 2:
执行器:
配置文件如下:
server.port=8099
#redis
spring.redis.host=localhost
spring.redis.port=6379
#spring.redis.password=bigdata123
spring.redis.database=0
#spring.redis.timeout=0
netdisk.picture.url.prefix=https://netdisk.longfor.uat/doc-preview/home
spring.redis.pool.maxTotal=8
spring.redis.pool.maxWaitMillis=1000
#spring.redis.pool.max-idle=8 # pool settings ...
#spring.redis.pool.min-idle=0
#spring.redis.pool.max-active=8
#spring.redis.pool.max-wait=-1
#spring.redis.sentinel.master= # name of Redis server
#spring.redis.sentinel.nodes= # comma-separated list of host:port pairs
# 数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/sql_test?serverTimezone=UTC&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
#连接池配置
#spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource
#mybatis
#entity扫描的包名
mybatis.type-aliases-package=com.springboot.demo.model
#Mapper.xml所在的位置
mybatis.mapper-locations=classpath*:/mybatis/mapper/*Mapper.xml
#mybatis.mapper-locations=classpath*:/mybatis/mapper/*Mapper.xml2entity,classpath*:/mapper/order/*.xml2entity,classpath*:/mapper/salechange/*.xml2entity
# 下面这个配置报错: Invalid bound statement (not found): com.example.demo.mapper.RegionMapper.list
#mybatis.mapper-location=classpath*:/mybatis/mapper/*Mapper.xml2entity
#开启MyBatis的二级缓存
mybatis.configuration.cache-enabled=true
# 分页插件 pagehelper
pagehelper.helperDialect=mysql
pagehelper.reasonable=true
pagehelper.supportMethodsArguments=true
pagehelper.params=count=countSql
#日志配置(debug 下 ,控制台,mybatis plugin 可以查到到sql )
logging.level.com.xiaolyuh=debug
logging.level.org.springframework.web=debug
logging.level.org.springframework.transaction=debug
logging.level.org.mybatis=debug
# mybatis plus log
mybatis.configuration.log-impl =org.apache.ibatis.logging.stdout.StdOutImpl
# 单个文件大小
spring.http.multipart.maxFileSize = 100MB
##总文件大小(允许存储文件的文件夹大小)
spring.http.multipart.maxRequestSize=1000MB
#单个文件大小
spring.servlet.multipart.maxFileSize = 100MB
#总文件大小(允许存储文件的文件夹大小)
spring.servlet.multipart.maxRequestSize=1000MB
front.cover.height = 12.00
front.cover.width = 0.40
# job
# log config ??
#logging.config=classpath:logback.xml
### xxl-job admin address list, such as "http://address" or "http://address01,http://address02"
xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin
### xxl-job, access token (没token 会注册不上,注册 不到 xxl_job_registry 表里 )
xxl.job.accessToken= 53aa0953
### xxl-job executor appname
xxl.job.executor.appname=xxl-job-executor-sample
### xxl-job executor registry-address: default use address to registry , otherwise use ip:port if address is null
xxl.job.executor.address=
### xxl-job executor server-info
xxl.job.executor.ip=
xxl.job.executor.port=9999
### xxl-job executor log-path
#xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
xxl.job.executor.logpath=E://log
### xxl-job executor log-retention-days
xxl.job.executor.logretentiondays=30
依赖文件:
<!--xxl job-->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.3.0</version>
</dependency>
java 配置类:
package com.example.demo.xxlJob.config;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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
*/
@Configuration
public class XxlJobConfig {
private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);
@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() {
logger.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();
*/
}
设置一个任务
package com.example.demo.xxlJob;
import com.example.demo.entity.Refundset;
import com.example.demo.entity.SOrder;
import com.example.demo.mapper.OrderSOrderMapper;
import com.example.demo.mapper.RefundsetMapper;
import com.example.demo.service.order.OrderSOrderService;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* XxlJob开发示例(Bean模式)
*
* 开发步骤:
* 1、任务开发:在Spring Bean实例中,开发Job方法;
* 2、注解配置:为Job方法添加注解 "@XxlJob(value="自定义jobhandler名称", init = "JobHandler初始化方法", destroy = "JobHandler销毁方法")",注解value值对应的是调度中心新建任务的JobHandler属性的值。
* 3、执行日志:需要通过 "XxlJobHelper.log" 打印执行日志;
* 4、任务结果:默认任务结果为 "成功" 状态,不需要主动设置;如有诉求,比如设置任务结果为失败,可以通过 "XxlJobHelper.handleFail/handleSuccess" 自主设置任务结果;
*
* @author xuxueli 2019-12-11 21:52:51
*/
@Component
public class SampleXxlJob {
private static Logger logger = LoggerFactory.getLogger(SampleXxlJob.class);
@Autowired
RefundsetMapper refundsetMapper;
@Autowired
OrderSOrderService orderSOrderService;
@Autowired
@Qualifier("orderSOrderMapper")
OrderSOrderMapper sOrderMapper;
/**
* 1、简单任务示例(Bean模式)
*/
@XxlJob("demoJobHandler")
// @Transactional(rollbackFor = Exception.class)
public void demoJobHandler() throws Exception {
XxlJobHelper.log("XXL-JOB, Hello World.");
for (int i = 0; i < 5; i++) {
XxlJobHelper.log("beat at:" + i);
TimeUnit.SECONDS.sleep(2);
}
// default success
}
/**
* 1、简单任务示例 2(Bean模式)
*/
@XxlJob("refundSetJobHandler")
public void refundSetJobHandler() throws Exception {
XxlJobHelper.log("XXL-JOB, Hello World.");
XxlJobHelper.log("XXL-JOB, main thread name" ,Thread.currentThread().getName());
List<Refundset> refundSetList = refundsetMapper.getRefundSetList();
// 不能用多线程
// new Thread (()->new Runnable(){
// @Override
// public void run() {
// System.out.println("hhhhh");
//
// }
//
// }).start();
//第一个任务。
refundSetList.forEach(e->{
XxlJobHelper.log(" job thread name {} , 分期id 为 {} ",Thread.currentThread().getName(),e.getXProjguid());
});
// 为什么 要加 service 层,没有service ,事务不好控制, mapper 已经入库,但是走到一半抛了异常,数据也没有回滚
LocalDateTime now = LocalDateTime.now();
SOrder sOrder = new SOrder();
sOrder.setCreatedGuid("000000000000");
sOrder.setCreatedTime(now);
sOrder.setUpdatedGuid("000000000000");
sOrder.setUpdatedTime(now);
sOrder.setDescription("嘿嘿");
sOrder.setId(1);
sOrder.setName("订单1");
// 这个method 会正常入库
int insertOrderCount = sOrderMapper.insert(sOrder);
XxlJobHelper.log(" insert s_order count {} ",insertOrderCount);
// s_order 表里插入数据 ,boy 表里插入数据,同时 这两个表的 insert 都加了事务,但是 boy的 insert 方法 有 /0 的异常,下面的method 不会正常入库
// orderSOrderService.insertOrder() 这个方法开启了事务
// spring 的处理逻辑如下
// try{
// orderSOrderService.insertOrder();
// 正常提交事务
// }catch(){
// 手动回滚事务(手滚)
// }
// 所以下面这个方法回滚的事务是 orderSOrderService.insertOrder() 这个方法里 s_order 表 和 boy 相关的事物 ,并不能控制 上面sOrderMapper.insert 里的事务
// spring 事务回滚之后 ,xxlJob catch 到这个异常,写入 异常信息
orderSOrderService.insertOrder();
}
}
xxlJob 反射调用 任务方法如下:
try{
// 反射调用方法
handler.execute();
// spring 处理事务后(回滚后,有异常 进入了下面 catch 方法)
}catch(Throwable e){
}
源码位置:
JobThread ------> run() 方法
面试题:
xxl 处理业务方法,会 try catch 目标方法,但是如果 业务方法 抛异常后,事务是怎么处理的?
答案:事务 是 spring 处理的 ,一般 要在 xxlJob 的method 方法里 抽成一个 Service 方法,service 对应的方法上加上 事务 ,这样业务方法抛异常后,spring 首先 先 回滚事务,然后 异常被 xxlJob捕获 ,而不会 造成异常被吃掉的现象
解决方案: