JobThread ------> run 方法

1.  分布式任务调度平台XXL-JOB

最新架构图 

springboot如何实现任务队列和处理 springboot 任务调度中心_spring boot

海量数据的处理:

springboot如何实现任务队列和处理 springboot 任务调度中心_tomcat_02

首先从GitHub上面将项目clone下来,如果网络问题导致速度慢也可以从Gitee上面拉取

GitHub地址:https://github.com/xuxueli/xxl-job

https://github.com/xuxueli/xxl-job

启动调度中心:

http://localhost:8080/xxl-job-admin/

默认账号密码: admin/123456

springboot如何实现任务队列和处理 springboot 任务调度中心_redis_03

① 启动任务调度中心项目

配置如下:

### 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

springboot如何实现任务队列和处理 springboot 任务调度中心_redis_04

引入依赖:

<?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;
	}

}

进入任务调度中心:

创建执行器:

springboot如何实现任务队列和处理 springboot 任务调度中心_redis_05

springboot如何实现任务队列和处理 springboot 任务调度中心_spring boot_06

创建任务1:

springboot如何实现任务队列和处理 springboot 任务调度中心_redis_07

在线 cron 表达式生成器:

在线Cron表达式生成器

springboot如何实现任务队列和处理 springboot 任务调度中心_redis_08

 启动任务执行器 项目

springboot如何实现任务队列和处理 springboot 任务调度中心_spring_09

 

springboot如何实现任务队列和处理 springboot 任务调度中心_tomcat_10

 

springboot如何实现任务队列和处理 springboot 任务调度中心_spring boot_11

 

springboot如何实现任务队列和处理 springboot 任务调度中心_spring boot_12

 启动执行器里的任务 2:

springboot如何实现任务队列和处理 springboot 任务调度中心_spring_13

查看日志发现也正常执行

表结构:

springboot如何实现任务队列和处理 springboot 任务调度中心_spring boot_14

 对应:

springboot如何实现任务队列和处理 springboot 任务调度中心_spring_15

springboot如何实现任务队列和处理 springboot 任务调度中心_spring boot_16

 对应:

springboot如何实现任务队列和处理 springboot 任务调度中心_spring boot_17

 

springboot如何实现任务队列和处理 springboot 任务调度中心_spring_18

springboot如何实现任务队列和处理 springboot 任务调度中心_spring_19

说明:

下面是最新的表结构:

springboot如何实现任务队列和处理 springboot 任务调度中心_redis_20

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>

springboot如何实现任务队列和处理 springboot 任务调度中心_tomcat_21

 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捕获 ,而不会 造成异常被吃掉的现象

解决方案:

springboot如何实现任务队列和处理 springboot 任务调度中心_tomcat_22