前言
xxl-job是一個分布式任務調度平台,其核心設計目標是開發迅速、學習簡單、輕量級、易擴展、開箱即用。我部門大部分定時任務調度都是基於xxl-job,諸如報表統計、定時數據同步等。
今天的素材來源於某天產品經理想在定時同步報表數據的基礎上,再增加一個手動觸發報表數據同步的功能。即在報表頁面上新增一個手動同步的按鈕,觸發該按鈕就可以執行報表數據同步
需求分析
1、保留定時同步功能,同時新增手動同步
2、手動同步的數據產生的效果要和定時數據同步的產生效果一樣
解決思路
1、方案一、新建一個手動調用的controller,controller觸發數據同步邏輯service
其實就是把寫在xxl-job執行器里面的同步邏輯,再放到controller執行一遍
2、方案二、新建一個手動調用的controller,在controller里面直接觸發xxl-job執行器
解決方案分析
在原先的定時器場景,我們為了避免定時器里面的同步邏輯還沒完成,下次定時器就觸發導致數據同步不准確,我們在執行器里面做一些手段進行規避,比如設置同步完成標志位等。
如果基於方案一,方案看似可行,其實存在潛在的坑點。即定時器執行的時候,手動剛好觸發執行,或者反過來,手動觸發的時候,定時器也執行了。這樣就會導致數據同步執行多次,導致數據不准確。
后面我們調研了xxl-job,看到了xxl-job有提供restful風格觸發執行器的功能,這個功能簡直就是為我們量身定做,當手動調用的時候,觸發執行器,因為執行的是執行器里面的調用邏輯,因此就會觸發我們為避免數據同步不准確所采取的手段
如何通過restful風格手動觸發xxl-job執行器執行
其具體介紹可以查看官網,其鏈接如下
https://www.xuxueli.com/xxl-job/#6.2 執行器 RESTful API
本例的核心代碼塊
@RestController
@RequestMapping(value = "xxl-job")
@Api(tags = "xxl-job restful調度")
@Profile("job")
@Slf4j
public class XxlJobController {
@Autowired
private XxljobClientHelper xxljobClientHelper;
@ApiOperation(value = "手動觸發任務")
@GetMapping("/run")
public AjaxResult execute(){
String adminClientAddressUrl = xxljobClientHelper.getAdminClientAddressUrl();
String accessToken = xxljobClientHelper.getAccessToken();
log.info("adminClientAddressUrl:{},accessToken:{}", adminClientAddressUrl,accessToken);
ExecutorBiz executorBiz = new ExecutorBizClient(adminClientAddressUrl, accessToken);
ReturnT<String> retval = executorBiz.run(getTriggerParam());
log.info("retval:{}", JSON.toJSONString(retval));
// 200 表示正常、其他失敗
if(retval.getCode() == 200){
return AjaxResult.success();
}
return AjaxResult.error(retval.getMsg(),retval.getCode());
}
private TriggerParam getTriggerParam(){
TriggerParam triggerParam = new TriggerParam();
// 任務ID
// triggerParam.setJobId(15);
// 任務標識
triggerParam.setExecutorHandler("demoJobHandler");
// 任務參數
triggerParam.setExecutorParams("手動觸發任務");
// 任務阻塞策略,可選值參考 com.xxl.job.core.enums.ExecutorBlockStrategyEnum
triggerParam.setExecutorBlockStrategy(ExecutorBlockStrategyEnum.COVER_EARLY.name());
// 任務模式,可選值參考 com.xxl.job.core.glue.GlueTypeEnum
triggerParam.setGlueType(GlueTypeEnum.BEAN.name());
// GLUE腳本代碼
triggerParam.setGlueSource(null);
// GLUE腳本更新時間,用於判定腳本是否變更以及是否需要刷新
triggerParam.setGlueUpdatetime(System.currentTimeMillis());
// 本次調度日志ID
triggerParam.setLogId(triggerParam.getJobId());
// 本次調度日志時間
triggerParam.setLogDateTime(System.currentTimeMillis());
return triggerParam;
}
}
注: 代碼中的demoJobHandler,就是執行器里面的調度方法。形如下
/**
* 1、簡單任務示例(Bean模式)
*/
@XxlJob("demoJobHandler")
public ReturnT<String> demoJobHandler(String param) throws Exception {
XxlJobLogger.log("XXL-JOB, Hello World.");
System.out.println("======================param:"+param+"================================隨機數:"+new Random().nextInt(1000));
return ReturnT.SUCCESS;
}
總結
如果選用方案一,也不是不行,就還得做一些改造,比如增加全局標志位,而且在設置標志位的時候,還要考慮並發場景下,可能出現的問題。因此還不如直接采用方案二。方案的選擇一定得要基於業務場景進行考量,不基於業務場景,談技術方案,很容易采坑
demo鏈接
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-xxl-job-executor