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

一. 简介

1. 概述

XXL-JOB是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。

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

官方文档:http://www.xuxueli.com/xxl-job/#/

2. 架构图

步骤:

① 部署:xxl-job-admin  作为注册中心

② 创建执行器(具体调度地址) 可以支持集群

③ 配置文件需要填写xxl-job注册中心地址

④ 每个具体执行job服务器需要创建一个netty连接端口号

⑤ 需要执行job的任务类,集成IJobHandler抽象类注册到job容器中

⑥ Execute方法中编写具体job任务

核心:

任务调度平台根据执行地址,转发到真实执行器。

3. 原理

首先要搭建一个xxl-job平台,在平台创建一个执行器,创建执行器的时候,netty会帮你创建一个端口号,
执行器启动的时候,会把服务器信息注册到xxl-job平台。
在xxl-job平台创建任务管理,任务管理核心参数有执行器,JobHandler(对应代码中@JobHandler的value名称),Cron表达式
首先会在xxl-job平台先去触发定时job,会获取到执行器对应的实际服务器地址,然后使用xxl-job发送请求到实际的定时job任务地址执行,使用demoJobHandler名称查找对应的jvm服务器上JobHandler容器获取到类的信息,再使用反射机制进行执行。

二. 环境搭建

1. 首先搭建xxl-job-admin平台

下载官方demo,建好数据库,导入xxl-job-admin项目,在xxl-job-admin.properties修改几个配置,如数据源,邮箱,账号等

由于不是springboot项目,需要单独部署tomcat启动,我配的是8090端口,浏览器访问:http://localhost:8090/,默认用户名密码可以在xxl-job-admin.properties修改,登录成功后出现如下页面:

接下来配置几个东西:

① 配置执行器:点击执行器管理 -- 新增执行器 -- 填写参数保存即可

(注册方式选择手动录入,机器地址为下面客户端springboot项目yml中的xxl.job.executor.port参数)

② 新建任务:任务管理 -- 新建任务 -- 填写参数保存即可

(执行器选择上面创建的job-执行器,Cron表示每三秒执行一次,JobHandler即客户端springboot项目中@JobHandler的value)

2. 搭建客户端(即实际中我们的项目)

这里以官方demo为例:xxl-job-executor-sample-springboot

配置:

server.port=8082
logging.config=classpath:logback.xml

xxl.job.admin.addresses=http://127.0.0.1:8090/ #即xxl-job-admin的地址
xxl.job.executor.appname=job-zhaobin #创建的AppName
xxl.job.executor.ip=
xxl.job.executor.port=9092 #执行器的执行地址端口号

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

编写核心定时job:

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.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;

/**
 * 任务Handler示例(Bean模式)
 * 开发步骤: 1、继承"IJobHandler":“com.xxl.job.core.handler.IJobHandler”;
 * 2、注册到Spring容器:添加“@Component”注解,被Spring容器扫描为Bean实例;
 * 3、注册到执行器工厂:添加“@JobHandler(value="自定义jobhandler名称")”注解,
 * 注解value值对应的是调度中心新建任务的JobHandler属性的值。 4、执行日志:需要通过 "XxlJobLogger.log" 打印执行日志;
 */
@JobHandler(value = "demoJobHandler")
@Component
public class DemoJobHandler extends IJobHandler {

	@Value("${xxl.job.executor.port}")
	private String executorPort;

	// @JobHandler 底层实现 value demoJobHandler 名称 对应存放类的class地址
	// com.xxl.job.executor.service.jobhandler 在使用反射机制 执行execute
	// 目的是为了反射统一执行方法
	@Override
	public ReturnT<String> execute(String param) throws Exception {
		// 任务调度执行地址
		System.out.println("####DemoJobHandler####execute()执行 executorPort:" + executorPort);
		return SUCCESS;
	}

}

此时,启动xxl-job-executor-sample-springboot,会发现控制台会每隔3秒打印一次

如想更改Cron规则,直接在xxl-job-admin的任务管理 - 编辑任务 - 更改Cron表达式即可。

拓展:任务管理有暂停和恢复的功能,必要时可以使用:

3. 搭建执行器集群

模拟集群,同时启动两个xxl-job-executor-sample-springboot,yml如下:

server.port=8081

logging.config=classpath:logback.xml

xxl.job.admin.addresses=http://127.0.0.1:8090/
### xxl-job executor address
xxl.job.executor.appname=job-zhaobin
xxl.job.executor.ip=
xxl.job.executor.port=9091

### 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
server.port=8082

logging.config=classpath:logback.xml

xxl.job.admin.addresses=http://127.0.0.1:8090/
### xxl-job executor address
xxl.job.executor.appname=job-zhaobin
xxl.job.executor.ip=
xxl.job.executor.port=9092

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

② xxl-job-admin配置

编辑执行器,新增一个机器地址9092,逗号隔开

任务管理,路由策略改为轮训:

同时启动,可以看到8081和8082轮训执行!

三. 搭建xxl-job-admin集群并整合SpringBoot

1. xxl-job-admin宕机了怎么,会导致所有的任务无法执行,怎么解决? 没错,xxl-job-admin实现集群

调度中心支持集群部署,提升调度系统容灾和可用性。

