目錄
概述
核心架構
核心特點
應用場景
什么是任務調度
快速入門
獲取源碼
初始化調度數據庫
基本配置
數據源datasource
郵箱email(可選)
會話令牌access token
啟動調度中心
啟動執行器
依賴
yaml基本配置
XxlJobConfig類配置
定義執行任務
添加執行任務
初級階段
時間轉為Cron表達式工具類
XxlJobRemoteApiUtils工具類
引入遠程發送請求依賴
允許遠程調用
概述
????????XXL-Job 是一個輕量級、分布式任務調度平臺,由國內技術團隊開發并開源。它旨在解決分布式系統中的定時任務調度問題,提供了一整套簡單、高效且可靠的解決方案
核心架構
XXL-Job 的架構主要由以下幾部分組成:
- 調度中心(Admin):負責任務的管理、調度策略、觸發時機以及調度請求的發起。它提供了可視化的 Web 管理界面,方便用戶進行任務的增刪改查和調度監控。
-
執行器(Executor):部署在業務服務環境中,用于接收調度中心的請求并執行具體的任務邏輯
-
任務代碼:由開發者編寫的業務邏輯代碼,注冊到執行器中,由調度中心觸發執行
核心特點
-
輕量級設計:核心代碼簡潔高效,易于集成和部署
-
分布式調度:支持多機分布式部署,可水平擴展,提高系統可用性和負載能力
-
簡單易用:提供簡潔的 API 和可視化界面,便于任務的創建、管理和監控
-
功能豐富:支持多種任務類型(如定時任務、周期任務、一次性任務),并提供任務分片、失敗重試、任務依賴等功能
-
彈性擴縮容:支持動態添加或移除執行器節點,無需停止服務
-
高可用性:通過多節點部署和故障轉移機制,確保任務的不中斷執行
應用場景
XXL-Job 廣泛應用于以下場景:
-
定時任務:如數據備份、報表生成、系統維護等
-
分布式任務處理:支持任務分片并行執行,提高任務處理效率
-
彈性擴縮容:根據業務量動態調整執行器數量,應對業務波動
-
業務流程自動化:實現復雜業務流程的自動化調度
什么是任務調度
我們可以思考一下下面業務場景的解決方案:
- 某電商平臺需要每天上午10點,下午3點,晚上8點發放一批優惠券
- 某銀行系統需要在信用卡到期還款日的前三天進行短信提醒
- 某財務系統需要在每天凌晨0:10分結算前一天的財務數據,統計匯總
以上場景就是任務調度所需要解決的問題。
任務調度是為了自動完成特定任務,在約定的特定時刻去執行任務的過程。
快速入門
官網:?分布式任務調度平臺XXL-JOB
獲取源碼
源碼倉庫地址 |
https://github.com/xuxueli/xxl-job |
xxl-job: 一個分布式任務調度平臺,其核心設計目標是開發迅速、學習簡單、輕量級、易擴展。現已開放源代碼并接入多家公司線上產品線,開箱即用。 |
GitCode - 全球開發者的開源社區,開源代碼托管平臺 |
獲取源碼解壓即可
初始化調度數據庫
打開項目我們可以獲取到 調度數據庫 ,路徑為: xxl-job-master/doc/db/tables_xxl_job.sql
使用 數據庫連接工具初始化運行即可。
基本配置
數據源datasource
隨后打開使用 xxl-job-admin 模塊,這個模塊就是用于管理我們的調度。并修改我們數據相關配置:
郵箱email(可選)
email 的相關配置,就是 當我們調度執行失敗的時候,可以通過 email 進行通知。具體email 的配置,通過個人的郵箱平臺相關配置即可。
會話令牌access token
執行器 連接 調度中心 所需要的令牌。
啟動調度中心
啟動 admin 模塊
調度中心訪問地址: http://localhost:8080/xxl-job-admin
默認登錄賬號“admin/123456”,登錄后運行界面如下圖所示
啟動執行器
打開你自己的項目,并進行相關配置。
依賴
<!-- xxl-job --><dependency><groupId>com.xuxueli</groupId><artifactId>xxl-job-core</artifactId><version>2.3.1</version></dependency>
yaml基本配置
dev:xxl:job:admin:### 調度中心部署根地址 [選填]:如調度中心集群部署存在多個地址則用逗號分隔。執行器將會使用該地址進行"執行器心跳注冊"和"任務結果回調";為空則關閉自動注冊;addresses: http://localhost:8080/xxl-job-admin### 調度中心通訊TOKEN [選填]:非空時啟用;accessToken: default### 調度中心通訊超時時間[選填],單位秒;默認3s;executor:### 執行器AppName [選填]:執行器心跳注冊分組依據;為空則關閉自動注冊appname: xxl-job-executor-sample### 執行器注冊 [選填]:優先使用該配置作為注冊地址,為空時使用內嵌服務 ”IP:PORT“ 作為注冊地址。從而更靈活的支持容器類型執行器動態IP和動態映射端口問題。address:### 執行器IP [選填]:默認為空表示自動獲取IP,多網卡時可手動設置指定IP,該IP不會綁定Host僅作為通訊使用;地址信息用于 "執行器注冊" 和 "調度中心請求并觸發任務";ip: localhost### 執行器端口號 [選填]:小于等于0則自動獲取;默認端口為9999,單機部署多個執行器時,注意要配置不同執行器端口;port: 9999### 執行器運行日志文件存儲磁盤路徑 [選填] :需要對該路徑擁有讀寫權限;為空則使用默認路徑;logpath: /data/applogs/xxl-job/jobhandler### 執行器日志文件保存天數 [選填] : 過期日志自動清理, 限制值大于等于3時生效; 否則, 如-1, 關閉自動清理功能;logretentiondays: 30
XxlJobConfig類配置
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Slf4j
@Configuration
public class XxlJobConfig {@Value("${xxl.job.admin.addresses}")private String adminAddresses;@Value("${xxl.job.admin.accessToken}")private String accessToken;@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.executor.logpath}")private String logPath;@Value("${xxl.job.executor.logretentiondays}")private int logRetentionDays;@Beanpublic XxlJobSpringExecutor xxlJobExecutor() {log.info(">>>>>>>>>>> xxl-job config init.");log.info("adminAddress:{}", adminAddresses);log.info("appname:{}", appname);log.info("ip:{}", ip);log.info("port:{}", port);log.info("accessToken:{}", accessToken);log.info("logPath:{}", logPath);log.info("logRetentionDays:{}", logRetentionDays);log.info(">>>>>>>>>>> xxl-job config init finish.");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;}
}
隨后啟動我們自己的項目。
同時查看執行器管理器
可以發現遠程注冊端口節點成功。
定義執行任務
添加執行任務
import com.xxl.job.core.handler.annotation.XxlJob;
import org.springframework.stereotype.Component;import java.util.Date;@Component
public class SimpleXxlJob {@XxlJob("simpleJobHandler") // 注解內的參數為我們運行模式為 Bean 類型對應的 JobHandlerpublic void simpleJobHandler() throws Exception {System.out.println("執行定時任務,執行時間>>>>>>>>>>> xxl-job, Hello World." + new Date());}
}
嘗試執行一次。
通過我們執行一次成功后并調用對應方法,即可。可以根據我們自己需求進行啟動配置對應的方法了。
初級階段
有時候我們需要的是,用戶使用自己的前端去設置觸發的時間。并不是我們去xxl-job-admin 的管理端進行添加定時任務的。
時間轉為Cron表達式工具類
以下是我收集的所用到的工具類。可以參考一下。
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;/**** <p>* 將時間轉為Cron表達式* </p> ** @author Angindem* @since 2025-03-08**/
public class CronUtils {private static final DateTimeFormatter FORMAT = DateTimeFormatter.ofPattern("ss mm HH dd MM ? yyyy");public enum TimeCycle {YEAR, MONTH, WEEK, DAY, HOUR, MINUTE, SECOND}/*** 將LocalDateTime轉換為cron表達式的字符串。* @param dateTime 要轉換的時間字符串* @param format 要轉換的時間格式* @return cron表達式*/public static String toCronExpression(String dateTime, String format) {LocalDateTime localDate = LocalDateTime.parse(dateTime, DateTimeFormatter.ofPattern(format));String date = localDate.format(FORMAT);return date;}/*** 將LocalDateTime轉換為cron表達式的字符串。* @param dateTime 要轉換的LocalDateTime* @return cron表達式*/public static String toCronExpression(LocalDateTime dateTime) {String date = dateTime.format(FORMAT);return date;}/*** 將多個 LocalDateTime 對象轉換為一個 cron 表達式字符串* @param times LocalDateTime 對象列表* @return cron 表達式字符串*/public static String convertToCron(List<LocalDateTime> times) {// 提取秒、分、時、日、月、周幾int second = times.get(0).getSecond();int minute = times.get(0).getMinute();List<Integer> hours = new ArrayList<>();List<Integer> daysOfMonth = new ArrayList<>();List<Integer> months = new ArrayList<>();List<Integer> daysOfWeek = new ArrayList<>();for (LocalDateTime time : times) {hours.add(time.getHour());daysOfMonth.add(time.getDayOfMonth());months.add(time.getMonthValue());daysOfWeek.add(time.getDayOfWeek().getValue());}// 構造Cron表達式StringBuilder cron = new StringBuilder();cron.append(second).append(" ");cron.append(minute).append(" ");cron.append(String.join(",", hours.stream().map(Object::toString).collect(Collectors.toList()))).append(" ");cron.append(String.join(",", daysOfMonth.stream().map(Object::toString).collect(Collectors.toList()))).append(" ");cron.append(String.join(",", months.stream().map(Object::toString).collect(Collectors.toList()))).append(" ");cron.append(String.join(",", daysOfWeek.stream().map(Object::toString).collect(Collectors.toList())));return cron.toString();}/*** 將指定的 LocalDateTime 對象轉換為 指定周期的 cron 表達式字符串* @param dateTime LocalDateTime 對象* @param timeCycle 時間周期枚舉值* @return cron 表達式字符串*/public static String toCronExpression(LocalDateTime dateTime, TimeCycle timeCycle) {String cron = null;switch (timeCycle) {case YEAR:cron = String.format("%d %d %d %d %d ? *", dateTime.getSecond(),dateTime.getMinute(), dateTime.getHour(), dateTime.getDayOfMonth(),dateTime.getMonthValue());break;case MONTH:cron = String.format("%d %d %d %d * ? *", dateTime.getSecond(),dateTime.getMinute(), dateTime.getHour(), dateTime.getDayOfMonth());break;case WEEK:cron = String.format("%d %d %d ? * %d *", dateTime.getSecond(),dateTime.getMinute(), dateTime.getHour(), dateTime.getDayOfWeek().getValue() % 7);break;case DAY:cron = String.format("%d %d %d * * ? *", dateTime.getSecond(),dateTime.getMinute(), dateTime.getHour());break;case HOUR:cron = String.format("%d %d * * * ? *", dateTime.getSecond(),dateTime.getMinute());break;case MINUTE:cron = String.format("%d * * * * ? *", dateTime.getSecond());break;case SECOND:cron = "0/1 * * * * ? *";break;default:throw new IllegalArgumentException("Unknown time cycle: " + timeCycle);}return cron;}
}
XxlJobRemoteApiUtils工具類
轉完 Cron 表達式后,我們可以通過遠程調用 xxl-job-admin 的對應接口進行操作添加。
引入遠程發送請求依賴
<!--httpclient的坐標用于在java中發起請求--><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.13</version></dependency><!--使用fastjson解析json數據 --><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.42</version></dependency>
以下是我收集并使用的工具類,可以做一下參考。
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pea.mic.domain.po.XxlJobInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
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.web.client.RestTemplate;import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;@Slf4j
@Component
public class XxlJobRemoteApiUtils {private static String adminAddresses;private static String appname;private static String accessToken;private final static RestTemplate restTemplate = new RestTemplate();private static final String ADD_URL = "/jobinfo/add";private static final String UPDATE_URL = "/jobinfo/update";private static final String REMOVE_URL = "/jobinfo/remove";private static final String PAUSE_URL = "/jobinfo/pause";private static final String START_URL = "/jobinfo/start";@Autowiredpublic void init(Environment env) {adminAddresses = env.getProperty("xxl.job.admin.addresses");appname = env.getProperty("xxl.job.executor.appname");accessToken = env.getProperty("xxl.job.admin.accessToken");log.info("xxl.job.admin.addresses:{}", adminAddresses);log.info("xxl.job.executor.appname:{}", appname);log.info("xxl.job.accessToken:{}", accessToken);}public static Map getJobInfoByLocalDateTime(LocalDateTime times,String author,String JobDesc,String taskHandler){String cron = CronUtils.toCronExpression(times);XxlJobInfo jobInfo = new XxlJobInfo();jobInfo.setJobGroup(2);jobInfo.setJobDesc(JobDesc);jobInfo.setAuthor(author);jobInfo.setScheduleType("CRON");jobInfo.setMisfireStrategy("DO_NOTHING");//執行時間jobInfo.setScheduleConf(cron);jobInfo.setGlueType("BEAN");jobInfo.setExecutorHandler(taskHandler);jobInfo.setExecutorRouteStrategy("FIRST");jobInfo.setExecutorBlockStrategy("SERIAL_EXECUTION");jobInfo.setExecutorTimeout(0);jobInfo.setExecutorFailRetryCount(0);jobInfo.setGlueType("BEAN");jobInfo.setGlueRemark("GLUE代碼初始化");jobInfo.setTriggerStatus(0);jobInfo.setTriggerLastTime(0);jobInfo.setTriggerNextTime(0);ObjectMapper objectMapper = new ObjectMapper();Map map = objectMapper.convertValue(jobInfo, Map.class);return map;}public static String add(Map param){return doPost(adminAddresses + ADD_URL, param);}public static String update(String id, String cron){Map param = new HashMap<>();param.put("id", id);param.put("jobCron", cron);return doPost(adminAddresses + UPDATE_URL, param);}public static String remove(String id){Map param = new HashMap<>();param.put("id", id);return doGet(adminAddresses + REMOVE_URL, param);}public static String pause(String id){Map param = new HashMap<>();param.put("id", id);return doGet(adminAddresses + PAUSE_URL, param);}public static String start(String id){Map param = new HashMap<>();param.put("id", id);return doGet(adminAddresses + START_URL, param);}public static String doPost(String url, Map fromData){HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);headers.add("accessToken", accessToken);HttpEntity<Map> entity = new HttpEntity<>(fromData ,headers);log.info(entity.toString());ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(url, entity, String.class);return stringResponseEntity.getBody().toString();}public static String doGet(String url, Map<String, String> params) {// 創建可關閉的HttpClient,使用try-with-resources確保資源自動關閉try (CloseableHttpClient httpClient = HttpClients.createDefault()) {// 使用URIBuilder來構建帶參數的URLURIBuilder uriBuilder = new URIBuilder(url);// 將Map中的參數添加到URL中if (params != null) {for (Map.Entry<String, String> entry : params.entrySet()) {uriBuilder.setParameter(entry.getKey(), entry.getValue());}}// 創建GET請求對象HttpGet httpGet = new HttpGet(uriBuilder.build());httpGet.setHeader("accessToken", accessToken);// 設置請求配置(超時等)
// httpGet.setConfig(buildRequestConfig());// 執行請求并獲取響應try (CloseableHttpResponse response = httpClient.execute(httpGet)) {// 判斷響應狀態碼是否為200(成功)if (response.getStatusLine().getStatusCode() == 200) {// 獲取響應內容并轉換為字符串String result = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);log.info("GET Response body: {}", result);return result;}}} catch (Exception e) {log.error("發送GET請求出錯: ", e);}return null;}
}
允許遠程調用
通過二次開發xxl-job-admin,允許遠程調用,修改方法如下:
通過發送請求并,走的流程,可以直到調用接口的時候是通過請求參數中的XXL_JOB_LOGIN_IDENTITY 進行校驗,我們可以通過當我們發送的請求參數,直接獲取即可cookieToken,即可。
PS:由于博主已經實驗成功過了,具體方法,大家可以參考參考即可。博主就不走結果啦。
----------------------------持續更新中----------------------------