xxl-job是一款功能强大的分布式任务调度系统。
部署方法按照官网写的说明即可:https://www.xuxueli.com/xxl-job/
1、下载release版本代码
https://github.com/xuxueli/xxl-job/releases/tag/2.1.2
修改父pom.xml中的:
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.test.skip>true</maven.test.skip>
2、执行数据库脚本
3、配置调度中心application.properties数据库连接信息,编译xxl-job-admin
修改下端口application.properties:
server.port=8081
修改logback.xml中日志路径:
<property name="log.path" value="e:/data/applogs/xxl-job/xxl-job-admin.log"/>
4、执行XxlJobAdminApplication.java的main方法
admin / 123456 登录
浏览器访问:http://127.0.0.1:8081/xxl-job-admin/
5、spring项目引入maven依赖
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.1.2</version>
</dependency>
6、配置执行器-BEAN模式(方法形式)
执行器是任务执行的单元
1)在webapp2_web模块src/main/resources下建立spring-xxl-job.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:amq="http://activemq.apache.org/schema/core"
xmlns:jms="http://www.springframework.org/schema/jms" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="fileEncoding" value="utf-8" />
<property name="locations">
<list>
<value>classpath:/env/xxl-job-executor.properties</value>
</list>
</property>
</bean>
<!-- ********************************* 基础配置 ********************************* -->
<!-- 配置01、JobHandler 扫描路径 -->
<!-- 扫描放入applicationContext.xml -->
<!-- 配置02、执行器 -->
<bean id="xxlJobSpringExecutor" class="com.xxl.job.core.executor.impl.XxlJobSpringExecutor" init-method="start" destroy-method="destroy">
<!-- 执行器注册中心地址[选填],为空则关闭自动注册 -->
<property name="adminAddresses" value="${xxl.job.admin.addresses}" />
<!-- 执行器AppName[选填],为空则关闭自动注册 -->
<property name="appName" value="${xxl.job.executor.appname}" />
<!-- 执行器IP[选填],为空则自动获取 -->
<property name="ip" value="${xxl.job.executor.ip}" />
<!-- 执行器端口号[选填],小于等于0则自动获取 -->
<property name="port" value="${xxl.job.executor.port}" />
<!-- 访问令牌[选填],非空则进行匹配校验 -->
<property name="accessToken" value="${xxl.job.accessToken}" />
<!-- 执行器日志路径[选填],为空则使用默认路径 -->
<property name="logPath" value="${xxl.job.executor.logpath}" />
<!-- 日志保存天数[选填],值大于3时生效 -->
<property name="logRetentionDays" value="${xxl.job.executor.logretentiondays}" />
</bean>
</beans>
applicationContext.xml文件添加引用
<import resource="classpath:/spring-xxl-job.xml"/>
2)在webapp2_web模块src/main/resources/env下建立xxl-job-executor.properties
### xxl-job admin address list, such as "http://address" or "http://address01,http://address02"
xxl.job.admin.addresses=http://127.0.0.1:8081/xxl-job-admin
### xxl-job executor address
xxl.job.executor.appname=xxl-job-executor-webapp2
xxl.job.executor.ip=
xxl.job.executor.port=-1
### xxl-job, access token
xxl.job.accessToken=
### xxl-job log path
xxl.job.executor.logpath=e:/data/applogs/xxl-job/jobhandler
### xxl-job log retention days
xxl.job.executor.logretentiondays=30
修改了:
xxl.job.admin.addresses
xxl.job.executor.appname
xxl.job.executor.port
xxl.job.executor.logpath
3)在webapp2_schedule模块com.study.schedule包下建立SampleXxlJob.java我们可以拿官方的例子文件
package com.study.schedule;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.xxl.job.core.log.XxlJobLogger;
import com.xxl.job.core.util.ShardingUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.TimeUnit;
/**
* XxlJob开发示例(Bean模式)
*
* 开发步骤:
* 1、在Spring Bean实例中,开发Job方法,方式格式要求为 "public ReturnT<String> execute(String param)"
* 2、为Job方法添加注解 "@XxlJob(value="自定义jobhandler名称", init = "JobHandler初始化方法", destroy = "JobHandler销毁方法")",注解value值对应的是调度中心新建任务的JobHandler属性的值。
* 3、执行日志:需要通过 "XxlJobLogger.log" 打印执行日志;
*
* @author xuxueli 2019-12-11 21:52:51
*/
@Component
public class SampleXxlJob {
private static Logger logger = LoggerFactory.getLogger(SampleXxlJob.class);
/**
* 1、简单任务示例(Bean模式)
*/
@XxlJob("demoJobHandler")
public ReturnT<String> demoJobHandler(String param) throws Exception {
XxlJobLogger.log("XXL-JOB, Hello World.");
for (int i = 0; i < 5; i++) {
XxlJobLogger.log("beat at:" + i);
TimeUnit.SECONDS.sleep(2);
}
return ReturnT.SUCCESS;
}
/**
* 2、分片广播任务
*/
@XxlJob("shardingJobHandler")
public ReturnT<String> shardingJobHandler(String param) throws Exception {
// 分片参数
ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo();
XxlJobLogger.log("分片参数:当前分片序号 = {}, 总分片数 = {}", shardingVO.getIndex(), shardingVO.getTotal());
// 业务逻辑
for (int i = 0; i < shardingVO.getTotal(); i++) {
if (i == shardingVO.getIndex()) {
XxlJobLogger.log("第 {} 片, 命中分片开始处理", i);
} else {
XxlJobLogger.log("第 {} 片, 忽略", i);
}
}
return ReturnT.SUCCESS;
}
/**
* 3、命令行任务
*/
@XxlJob("commandJobHandler")
public ReturnT<String> commandJobHandler(String param) throws Exception {
String command = param;
int exitValue = -1;
BufferedReader bufferedReader = null;
try {
// command process
Process process = Runtime.getRuntime().exec(command);
BufferedInputStream bufferedInputStream = new BufferedInputStream(process.getInputStream());
bufferedReader = new BufferedReader(new InputStreamReader(bufferedInputStream));
// command log
String line;
while ((line = bufferedReader.readLine()) != null) {
XxlJobLogger.log(line);
}
// command exit
process.waitFor();
exitValue = process.exitValue();
} catch (Exception e) {
XxlJobLogger.log(e);
} finally {
if (bufferedReader != null) {
bufferedReader.close();
}
}
if (exitValue == 0) {
return IJobHandler.SUCCESS;
} else {
return new ReturnT<String>(IJobHandler.FAIL.getCode(), "command exit value("+exitValue+") is failed");
}
}
/**
* 4、跨平台Http任务
*/
@XxlJob("httpJobHandler")
public ReturnT<String> httpJobHandler(String param) throws Exception {
// request
HttpURLConnection connection = null;
BufferedReader bufferedReader = null;
try {
// connection
URL realUrl = new URL(param);
connection = (HttpURLConnection) realUrl.openConnection();
// connection setting
connection.setRequestMethod("GET");
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setUseCaches(false);
connection.setReadTimeout(5 * 1000);
connection.setConnectTimeout(3 * 1000);
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
connection.setRequestProperty("Accept-Charset", "application/json;charset=UTF-8");
// do connection
connection.connect();
//Map<String, List<String>> map = connection.getHeaderFields();
// valid StatusCode
int statusCode = connection.getResponseCode();
if (statusCode != 200) {
throw new RuntimeException("Http Request StatusCode(" + statusCode + ") Invalid.");
}
// result
bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
StringBuilder result = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
result.append(line);
}
String responseMsg = result.toString();
XxlJobLogger.log(responseMsg);
return ReturnT.SUCCESS;
} catch (Exception e) {
XxlJobLogger.log(e);
return ReturnT.FAIL;
} finally {
try {
if (bufferedReader != null) {
bufferedReader.close();
}
if (connection != null) {
connection.disconnect();
}
} catch (Exception e2) {
XxlJobLogger.log(e2);
}
}
}
/**
* 5、生命周期任务示例:任务初始化与销毁时,支持自定义相关逻辑;
*/
@XxlJob(value = "demoJobHandler2", init = "init", destroy = "destroy")
public ReturnT<String> demoJobHandler2(String param) throws Exception {
XxlJobLogger.log("XXL-JOB, Hello World.");
return ReturnT.SUCCESS;
}
public void init(){
logger.info("init");
}
public void destroy(){
logger.info("destory");
}
}
7、随便运行一个测试类,把spring run起来,发现报错
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'xxlJobSpringExecutor' defined in class path resource [spring-xxl-job.xml]: Invocation of init method failed; nested exception is org.springframework.beans.factory.BeanIsAbstractException: Error creating bean with name 'parentMessageListener': Bean definition is abstract
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1630)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:481)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:312)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:308)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:756)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542)
at org.springframework.test.context.web.AbstractGenericWebContextLoader.loadContext(AbstractGenericWebContextLoader.java:130)
at org.springframework.test.context.web.AbstractGenericWebContextLoader.loadContext(AbstractGenericWebContextLoader.java:61)
at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.delegateLoading(AbstractDelegatingSmartContextLoader.java:281)
at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:249)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:98)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:116)
... 25 more
Caused by: org.springframework.beans.factory.BeanIsAbstractException: Error creating bean with name 'parentMessageListener': Bean definition is abstract
at org.springframework.beans.factory.support.AbstractBeanFactory.checkMergedBeanDefinition(AbstractBeanFactory.java:1332)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:285)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1082)
at com.xxl.job.core.executor.impl.XxlJobSpringExecutor.initJobHandlerMethodRepository(XxlJobSpringExecutor.java:82)
at com.xxl.job.core.executor.impl.XxlJobSpringExecutor.afterPropertiesSet(XxlJobSpringExecutor.java:36)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1688)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1626)
... 40 more
经排查,原因是在xml中定义了一个bean:parentMessageListener配置为abstract="true"
异常信息com.xxl.job.core.executor.impl.XxlJobSpringExecutor.initJobHandlerMethodRepository(XxlJobSpringExecutor.java:82)对应的代码中
// init job handler from method
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
Object bean = applicationContext.getBean(beanDefinitionName);
Method[] methods = bean.getClass().getDeclaredMethods();
for (Method method: methods) {
XxlJob xxlJob = AnnotationUtils.findAnnotation(method, XxlJob.class);
if (xxlJob != null) {
// name
String name = xxlJob.value();
if (name.trim().length() == 0) {
throw new RuntimeException("xxl-job method-jobhandler name invalid, for[" + bean.getClass() + "#"+ method.getName() +"] .");
}
if (loadJobHandler(name) != null) {
throw new RuntimeException("xxl-job jobhandler[" + name + "] naming conflicts.");
}
// execute method
if (!(method.getParameterTypes()!=null && method.getParameterTypes().length==1 && method.getParameterTypes()[0].isAssignableFrom(String.class))) {
throw new RuntimeException("xxl-job method-jobhandler param-classtype invalid, for[" + bean.getClass() + "#"+ method.getName() +"] , " +
"The correct method format like \" public ReturnT<String> execute(String param) \" .");
}
if (!method.getReturnType().isAssignableFrom(ReturnT.class)) {
throw new RuntimeException("xxl-job method-jobhandler return-classtype invalid, for[" + bean.getClass() + "#"+ method.getName() +"] , " +
"The correct method format like \" public ReturnT<String> execute(String param) \" .");
}
method.setAccessible(true);
// init and destory
Method initMethod = null;
Method destroyMethod = null;
if(xxlJob.init().trim().length() > 0) {
try {
initMethod = bean.getClass().getDeclaredMethod(xxlJob.init());
initMethod.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException("xxl-job method-jobhandler initMethod invalid, for[" + bean.getClass() + "#"+ method.getName() +"] .");
}
}
if(xxlJob.destroy().trim().length() > 0) {
try {
destroyMethod = bean.getClass().getDeclaredMethod(xxlJob.destroy());
destroyMethod.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException("xxl-job method-jobhandler destroyMethod invalid, for[" + bean.getClass() + "#"+ method.getName() +"] .");
}
}
// registry jobhandler
registJobHandler(name, new MethodJobHandler(bean, method, initMethod, destroyMethod));
}
}
}
获取所有bean的名字,然后获取实例,循环判断方法上是否有@XxlJob的注解,由于我定义有抽象类,不能实例化所以报错
已经在github上提了一个issues,https://github.com/xuxueli/xxl-job/issues/1545
等待回复,整合暂停。。。
8、自己改吧,加个try-catch
Object bean = null;
try {
bean = applicationContext.getBean(beanDefinitionName);
} catch (Exception e) {
System.out.println("error...");
continue;
}
9、在xxl-job任务调度中心添加执行器
10、再次把webapp2 run起来,自动注册成功
日志:
2020-04-02 14:14:02.712 [] [] [] [] INFO [main] com.xxl.job.core.executor.XxlJobExecutor [211]:>>>>>>>>>>> xxl-job register jobhandler success, name:demoJobHandler, jobHandler:com.xxl.job.core.handler.impl.MethodJobHandler@38018b62[class com.study.schedule.SampleXxlJob#demoJobHandler]
2020-04-02 14:14:02.713 [] [] [] [] INFO [main] com.xxl.job.core.executor.XxlJobExecutor [211]:>>>>>>>>>>> xxl-job register jobhandler success, name:httpJobHandler, jobHandler:com.xxl.job.core.handler.impl.MethodJobHandler@3533df16[class com.study.schedule.SampleXxlJob#httpJobHandler]
2020-04-02 14:14:02.713 [] [] [] [] INFO [main] com.xxl.job.core.executor.XxlJobExecutor [211]:>>>>>>>>>>> xxl-job register jobhandler success, name:demoJobHandler2, jobHandler:com.xxl.job.core.handler.impl.MethodJobHandler@15d0849[class com.study.schedule.SampleXxlJob#demoJobHandler2]
2020-04-02 14:14:02.713 [] [] [] [] INFO [main] com.xxl.job.core.executor.XxlJobExecutor [211]:>>>>>>>>>>> xxl-job register jobhandler success, name:shardingJobHandler, jobHandler:com.xxl.job.core.handler.impl.MethodJobHandler@4038cd3a[class com.study.schedule.SampleXxlJob#shardingJobHandler]
2020-04-02 14:14:02.714 [] [] [] [] INFO [main] com.xxl.job.core.executor.XxlJobExecutor [211]:>>>>>>>>>>> xxl-job register jobhandler success, name:commandJobHandler, jobHandler:com.xxl.job.core.handler.impl.MethodJobHandler@14ac77b9[class com.study.schedule.SampleXxlJob#commandJobHandler]
2020-04-02 14:14:03.362 [] [] [] [] INFO [main] com.xxl.rpc.remoting.provider.XxlRpcProviderFactory [197]:>>>>>>>>>>> xxl-rpc, provider factory add service success. serviceKey = com.xxl.job.core.biz.ExecutorBiz, serviceBean = class com.xxl.job.core.biz.impl.ExecutorBizImpl
2020-04-02 14:14:03.365 [] [] [] [] INFO [main] com.xxl.rpc.remoting.provider.XxlRpcProviderFactory [197]:>>>>>>>>>>> xxl-rpc, provider factory add service success. serviceKey = com.xxl.job.core.biz.ExecutorBiz, serviceBean = class com.xxl.job.core.biz.impl.ExecutorBizImpl
2020-04-02 14:14:03.407 [] [] [] [] INFO [Thread-10] com.xxl.rpc.remoting.net.Server [66]:>>>>>>>>>>> xxl-rpc remoting server start success, nettype = com.xxl.rpc.remoting.net.impl.netty_http.server.NettyHttpServer, port = 9999
2020-04-02 14:14:03.468 [] [] [] [] DEBUG [xxl-job, executor ExecutorRegistryThread] com.xxl.job.core.thread.ExecutorRegistryThread [51]:>>>>>>>>>>> xxl-job registry success, registryParam:RegistryParam{registryGroup='EXECUTOR', registryKey='xxl-job-executor-webapp2', registryValue='192.168.12.221:9999'}, registryResult:ReturnT [code=200, msg=null, content=null]
2020-04-02 14:14:33.502 [] [] [] [] DEBUG [xxl-job, executor ExecutorRegistryThread] com.xxl.job.core.thread.ExecutorRegistryThread [51]:>>>>>>>>>>> xxl-job registry success, registryParam:RegistryParam{registryGroup='EXECUTOR', registryKey='xxl-job-executor-webapp2', registryValue='192.168.12.221:9999'}, registryResult:ReturnT [code=200, msg=null, content=null]
2020-04-02 14:15:03.546 [] [] [] [] DEBUG [xxl-job, executor ExecutorRegistryThread] com.xxl.job.core.thread.ExecutorRegistryThread [51]:>>>>>>>>>>> xxl-job registry success, registryParam:RegistryParam{registryGroup='EXECUTOR', registryKey='xxl-job-executor-webapp2', registryValue='192.168.12.221:9999'}, registryResult:ReturnT [code=200, msg=null, content=null]
11、新建一个任务
12、查看调度日志
它的调度和执行时分开来的,调度由调度中心完成即xxl-job-admin,执行由执行器完成
可能会有由于调度间隔很短,调度了100次,但是执行器只有一个,会慢慢地执行
13、其他
发现整合了之后tomcat启动时会报一个错,暂时不知道原因:
Exception in thread "Thread-14" java.net.BindException: Address already in use: bind
at sun.nio.ch.Net.bind0(Native Method)
at sun.nio.ch.Net.bind(Net.java:433)
at sun.nio.ch.Net.bind(Net.java:425)
at sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:223)
at io.netty.channel.socket.nio.NioServerSocketChannel.doBind(NioServerSocketChannel.java:134)
at io.netty.channel.AbstractChannel$AbstractUnsafe.bind(AbstractChannel.java:550)
at io.netty.channel.DefaultChannelPipeline$HeadContext.bind(DefaultChannelPipeline.java:1334)
at io.netty.channel.AbstractChannelHandlerContext.invokeBind(AbstractChannelHandlerContext.java:504)
at io.netty.channel.AbstractChannelHandlerContext.bind(AbstractChannelHandlerContext.java:489)
at io.netty.channel.DefaultChannelPipeline.bind(DefaultChannelPipeline.java:973)
at io.netty.channel.AbstractChannel.bind(AbstractChannel.java:248)
at io.netty.bootstrap.AbstractBootstrap$2.run(AbstractBootstrap.java:348)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)
猜测是两个组件同时使用netty,会创建多个channel并且绑定到本机地址?
参考资料:
https://blog.csdn.net/weixin_39433171/article/details/80929270
http://www.heartthinkdo.com/?p=2899
注:最新代码上传至https://github.com/csj50/webapp2.git