调度中心集群部署时,几点要求和建议:(拷贝的官方文档)

  • DB配置保持一致;
  • 登陆账号配置保持一致;
  • 集群机器时钟保持一致(单机集群忽视);
  • 建议:推荐通过nginx为调度中心集群做负载均衡,分配域名。调度中心访问、执行器回调配置、调用API服务等操作均通过该域名进行。

这里以Nginx为例,首先在本机host文件配置伪域名,然后配置nginx.conf如下:

    upstream  backServer{
	server 127.0.0.1:8080 weight=1;
	server 127.0.0.1:8081 weight=1;
    }
    server {
        listen       80;
        server_name  xxl-job-bin.com;
        #charset koi8-r;
        #access_log  logs/host.access.log  main;
        location / {
            proxy_pass   http://backServer;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

启动两个xxl-job-admin,此时,先启动8080,再启动8081,浏览器访问:xxl-job-bin.com,执行任务。点击回复/执行

会发现会一直走8081服务器,即后面启动的服务器(报错是因为找不到执行器,不用考虑):

当关闭8081,则会走8080端口去执行(符合上面图中一主一备的说法)

2. 编写完成属于自己的SpringBoot项目

首先需要把官方的demo打到本地仓库(尤其是xxl-job-core)

然后复制官方demo的以下文件:

    

pom如下:

<?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>org.springframework.boot</groupId>-->
        <!--<artifactId>spring-boot-starter-parent</artifactId>-->
        <!--<version>2.1.5.RELEASE</version>-->
        <!--<relativePath/> &lt;!&ndash; lookup parent from repository &ndash;&gt;-->
    <!--</parent>-->
    <groupId>com.example</groupId>
    <artifactId>springboot-xxl-job-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-xxl-job-demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <javax.servlet-api.version>3.0.1</javax.servlet-api.version>
        <jsp-api.version>2.2</jsp-api.version>
        <spring.version>4.3.14.RELEASE</spring.version>
        <jackson.version>2.9.4</jackson.version>
        <aspectjweaver.version>1.8.13</aspectjweaver.version>
        <slf4j-api.version>1.7.25</slf4j-api.version>
        <freemarker.version>2.3.23</freemarker.version>
        <junit.version>4.12</junit.version>
        <jetty-server.version>9.2.24.v20180105</jetty-server.version>
        <hessian.version>4.0.51</hessian.version>
        <httpclient.version>4.5.5</httpclient.version>
        <commons-exec.version>1.3</commons-exec.version>
        <commons-collections4.version>4.1</commons-collections4.version>
        <commons-lang3.version>3.7</commons-lang3.version>
        <commons-email.version>1.5</commons-email.version>
        <c3p0.version>0.9.5.2</c3p0.version>
        <mysql-connector-java.version>5.1.45</mysql-connector-java.version>
        <mybatis-spring.version>1.3.1</mybatis-spring.version>
        <mybatis.version>3.4.5</mybatis.version>
        <groovy-all.version>2.4.13</groovy-all.version>
        <quartz.version>2.3.0</quartz.version>
        <spring-boot.version>1.5.10.RELEASE</spring-boot.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <project.parent.version>1.9.2-SNAPSHOT</project.parent.version>
    </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>

            <!-- jetty -->
            <dependency>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-server</artifactId>
                <version>${jetty-server.version}</version>
            </dependency>
            <dependency>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-util</artifactId>
                <version>${jetty-server.version}</version>
            </dependency>
            <dependency>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-http</artifactId>
                <version>${jetty-server.version}</version>
            </dependency>
            <dependency>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-io</artifactId>
                <version>${jetty-server.version}</version>
            </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>1.9.2-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

注意官方demo中不用parent的方式引入springboot版本,而是直接在一个  <dependencyManagement>标签引入:

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

新建job:

package com.example.job;

import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.handler.annotation.JobHandler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
@JobHandler("myJob")
public class MyJob extends IJobHandler {

    @Value("${server.port}")
    private String serverPort;

    @Override
    public ReturnT<String> execute(String param) throws Exception {
        System.out.println("xxl-job传递参数param:" + param);
        return SUCCESS;
    }
}

注意:@JobHandler("myJob")的value要与任务管理的JobHandler一致

application.peoperties配置如下:

server.port=8082
        
logging.config=classpath:logback.xml
        
xxl.job.admin.addresses=http://127.0.0.1:8080/

xxl.job.executor.appname=job-zhaobin
xxl.job.executor.ip=
xxl.job.executor.port=9091
        
xxl.job.accessToken=
        
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler

xxl.job.executor.logretentiondays=-1

注意:appname和xxl执行端口要和下图一致:

此时,启动xxl-job-admin,启动springboot项目,修改参数分别为hahaha和xixixi,cron为2秒执行一次:

可以看到后台打印日志:

至此,分布式任务调度平台xxl-job正式搭建并整合完毕!

补充知识:

1. 常用的路由策略:

故障转移:会一直走第一个,当第一个挂了,会走第二个(即一主一备) 

② 分片广播:第一个和第二个会一起执行(不推荐,job会重复执行)

③ 第一个和最后一个:会一直在第一个和最后一个执行

轮训:类似nginx轮训机制

2. 如果第一次job没有执行完毕,第二次还会执行吗? 会执行

3. 任务超时时间:任务在规定时间内没有完成的情况下,就会进行重试
    阻塞处理策略:选择单机串行即可

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值