度中心到執(zhí)行器的完整實戰(zhàn)指南)
1. 為什么用 Docker 部署 XXL-JOB先搞清楚它到底解決什么問題先聊點實在的。很多團隊的業(yè)務(wù)系統(tǒng)里都有定時任務(wù)比如每天早上給用戶推送報表、凌晨清理過期數(shù)據(jù)、整點拉取對賬單。早期大家習(xí)慣直接在應(yīng)用服務(wù)里寫個Scheduled注解或者扔到服務(wù)器 crontab 里任務(wù)少的時候確實夠用。但一旦任務(wù)量上來問題就冒出來了同一套代碼在多臺機器上部署定時任務(wù)被重復(fù)執(zhí)行任務(wù)跑掛了沒有告警業(yè)務(wù)方第二天才發(fā)現(xiàn)想統(tǒng)計一下今天所有任務(wù)的成功率只能去日志里翻。這些都是典型的分布式任務(wù)調(diào)度痛點。XXL-JOB 這個項目說白了就是把你手里這些分散的定時任務(wù)集中到一個平臺上統(tǒng)一管起來。它由兩個角色組成調(diào)度中心xxl-job-admin和_執(zhí)行器_xxl-job-executor。調(diào)度中心負責(zé)任務(wù)的注冊、觸發(fā)、調(diào)度、日志管理執(zhí)行器負責(zé)真正跑業(yè)務(wù)邏輯。這種調(diào)度和執(zhí)行分離的設(shè)計天然就支持分布式——你有多臺應(yīng)用服務(wù)器就在每臺上都掛一個執(zhí)行器調(diào)度中心通過注冊中心發(fā)現(xiàn)它們?nèi)缓蟀绰酚刹呗园讶蝿?wù)分發(fā)下去。那為什么非要用 Docker 部署直接下個 jar 包跑不行嗎當(dāng)然行但我不建議你在生產(chǎn)環(huán)境直接裸跑。XXL-JOB 調(diào)度中心本身依賴 MySQL它自己要有個數(shù)據(jù)庫存任務(wù)配置和執(zhí)行記錄。如果你手裝要配 Java 環(huán)境、配 MySQL 連接、寫 systemd 服務(wù)腳本升級版本還得手動替換 jar 包。用 Docker 之后調(diào)度中心連同它依賴的 MySQL一次編排搞定三分鐘就能從零拉起一個能用的環(huán)境。后續(xù)要換版本改個鏡像標簽重新docker compose up -d就行。有一點要提前說清楚XXL-JOB 的調(diào)度中心是 Java 寫的它打包好的鏡像大多基于官方源碼構(gòu)建所以如果你們公司有內(nèi)網(wǎng)安全要求不能直接拉外網(wǎng)鏡像可以考慮自己構(gòu)建鏡像推到內(nèi)網(wǎng)倉庫。這個流程我在后面的實操部分也會帶一下。另外提醒一句Docker 部署調(diào)度中心很簡單但是執(zhí)行器接入才是實際使用中花時間最多的地方。你可以純手工把執(zhí)行器 jar 包丟到已有服務(wù)里也可以用官方提供的xxl-job-executor-sample-springboot快速生成一個執(zhí)行器樣例。我后面會分別講這兩種方式你根據(jù)自己的實際情況選。2. 部署前奏鏡像選型、版本選擇和數(shù)據(jù)庫準備2.1 鏡像到底選哪個別用 latest最好自己構(gòu)建項目地址里有官方鏡像常見的有兩個xuxueli/xxl-job-admin這是調(diào)度中心的官方鏡像。xuxueli/xxl-job-executor-sample-springboot官方提供的執(zhí)行器示例鏡像。實際操作中我強烈建議不要直接用 latest 標簽。最新版可能引入一些調(diào)整而且后續(xù)升級時你也不知道改動有多大。比如 XXL-JOB 2.4.1 的鏡像在部分環(huán)境下就有性能問題升級前要對照官方 release notes。正確的做法是選定一個穩(wěn)定版本比如 2.3.1、2.4.0。確認版本后可以把這個版本對應(yīng)的調(diào)度中心鏡像 tag 固定下來。如果你的環(huán)境不能直接訪問鏡像倉庫那就自己構(gòu)建。構(gòu)建方式很簡單去 GitHub 把xxl-job源碼 clone 下來切到你想要的 tag然后構(gòu)建調(diào)度中心模塊的鏡像。官方源碼里xxl-job-admin目錄下是有 Dockerfile 的自己構(gòu)建的好處是順便可以把數(shù)據(jù)庫初始化腳本打進去或者替換一些自定義配置。2.2 數(shù)據(jù)庫準備MySQL 和 PostgreSQL 我分別講一下XXL-JOB 官方默認支持 MySQL但社區(qū)里也有人把數(shù)據(jù)層適配到 PostgreSQL。如果你團隊里 PostgreSQL 比較多不想為這個項目單獨起一個 MySQL 實例那下面這個適配過程就值得看。先說 MySQL 方案這個最穩(wěn)。直接用 docker compose 起一個 MySQL 5.7 或 8.0 容器然后執(zhí)行官方提供的tables_xxl_job.sql建表腳本就行。官方對 MySQL 版本要求不高5.7 和 8.0 實測都能跑。需要注意字符集建議指定utf8mb4避免以后任務(wù)描述里出現(xiàn) emoji 導(dǎo)致亂碼。再聊 PostgreSQL 適配。XXL-JOB 使用的 MyBatis 框架SQL 多集中在XxlJobLogDao、XxlJobInfoDao等 XML 文件中。要適配 PostgreSQL核心是改兩點一是 SQL 方言比如 MySQL 的limit用法、replace into、date_add等函數(shù)在 PostgreSQL 里都需要換成對應(yīng)寫法二是數(shù)據(jù)類型比如datetime換timestampTINYINT用SMALLINT。社區(qū)有人提過 MR直接把xxl-job-admin里的 mapper XML 替換成 PostgreSQL 版本再改一下application.properties里的數(shù)據(jù)源配置是可以跑通的。不過我不太建議在核心生產(chǎn)環(huán)境用這種非官方適配因為后續(xù) XXL-JOB 升級時官方 MySQL 版本的 SQL 變更需要你自己同步到 PG 方言維護成本會持續(xù)存在。如果只是測試或者內(nèi)部工具那無所謂。如果不想改源碼還有個偷懶的辦法把 XXL-JOB 的調(diào)度數(shù)據(jù)放到 MySQL業(yè)務(wù)數(shù)據(jù)留在 PostgreSQL。反正調(diào)度中心不碰你的業(yè)務(wù)表數(shù)據(jù)跨庫沒問題只是部署上多維護一個 MySQL 而已。我個人更傾向這種方案因為官方支持路徑最穩(wěn)。2.3 Docker 環(huán)境檢查與常見啟動失敗部署前先確認你本機 Docker 能正常用。Windows 用戶經(jīng)常遇到的問題就是 Docker Desktop 啟動失敗提示virtualization support not detected。這個錯通常是 BIOS 里虛擬化沒開或者 Windows 的 Hyper-V 功能沒啟用。排查路徑很簡單打開任務(wù)管理器 - 性能 - CPU看右下角虛擬化是否顯示已啟用。如果是已禁用重啟進 BIOS找到 Intel Virtualization Technology 或 AMD SVM Mode開啟后保存退出。如果虛擬化已經(jīng)開啟去啟用或關(guān)閉 Windows 功能里確認 Hyper-V 和適用于 Linux 的 Windows 子系統(tǒng)都勾上了重啟后再試。Docker Desktop 啟動時還會依賴 Windows 的虛擬機監(jiān)控程序有時候第三方殺毒軟件會攔截把它加入白名單再試。Linux 服務(wù)器上部署就少這類問題只要內(nèi)核版本和 Docker 版本不沖突就行。Docker 20.10 以上一般都能正常跑。部署時候還會遇到端口占用。XXL-JOB 調(diào)度中心默認端口是 8080如果本機 8080 被其他服務(wù)占了記得在 compose 文件里改映射。我這邊實際部署用的 docker-compose 配置如下你參考這個基礎(chǔ)后續(xù)可以按需改參數(shù)version: 3.8 services: mysql: image: mysql:5.7 container_name: xxl-job-mysql environment: MYSQL_ROOT_PASSWORD: root123456 MYSQL_DATABASE: xxl_job MYSQL_USER: xxl MYSQL_PASSWORD: xxl123456 ports: - 3306:3306 volumes: - ./mysql-data:/var/lib/mysql - ./sql/tables_xxl_job.sql:/docker-entrypoint-initdb.d/tables_xxl_job.sql command: --default-authentication-pluginmysql_native_password --character-set-serverutf8mb4 --collation-serverutf8mb4_unicode_ci restart: always xxl-job-admin: image: xuxueli/xxl-job-admin:2.4.0 container_name: xxl-job-admin environment: PARAMS: --spring.datasource.urljdbc:mysql://mysql:3306/xxl_job?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue --spring.datasource.usernamexxl --spring.datasource.passwordxxl123456 ports: - 8080:8080 volumes: - ./logs:/data/applogs depends_on: - mysql restart: always注意PARAMS環(huán)境變量是官方鏡像預(yù)留的啟動參數(shù)入口。調(diào)度中心里的application.properties默認端口是 8080容器內(nèi)的應(yīng)用端口不用改只改宿主機映射端口就行。2.4 調(diào)度中心賬號初始化新部署的調(diào)度中心默認會創(chuàng)建一個管理員賬戶登錄后第一件事就是改密碼。官方賬號我提醒一下部署完別直接用默認密碼裸奔尤其是云服務(wù)器上改掉再讓團隊成員使用。登錄之后進系統(tǒng)管理 - 用戶管理新建賬號給不同團隊用別讓所有人共用 admin。3. 實操從零到一用 Docker Compose 拉起調(diào)度中心3.1 一次性初始化數(shù)據(jù)庫表數(shù)據(jù)庫表結(jié)構(gòu)在官方源碼的doc/db/tables_xxl_job.sql。如果你用的是 docker-compose 里的 MySQL 容器初始化方式把 SQL 文件掛載到/docker-entrypoint-initdb.d/目錄下MySQL 容器首次啟動時會自動執(zhí)行。這個方法適合干凈環(huán)境。如果你的 MySQL 是自己已有的比如公司已經(jīng)有個公共數(shù)據(jù)庫實例那就可以手動導(dǎo)入mysql -h127.0.0.1 -uroot -p tables_xxl_job.sql建表成功后會生成 8 張表主要包括xxl_job_info任務(wù)信息、xxl_job_log調(diào)度日志、xxl_job_registry執(zhí)行器注冊表、xxl_job_group執(zhí)行器分組等。我見過有人只導(dǎo)入了一半表就啟動調(diào)度中心結(jié)果頁面能開但一點任務(wù)管理就報錯。這里提醒一下導(dǎo)入過程不要中斷最后確認一下表數(shù)量。如果xxl_job_user表里沒有初始數(shù)據(jù)登錄頁面會報用戶名或密碼錯誤初始化腳本里默認會插入 admin 賬號密碼是 123456別改掉腳本里的初始數(shù)據(jù)否則首次登錄會卡住。3.2 啟動調(diào)度中心并驗證在 compose 文件所在目錄執(zhí)行docker compose up -d啟動后等幾秒看兩個容器狀態(tài)docker compose ps如果xxl-job-admin一直重啟多半是數(shù)據(jù)庫連不上。先看日志docker logs xxl-job-admin --tail 50看到Started XxlJobAdminApplication基本就成功。瀏覽器訪問http://localhost:8080/xxl-job-admin用 admin 賬號登錄進去首頁能看到任務(wù)調(diào)度數(shù)量、執(zhí)行器數(shù)量這些粗略信息說明調(diào)度中心已經(jīng)是活的了。實操心得我這里用docker compose而不是docker run直接跑是因為調(diào)度中心本身依賴數(shù)據(jù)庫用 compose 能把 MySQL 和 admin 一次編排起來別人接手也方便。depends_on只能保證容器啟動順序不能保證 MySQL 初始化完成所以首次啟動時 admin 容器可能會因為連不上庫而退出幾次后面 MySQL 就緒后 compose 的restart: always會自動拉起它不用慌。3.3 關(guān)于容器的時區(qū)問題很多人部署完發(fā)現(xiàn)任務(wù)日志時間對不上比實際時間晚了 8 小時。這是因為官方的 admin 鏡像默認時區(qū)有可能是 UTC。解決方式是在 compose 里給 admin 容器設(shè)置時區(qū)環(huán)境變量environment: - TZAsia/Shanghai同時連接數(shù)據(jù)庫的 URL 里要帶serverTimezoneAsia/Shanghai這個參數(shù)在前面給的PARAMS里已經(jīng)寫上了。兩處都設(shè)置好日志時間和任務(wù)觸發(fā)時間才會和本地一致。4. 執(zhí)行器接入讓業(yè)務(wù)服務(wù)真正跑起來4.1 手工接入執(zhí)行器依賴調(diào)度中心準備好了業(yè)務(wù)系統(tǒng)想變成執(zhí)行器最快的方式是引入官方 starter 依賴。以 Maven 項目為例在pom.xml里加上dependency groupIdcom.xuxueli/groupId artifactIdxxl-job-core/artifactId version2.4.0/version /dependency然后寫一個 XxlJobConfig 配置類注冊執(zhí)行器 BeanConfiguration public class XxlJobConfig { Value(${xxl.job.admin.addresses}) private String adminAddresses; Value(${xxl.job.accessToken}) private String accessToken; Value(${xxl.job.executor.appname}) private String appname; Value(${xxl.job.executor.port}) private int port; Bean public XxlJobSpringExecutor xxlJobExecutor() { XxlJobSpringExecutor executor new XxlJobSpringExecutor(); executor.setAdminAddresses(adminAddresses); executor.setAccessToken(accessToken); executor.setAppname(appname); executor.setAddress(http://localhost: port); executor.setPort(port); return executor; } }配置文件里加上xxl.job.admin.addresseshttp://localhost:8080/xxl-job-admin xxl.job.accessTokendefault_token xxl.job.executor.appnamemy-app-executor xxl.job.executor.port9999注意accessToken要跟調(diào)度中心里配置的一致否則執(zhí)行器注冊不上執(zhí)行任務(wù)會報xxl-job access token is invalid。調(diào)度中心默認的 accessToken 在哪里看登錄后進入系統(tǒng)管理 - 系統(tǒng)配置里面有個AccessToken默認是default_token如果你改了兩邊要同步。executor.port是執(zhí)行器跟調(diào)度中心通信的一個 HTTP 端口隨便選個沒被占用的端口就行。這個端口不要跟業(yè)務(wù)服務(wù)端口混在一起它是執(zhí)行器自己啟動的一個 Netty HTTP 服務(wù)專門接收調(diào)度中心的調(diào)度請求。4.2 在調(diào)度中心新增執(zhí)行器和任務(wù)執(zhí)行器服務(wù)啟動起來之后登錄調(diào)度中心后臺。先到執(zhí)行器管理頁面點新增執(zhí)行器AppName填配置里面的my-app-executor名稱隨意注冊方式選自動注冊。保存后稍等一下執(zhí)行器列表里就能看到注冊過來的機器顯示的是 IP 和端口。再到任務(wù)管理頁面點新增任務(wù)填寫任務(wù)描述、調(diào)度類型一般選 Cron、Cron 表達式。比如每天凌晨 2 點跑一次Cron 就寫0 0 2 * * ?。JobHandler 這一項是關(guān)鍵它對應(yīng)你代碼里XxlJob注解的 value。在業(yè)務(wù)代碼中加一個定時方法Component public class DemoJob { XxlJob(demoJobHandler) public void demoJobHandler() throws Exception { System.out.println(執(zhí)行定時任務(wù)當(dāng)前時間 new Date()); } }JobHandler 填demoJobHandler執(zhí)行器選my-app-executor然后保存?;氐搅斜睃c擊執(zhí)行一次日志里能看到執(zhí)行成功。再到調(diào)度日志里查看完整調(diào)用鏈有執(zhí)行結(jié)果和時間消耗。實操心得新手最容易在 JobHandler 上踩坑代碼里XxlJob的值跟后臺填的 JobHandler 不一致導(dǎo)致執(zhí)行時報job handler not found。這個錯誤信息很常見排查的時候直接去代碼里搜注解值跟后臺比對就行。4.3 用容器方式部署執(zhí)行器的注意事項如果你打算把執(zhí)行器也容器化有幾個坑要注意。第一執(zhí)行器的網(wǎng)絡(luò)模式。調(diào)度中心要能訪問到執(zhí)行器的端口如果兩個容器在同一個 docker 網(wǎng)絡(luò)里執(zhí)行器的address要填容器的服務(wù)名不要填 localhost。比如執(zhí)行器容器名叫my-executor調(diào)度中心里自動注冊拿到的 IP 如果是 172.x 開頭的內(nèi)網(wǎng) IP調(diào)度中心可以訪問到但如果你在本機瀏覽器訪問可能不通。這種情況下可以在執(zhí)行器配置里手動指定xxl.job.executor.address為宿主機 IP 加映射端口或者讓兩個容器都走 host 網(wǎng)絡(luò)模式。生產(chǎn)環(huán)境我見過最省心的就是執(zhí)行器不容器化直接用宿主機進程跑避免容器 IP 變化導(dǎo)致調(diào)度失敗。第二任務(wù)日志掛載。執(zhí)行器的日志默認寫到應(yīng)用目錄下的logs/xxl-job/容器化部署時一定要用 volume 掛載出來否則任務(wù)跑一段時間容器重啟日志全丟排查問題無從下手。volumes: - ./executor-logs:/app/logs第三內(nèi)存限制。啟動執(zhí)行器時給進程預(yù)留足夠的內(nèi)存JVM 參數(shù)-Xms256m -Xmx512m這種別讓容器因為內(nèi)存不足被內(nèi)核殺掉。任務(wù)邏輯如果特別重堆內(nèi)存可以放寬。5. 新增任務(wù)與路由策略的實戰(zhàn)配置5.1 路由策略怎么選調(diào)度中心在配置任務(wù)時路由策略是個重要選項。默認是第一個意思是每次調(diào)度都發(fā)給注冊列表里的第一臺機器。如果只有一臺執(zhí)行器哪種策略都無所謂但多臺執(zhí)行器時就要想清楚了。簡單說說幾個常用策略的區(qū)別輪詢每次調(diào)度按順序發(fā)給不同機器適合無狀態(tài)任務(wù)。故障轉(zhuǎn)移如果一臺機器失敗自動發(fā)到下一臺適合對成功率有要求的任務(wù)。分片廣播所有機器同時執(zhí)行但通過參數(shù)區(qū)分各自處理的業(yè)務(wù)范圍適合大批量數(shù)據(jù)拆分的場景。實際用得最多的是輪詢和分片廣播。比如你要處理 100 萬條歷史數(shù)據(jù)單臺機器跑 2 小時用分片廣播后每臺機器只處理一部分速度成倍提升。分片廣播模式里執(zhí)行器會拿到j(luò)obParam和shardIndex、shardTotal在代碼里取這兩個值做數(shù)據(jù)切分。5.2 任務(wù)參數(shù)傳遞任務(wù)需要入?yún)r可以在后臺配置任務(wù)時填任務(wù)參數(shù)這個參數(shù)會以String形式傳給執(zhí)行器。代碼里處理方式XxlJob(paramJobHandler) public void paramJobHandler() throws Exception { String param XxlJobHelper.getJobParam(); // 解析 param比如按逗號拆分 String[] parts param.split(,); // 業(yè)務(wù)邏輯 }任務(wù)參數(shù)適合放一些業(yè)務(wù)標識、批次號之類的簡單值。如果你的參數(shù)特別復(fù)雜建議別塞在任務(wù)參數(shù)里而是讓執(zhí)行器去數(shù)據(jù)庫或配置中心讀取避免后臺維護大段 JSON。5.3 任務(wù)阻塞處理任務(wù)阻塞策略也很關(guān)鍵理解成上一個任務(wù)還沒跑完下一個調(diào)度時間到了怎么辦。默認是單機串行下一個等上一個跑完再執(zhí)行適合大多數(shù)場景。如果是丟棄后續(xù)調(diào)度那延遲比較高的任務(wù)可以選擇這個避免堆積。如果是覆蓋之前調(diào)度可以強行終止前一個任務(wù)但如果任務(wù)在跑數(shù)據(jù)庫事務(wù)強行終止可能造成事務(wù)懸掛不建議常用。任務(wù)設(shè)置界面還有個超時時間設(shè)置后如果任務(wù)執(zhí)行超過這個時間調(diào)度中心會認為失敗并記錄。但它不是強制 kill 任務(wù)只是標記失敗。真正要想 kill 運行中的任務(wù)需要后臺點終止按鈕或者配置了執(zhí)行器的端口后通過接口終止。5.4 執(zhí)行日志的配置執(zhí)行日志很重要排查問題全靠它。代碼里盡量多寫XxlJobHelper.log(處理到第{}條, i)這個日志會同步到調(diào)度中心的日志詳情頁面不用再跑到業(yè)務(wù)服務(wù)器上看文件。特別是跨部門協(xié)作時運維不會給業(yè)務(wù)開發(fā)開服務(wù)器權(quán)限調(diào)度中心的日志頁面就是唯一入口把關(guān)鍵日志打上去能省不少事。6. 常見問題與排查技巧實錄6.1 問題速查表我在多次部署和答疑過程中整理了一張問題速查表基本都是高頻問題建議收藏現(xiàn)象排查方向解決方案調(diào)度中心啟動后一直重啟數(shù)據(jù)庫連不上看 admin 容器日志確認數(shù)據(jù)庫地址、賬號密碼、表結(jié)構(gòu)是否初始化執(zhí)行器管理里沒有機器注冊accessToken 不一致端口不通校驗執(zhí)行器配置里的 token 與后臺系統(tǒng)配置是否一致從調(diào)度中心容器 telnet 執(zhí)行器端口執(zhí)行任務(wù)報job handler not found注解 value 和后臺配置不一致搜索代碼里 XxlJob 注解的 value跟后臺 JobHandler 比對任務(wù)執(zhí)行了但日志里看不到記錄執(zhí)行器日志丟失檢查執(zhí)行器日志文件確認 volume 掛載是否生效調(diào)度日志一直顯示運行中執(zhí)行器回調(diào)失敗看調(diào)度中心日志檢查執(zhí)行器地址是否可達任務(wù)觸發(fā)時間與本地差 8 小時時區(qū)問題給容器設(shè)置 TZAsia/ShanghaiJDBC URL 加 serverTimezoneAsia/ShanghaiMySQL 連接報Public Key Retrieval is not allowedMySQL 8.0 的問題JDBC URL 加allowPublicKeyRetrievaltrueDocker Desktop 啟動報 virtualization 錯誤虛擬化沒開或 Hyper-V 被禁任務(wù)管理器查看虛擬化狀態(tài)BIOS 開啟 VT-x/AMD-V啟用 Hyper-V 功能6.2 案例xxl-job 適配 PostgreSQL 時遇到的坑我有個客戶環(huán)境沒有現(xiàn)成的 MySQLDBA 只給了一個 PostgreSQL 實例。當(dāng)時試了一下社區(qū)方案過程不復(fù)雜但有幾個細節(jié)要注意。一是 MyBatis 的useGeneratedKeys用法。XXL-JOB 的XxlJobInfoDao在 insert 后要回拿主鍵 IDMySQL 里通過useGeneratedKeystrue keyPropertyid做PostgreSQL 里需要改 SQL 顯式返回主鍵比如INSERT ... RETURNING id。二是分頁語法。XXL-JOB 的任務(wù)列表查詢用的是 MySQL 的LIMIT offset, sizePostgreSQL 要改成LIMIT size OFFSET offset。如果漏改翻頁功能就會異常或直接報 SQL 語法錯誤。三是定時表達式存儲。XXL-JOB 的調(diào)度是內(nèi)部維護了一個時間輪和底層數(shù)據(jù)庫沒太大關(guān)系所以 SQL 適配對調(diào)度本身影響不大。但日志表xxl_job_log的清理 SQL 里有DELETE FROM xxl_job_log WHERE trigger_time DATE_SUB(NOW(), INTERVAL 7 DAY)這種 MySQL 函數(shù)要替換成 PostgreSQL 的NOW() - INTERVAL 7 days。所以你要是真要適配建議把 XML 里的 SQL 全部過一遍不能只改連接配置就完事。6.3 案例執(zhí)行器容器化后調(diào)度失敗之前幫一個項目排查問題執(zhí)行器放在容器里調(diào)度中心在另一臺服務(wù)器上。任務(wù)列表里添加任務(wù)后手動觸發(fā)日志一直顯示調(diào)度失敗。排查過程是先在調(diào)度中心所在服務(wù)器上用 curl 測一下執(zhí)行器端口發(fā)現(xiàn)不通。接著 docker inspect 看了執(zhí)行器容器的網(wǎng)絡(luò)發(fā)現(xiàn) Compose 默認創(chuàng)建了 bridge 網(wǎng)絡(luò)容器 IP 是 172.18.0.x。調(diào)度中心如果也在同一個 bridge 網(wǎng)絡(luò)它是能訪問的但那個客戶調(diào)度中心是裸 Java 進程跑的不在 docker 網(wǎng)絡(luò)里只能訪問宿主機 IP。執(zhí)行器容器映射到宿主機的端口沒有配置好導(dǎo)致外部無法訪問。解決辦法在 docker-compose 里給執(zhí)行器加ports: - 9999:9999同時把執(zhí)行器配置里的xxl.job.executor.address改成宿主機 IP 加映射端口。也就是說容器化執(zhí)行器時調(diào)度中心到執(zhí)行器的網(wǎng)絡(luò)通路一定要提前設(shè)計好不能只盯著容器內(nèi)網(wǎng)。6.4 日志排查的幾個技巧遇到任務(wù)執(zhí)行失敗第一件事不是改代碼而是看調(diào)度中心的執(zhí)行日志。在調(diào)度日志頁面點進某次調(diào)度記錄能看到兩個部分調(diào)度結(jié)果和執(zhí)行結(jié)果。如果調(diào)度結(jié)果是失敗說明調(diào)度中心沒能把請求發(fā)到執(zhí)行器網(wǎng)絡(luò)或者注冊的問題。如果調(diào)度結(jié)果成功但執(zhí)行結(jié)果失敗說明任務(wù)跑起來了但代碼里拋了異常這時候看執(zhí)行器日志重點看堆棧。如果兩個都成功但業(yè)務(wù)結(jié)果不對那問題就在代碼業(yè)務(wù)邏輯上。另外日志詳情頁每一行都有時間戳可以快速定位某次調(diào)度的具體執(zhí)行鏈路。多實例部署時調(diào)度中心日志里會顯示路由策略和所選機器 IP方便你確認這次是哪個實例執(zhí)行的。實操心得有時候任務(wù)沒執(zhí)行但日志里沒有任何記錄這種情況先看調(diào)度中心是否到了觸發(fā)時間還有調(diào)度中心上的調(diào)度狀態(tài)是否正常。如果用的是Cron類型可以點進任務(wù)詳情看最近 5 次觸發(fā)時間如果顯示觸發(fā)成功但沒有執(zhí)行日志大概率是執(zhí)行器回調(diào)地址配置有問題。6.5 關(guān)于 docker 青龍面板依賴管理和任務(wù)腳本的提醒Docker 生態(tài)里經(jīng)??吹接腥擞们帻埫姘暹@類工具來跑各種定時腳本它本身也是通過 Docker 部署的調(diào)度工具。有人會問 XXL-JOB 和這類面板有什么區(qū)別想用 XXL-JOB 替代或者反過來。其實兩者定位不太一樣。青龍面板更偏向個人腳本場景安裝簡單、自帶依賴管理能力比如安裝 Python 或 Node 依賴適合個人環(huán)境跑一些小腳本。XXL-JOB 則是企業(yè)級的任務(wù)調(diào)度平臺有完善的任務(wù)分組、權(quán)限管理、告警監(jiān)控和多執(zhí)行器路由適合在業(yè)務(wù)系統(tǒng)體系內(nèi)承擔(dān)定時任務(wù)中控角色。如果你是想在 Docker 里同時維護 XXL-JOB 和一堆依賴腳本的執(zhí)行環(huán)境可以在執(zhí)行器容器里預(yù)裝好 Python、Node、curl 等工具。比如自定義一個執(zhí)行器鏡像基于 openjdk 再用RUN apt-get install -y python3 nodejs把腳本運行時打進去這樣調(diào)度中心觸發(fā)任務(wù)時執(zhí)行器就能直接拉起外部腳本來跑。這個思路比維護一個獨立腳本面板跟業(yè)務(wù)打通更順滑權(quán)限、日志、監(jiān)控都在 XXL-JOB 平臺里統(tǒng)一管。但要注意腳本運行耗時如果遠超任務(wù)超時時間會被調(diào)度中心標記為失敗所以在 XXL-JOB 里跑外部腳本超時時間參數(shù)要留足余量。7. 進階擴展從單機到高可用單個調(diào)度中心跑起來容易但線上環(huán)境肯定要考慮高可用。XXL-JOB 的調(diào)度中心本身是無狀態(tài)的調(diào)度狀態(tài)都存數(shù)據(jù)庫所以橫向擴展調(diào)度中心實例就行。最簡單的方式是同一個鏡像起兩個容器前面掛 Nginx 做負載均衡。兩個實例連同一個 MySQL用戶訪問哪一個實例看到的任務(wù)數(shù)據(jù)是一致的。新增任務(wù)和執(zhí)行日志都會直接寫庫。需要注意數(shù)據(jù)庫本身不能單點。調(diào)度中心的表不大但對可用性要求高建議至少用 MySQL 主從。任務(wù)調(diào)度框架掛掉的代價是任務(wù)漏跑所以數(shù)據(jù)庫不要求多高性能但要求高可用和自動故障切換。執(zhí)行器的高可用就簡單了多部署幾個實例注冊到同一個 AppName 分組里利用路由策略分攤流量。執(zhí)行器實例掛了調(diào)度中心在注冊表里會自動把它摘除依賴心跳超時機制后續(xù)調(diào)度就不會再發(fā)給它。這里的心跳超時時間可以在系統(tǒng)配置里調(diào)默認是 90 秒。如果想在機器宕機后快速切走任務(wù)可以調(diào)小一點。但調(diào)太小網(wǎng)絡(luò)抖動時容易被誤判建議 60 到 90 秒之間比較合理。調(diào)度中心的告警功能也建議早點配起來。任務(wù)執(zhí)行失敗時可以發(fā)郵件告警需要配置郵箱相關(guān)信息。這塊在系統(tǒng)配置里能找到配置好之后告警對日常運維幫助極大——不用時刻盯調(diào)度日志任務(wù)失敗會第一時間收郵件。另一個進階操作是把調(diào)度中心接入你們已有的監(jiān)控體系比如 Prometheus。XXL-JOB 本身不直接暴露 metrics但可以用micronaut或者自定義攔截器來暴露任務(wù)執(zhí)行數(shù)量的指標。這個屬于錦上添花前期建議先把任務(wù)日志和告警用起來。8. 日常維護與升級經(jīng)驗調(diào)度平臺部署完不是結(jié)束而是要持續(xù)維護的。我拿自己維護這套平臺的經(jīng)驗列幾個特別容易忽視的點。首先是日志文件。調(diào)度中心和執(zhí)行器都會不斷產(chǎn)生日志文件時間久了磁盤會滿。建議設(shè)置日志保留天數(shù)調(diào)度中心的 applogs 目錄和執(zhí)行器的 xxl-job 日志目錄可以讓運維在宿主機上加 crontab 定清理比如只保留最近 7 天的日志。如果你給執(zhí)行器容器掛載了宿主機目錄那定期寫一條清理命令到 crontab 就行find /data/xxl-job/executor-logs -type f -mtime 7 -name *.log -delete其次是 xxl_job_log 表的增長。任務(wù)數(shù)量大時這張表漲得飛快。官方有日志自動清理功能在后臺系統(tǒng)配置里可以設(shè)置日志保留天數(shù)后臺會有個線程定期清理。默認值是 3 天你可以根據(jù)公司和合規(guī)要求調(diào)整但別設(shè)太長否則數(shù)據(jù)量大了之后查詢變慢。我一般建議保留 7 到 15 天。再一個是版本升級。升級前一定先備份數(shù)據(jù)庫這個最重要。XXL-JOB 從 2.3.0 到 2.4.0 之間數(shù)據(jù)庫結(jié)構(gòu)有過變化如果跨版本升級只替換鏡像不遷移數(shù)據(jù)庫啟動會報缺少字段。升級的正確步驟是停掉調(diào)度中心容器。備份xxl_job庫。用最新版源碼里的tables_xxl_job.sql對照增量變更執(zhí)行ALTER TABLE語句補齊字段。替換鏡像啟動。如果不想手動比對增量 SQL最快的方式是在測試環(huán)境用新版跑一遍觀察它第一次啟動時有沒有自動執(zhí)行遷移腳本再對生產(chǎn)環(huán)境操作。官方并不保證所有版本都平滑升級所以生產(chǎn)升級前一定在測試環(huán)境完整演練一次。最后是訪問權(quán)限控制。調(diào)度中心后臺要限制訪問來源如果用的是云服務(wù)器把 8080 端口的安全組白名單設(shè)成公司出口 IP如果在內(nèi)網(wǎng)用防火墻規(guī)則限制。調(diào)度中心的管理員賬號不要共用每人一個賬號方便審計。還有一個經(jīng)常被忽略的問題任務(wù)權(quán)限。XXL-JOB 里執(zhí)行器分組和用戶權(quán)限是有對應(yīng)關(guān)系的。給不同團隊建不同的執(zhí)行器分組再分配相應(yīng)的用戶這樣 A 團隊看不到 B 團隊的任務(wù)日志也不能互相穿透。這個在團隊規(guī)模大了之后特別重要。在我實際部署過的環(huán)境里真正值得注意的往往是這些細節(jié)問題。調(diào)度中心本身不復(fù)雜Docker 部署也不難但把權(quán)限、網(wǎng)絡(luò)、日志、升級這些事情都安排明白平臺才能真正穩(wěn)定運行。我這邊這套方案從 2.2.0 一路升到 2.4.0中間踩過不少坑上面寫的這些基本都是親身經(jīng)歷過的。最后再分享一個小經(jīng)驗如果你在任務(wù)日志里看到類似任務(wù)結(jié)果丟失的情況不要急著改代碼先去確認一下執(zhí)行器的 Netty HTTP 端口是不是一直被某些安全策略攔截。這個端口雖然是隨機選的但得保證調(diào)度中心到執(zhí)行器的雙向網(wǎng)絡(luò)暢通。我第一次部署時就是吃了這個虧搞了半天最后發(fā)現(xiàn)是云平臺安全組策略把執(zhí)行器端口給禁了。