xxl-job 分佈式job調度中心接入手冊
項目是spring cloud框架,eureka註冊中心,使用許雪裏的xxl-job,打算後續將其改造爲spring boot工程,接入服務中心。以下是我對其源碼接入spring cloud/boot的使用方式,可改良之處尚多,僅給大家提供參考,方便使用。
xxl-job項目地址:
github https://github.com/xuxueli/xxl-job
document http://www.xuxueli.com/xxl-job/
對原項目的介紹此處不再細說,可點開上面的參考文檔熟悉
改動後的admin地址:https://github.com/lich1n/my-xxl-job
改動內容:
- 添加了通過業務單號查詢job的方法
- 將jobinfo操作的幾個接口直接暴露出來了,接我們系統權限驗證的我移除了。
- jobinfo表增加業務類型及單號字段
使用方式
將xxl-job-admin打包部署好,自己的業務系統接入job中心啓動即可。上面改動過的admin修改mysql連接地址後即可運行,sql腳本爲resources下的tables_xxl_job.sql
以下是業務模塊的接入過程
spring boot 項目接入步驟
1. 引入依賴
2. yml配置
3. 裝配配置類
4. 創建jobhandler執行器
5. 進入xxl-job控制檯新增執行器
6. 使用新增的執行器開始任務
7. 在業務模塊中操作job的【增、刪、改、查、暫停】
1 . 引入依賴
<!-- xxl-job-core -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>1.9.1</version>
</dependency>
2. yml配置
xxl:
job:
admin:
### xxl-job admin address list, such as "http://address" or "http://address01,http://address02"
addresses: http://localhost:8080
### xxl-job executor address
executor:
appname: mktcenter
ip:
port: 9888
### xxl-job log path
logpath: /data/applogs/xxl-job/jobhandler
### xxl-job log retention days
logretentiondays: -1
### xxl-job, access token
accessToken:
3. 裝配配置類
package com.bizvane.mktcenterserviceimpl.common.job;
import com.xxl.job.core.executor.XxlJobExecutor;
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.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "com.bizvane.mktcenterserviceimpl.service.jobhandler")
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 XxlJobExecutor xxlJobExecutor() {
logger.info(">>>>>>>>>>> xxl-job config init.");
XxlJobExecutor xxlJobExecutor = new XxlJobExecutor();
xxlJobExecutor.setAdminAddresses(adminAddresses);
xxlJobExecutor.setAppName(appName);
xxlJobExecutor.setIp(ip);
xxlJobExecutor.setPort(port);
xxlJobExecutor.setAccessToken(accessToken);
xxlJobExecutor.setLogPath(logPath);
xxlJobExecutor.setLogRetentionDays(-1);
return xxlJobExecutor;
}
}
4. 創建jobhandler執行器
package com.bizvane.mktcenterserviceimpl.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 org.springframework.stereotype.Component;
@JobHandler(value="activity")
@Component
public class ActivityJobHandler extends IJobHandler {
@Override
public ReturnT<String> execute(String param) throws Exception {
System.out.println("開始執行生日活動");
return null;
}
}
5. 進入xxl-job控制檯新增執行器
注意appname與yml中配置名字保持一致 !
6. 使用新增的執行器開始任務
JobHandler名字爲步驟4中@JobHandler註解value值
保存即可。
7. 在業務模塊中操作job的【增、刪、改、查、暫停】等
jobclient工具類,使用restTemplate進行遠程調用
package com.bizvane.utils.jobutils;
import java.util.Random;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
@Component
public class JobClient {
@Autowired
private RestTemplate restTemplate;
@Value("${xxl.job.admin.addresses}")
private String[] jobAdminUrl;
private static String add = "/jobinfo/add";
private static String update = "/jobinfo/update";
private static String remove = "/jobinfo/remove";
private static String pause = "/jobinfo/pause";
private static String resume = "/jobinfo/resume";
private static String getJobInfoByBiz = "/jobinfo/getJobInfoByBiz";
public JobClient() {
}
public ResponseEntity<String> addJob(XxlJobInfo xxlJobInfo) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> xxlJobInfoMap = MapUtil.obj2Map(xxlJobInfo);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity(xxlJobInfoMap, headers);
ResponseEntity<String> response = this.restTemplate.postForEntity(this.getLoadUrl(add), request, String.class, new Object[0]);
return response;
}
public ResponseEntity<String> updateJob(XxlJobInfo xxlJobInfo) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> xxlJobInfoMap = MapUtil.obj2Map(xxlJobInfo);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity(xxlJobInfoMap, headers);
ResponseEntity<String> response = this.restTemplate.postForEntity(this.getLoadUrl(update), request, String.class, new Object[0]);
return response;
}
public ResponseEntity<String> removeJob(Integer xxlJobInfoId) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<Integer> request = new HttpEntity(xxlJobInfoId, headers);
ResponseEntity<String> response = this.restTemplate.postForEntity(this.getLoadUrl(remove), request, String.class, new Object[0]);
return response;
}
public ResponseEntity<String> pauseJob(int xxlJobInfoId) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<Integer> request = new HttpEntity(xxlJobInfoId, headers);
ResponseEntity<String> response = this.restTemplate.postForEntity(this.getLoadUrl(pause), request, String.class, new Object[0]);
return response;
}
public ResponseEntity<String> resumeJob(int xxlJobInfoId) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<Integer> request = new HttpEntity(xxlJobInfoId, headers);
ResponseEntity<String> response = this.restTemplate.postForEntity(this.getLoadUrl(resume), request, String.class, new Object[0]);
return response;
}
public ResponseEntity<String> getJobInfoByBizJob(XxlJobInfo xxlJobInfo) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> xxlJobInfoMap = MapUtil.obj2Map(xxlJobInfo);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity(xxlJobInfoMap, headers);
ResponseEntity<String> response = this.restTemplate.postForEntity(this.getLoadUrl(getJobInfoByBiz), request, String.class, new Object[0]);
return response;
}
public String getLoadUrl(String method) {
int length = this.jobAdminUrl.length;
Random random = new Random();
int i = random.nextInt(length);
String url = this.jobAdminUrl[i] + method;
return url;
}
}
map工具類
package com.bizvane.utils.jobutils;
import java.lang.reflect.Field;
import java.util.Collections;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
public class MapUtil {
public MapUtil() {
}
public static MultiValueMap<String, String> obj2Map(Object obj) {
MultiValueMap<String, String> map = new LinkedMultiValueMap();
Field[] fields = obj.getClass().getDeclaredFields();
int i = 0;
for(int len = fields.length; i < len; ++i) {
String varName = fields[i].getName();
try {
boolean accessFlag = fields[i].isAccessible();
fields[i].setAccessible(true);
Object o = fields[i].get(obj);
if (o != null) {
map.put(varName, Collections.singletonList(o.toString()));
}
fields[i].setAccessible(accessFlag);
} catch (IllegalArgumentException var8) {
var8.printStackTrace();
} catch (IllegalAccessException var9) {
var9.printStackTrace();
}
}
return map;
}
public static MultiValueMap<String, String> obj2MapWithNull(Object obj) {
MultiValueMap<String, String> map = new LinkedMultiValueMap();
Field[] fields = obj.getClass().getDeclaredFields();
int i = 0;
for(int len = fields.length; i < len; ++i) {
String varName = fields[i].getName();
try {
boolean accessFlag = fields[i].isAccessible();
fields[i].setAccessible(true);
Object o = fields[i].get(obj);
if (o != null) {
map.put(varName, Collections.singletonList(o.toString()));
} else {
map.put(varName, (Object)null);
}
fields[i].setAccessible(accessFlag);
} catch (IllegalArgumentException var8) {
var8.printStackTrace();
} catch (IllegalAccessException var9) {
var9.printStackTrace();
}
}
return map;
}
public static MultiValueMap<String, String> obj2MapWithString(Object obj) {
MultiValueMap<String, String> map = new LinkedMultiValueMap();
Field[] fields = obj.getClass().getDeclaredFields();
int i = 0;
for(int len = fields.length; i < len; ++i) {
String varName = fields[i].getName();
try {
boolean accessFlag = fields[i].isAccessible();
fields[i].setAccessible(true);
Object o = fields[i].get(obj);
if (o != null) {
map.put(varName, Collections.singletonList(o.toString()));
} else {
map.put(varName, Collections.singletonList(""));
}
fields[i].setAccessible(accessFlag);
} catch (IllegalArgumentException var8) {
var8.printStackTrace();
} catch (IllegalAccessException var9) {
var9.printStackTrace();
}
}
return map;
}
}
添加了業務類型及code的實體類
package com.bizvane.utils.jobutils;
import java.util.Date;
public class XxlJobInfo {
private int id;
private int jobGroup;
private String jobCron;
private String jobDesc;
private Date addTime;
private Date updateTime;
private String author;
private String alarmEmail;
private String executorRouteStrategy;
private String executorHandler;
private String executorParam;
private String executorBlockStrategy;
private String executorFailStrategy;
private int executorTimeout;
private String glueType;
private String glueSource;
private String glueRemark;
private Date glueUpdatetime;
private String childJobId;
private String jobStatus;
private String appName;
private Integer bizType;
private String bizCode;
public XxlJobInfo() {
}
public int getId() {
return this.id;
}
public void setId(int id) {
this.id = id;
}
public int getJobGroup() {
return this.jobGroup;
}
public void setJobGroup(int jobGroup) {
this.jobGroup = jobGroup;
}
public String getJobCron() {
return this.jobCron;
}
public void setJobCron(String jobCron) {
this.jobCron = jobCron;
}
public String getJobDesc() {
return this.jobDesc;
}
public void setJobDesc(String jobDesc) {
this.jobDesc = jobDesc;
}
public Date getAddTime() {
return this.addTime;
}
public void setAddTime(Date addTime) {
this.addTime = addTime;
}
public Date getUpdateTime() {
return this.updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
public String getAuthor() {
return this.author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getAlarmEmail() {
return this.alarmEmail;
}
public void setAlarmEmail(String alarmEmail) {
this.alarmEmail = alarmEmail;
}
public String getExecutorRouteStrategy() {
return this.executorRouteStrategy;
}
public void setExecutorRouteStrategy(String executorRouteStrategy) {
this.executorRouteStrategy = executorRouteStrategy;
}
public String getExecutorHandler() {
return this.executorHandler;
}
public void setExecutorHandler(String executorHandler) {
this.executorHandler = executorHandler;
}
public String getExecutorParam() {
return this.executorParam;
}
public void setExecutorParam(String executorParam) {
this.executorParam = executorParam;
}
public String getExecutorBlockStrategy() {
return this.executorBlockStrategy;
}
public void setExecutorBlockStrategy(String executorBlockStrategy) {
this.executorBlockStrategy = executorBlockStrategy;
}
public String getExecutorFailStrategy() {
return this.executorFailStrategy;
}
public void setExecutorFailStrategy(String executorFailStrategy) {
this.executorFailStrategy = executorFailStrategy;
}
public int getExecutorTimeout() {
return this.executorTimeout;
}
public void setExecutorTimeout(int executorTimeout) {
this.executorTimeout = executorTimeout;
}
public String getGlueType() {
return this.glueType;
}
public void setGlueType(String glueType) {
this.glueType = glueType;
}
public String getGlueSource() {
return this.glueSource;
}
public void setGlueSource(String glueSource) {
this.glueSource = glueSource;
}
public String getGlueRemark() {
return this.glueRemark;
}
public void setGlueRemark(String glueRemark) {
this.glueRemark = glueRemark;
}
public Date getGlueUpdatetime() {
return this.glueUpdatetime;
}
public void setGlueUpdatetime(Date glueUpdatetime) {
this.glueUpdatetime = glueUpdatetime;
}
public String getChildJobId() {
return this.childJobId;
}
public void setChildJobId(String childJobId) {
this.childJobId = childJobId;
}
public String getJobStatus() {
return this.jobStatus;
}
public void setJobStatus(String jobStatus) {
this.jobStatus = jobStatus;
}
public String getAppName() {
return this.appName;
}
public void setAppName(String appName) {
this.appName = appName;
}
public Integer getBizType() {
return this.bizType;
}
public void setBizType(Integer bizType) {
this.bizType = bizType;
}
public String getBizCode() {
return this.bizCode;
}
public void setBizCode(String bizCode) {
this.bizCode = bizCode;
}
}
再封裝一個工具類使用jobclient,下面給出了一個添加job的操作
package com.bizvane.mktcenterserviceimpl.common.utils;
import com.bizvane.mktcenterservice.models.po.MktTaskPOWithBLOBs;
import com.bizvane.mktcenterservice.models.vo.ActivitySmartVO;
import com.bizvane.mktcenterservice.models.vo.ActivityVO;
import com.bizvane.mktcenterserviceimpl.common.constants.JobHandlerConstants;
import com.bizvane.mktcenterserviceimpl.common.enums.BusinessTypeEnum;
import com.bizvane.mktcenterserviceimpl.common.job.XxlJobConfig;
import com.bizvane.utils.enumutils.JobEnum;
import com.bizvane.utils.jobutils.JobClient;
import com.bizvane.utils.jobutils.XxlJobInfo;
import com.bizvane.utils.tokens.SysAccountPO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @author chen.li
* @date on 2018/7/4 9:50
* @description
*/
@Component
public class JobUtil {
@Autowired
private XxlJobConfig xxlJobConfig;
@Autowired
private JobClient jobClient;
/**
* 通用job添加方法
* @param execuDate
* @param desc
* @param param
* @param author
* @param jobHandler
* @param businessType
*/
public void addJob(Date execuDate,String desc,String param,String author,String jobHandler,int businessType){
//構建job對象
XxlJobInfo xxlJobInfo = new XxlJobInfo();
//設置appName
xxlJobInfo.setAppName(xxlJobConfig.getAppName());
//設置路由策略
xxlJobInfo.setExecutorRouteStrategy(JobEnum.EXECUTOR_ROUTE_STRATEGY_FIRST.getValue());
//設置job定時器
xxlJobInfo.setJobCron(DateUtil.getCronExpression(execuDate));
//設置運行模式
xxlJobInfo.setGlueType(JobEnum.GLUE_TYPE_BEAN.getValue());
//設置job處理器
xxlJobInfo.setExecutorHandler(jobHandler);
//設置job描述
xxlJobInfo.setJobDesc(desc);
//設置執行參數
xxlJobInfo.setExecutorParam(param);
//設置阻塞處理策略
xxlJobInfo.setExecutorBlockStrategy(JobEnum.EXECUTOR_BLOCK_SERIAL_EXECUTION.getValue());
//設置失敗處理策略
xxlJobInfo.setExecutorFailStrategy(JobEnum.EXECUTOR_FAIL_STRATEGY_NULL.getValue());
//設置負責人
xxlJobInfo.setAuthor(author);
//設置業務類型
xxlJobInfo.setBizType(businessType);
//添加job
jobClient.addJob(xxlJobInfo);
}
}
至此將job-admin啓動起來,自己的業務模塊啓動起來,就可通過業務操作對job中心的job進行更改,也可以直接在job平臺直觀操作job控制業務模塊的jobhandler的執行。由於restTemplate的loadBalance註解會產生問題,具體問題我沒有細查,因此在啓動類上需要註釋掉此註解,於是我手動在上面jobClient最下方中做了簡單的請求負載。以下是啓動類代碼
package com.bizvane.mktcenterserviceimpl;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@SpringBootApplication(scanBasePackages = "com.bizvane")
@EnableDiscoveryClient
@EnableSwagger2
@EnableFeignClients(basePackages={"com.bizvane.centerstageservice.rpc","com.bizvane.members.facade.service.api","com.bizvane.couponfacade.interfaces"})
@MapperScan("com.bizvane.mktcenterserviceimpl.mappers")
public class MktcenterApplication {
// @Value("${swagger.show}")
// private boolean swaggerShow;
// @LoadBalanced
@Bean
RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder.build();
}
public static void main(String[] args) {
SpringApplication.run(MktcenterApplication.class, args);
}
}