1. 项目概述:为什么XXL-Job必须集群化,而不是单点跑着凑合?
XXL-Job不是那种装完就能扔角落里自动运转的“傻瓜调度器”。我最早在2019年接手一个电商订单对账系统时,就吃过单节点的亏——凌晨三点调度中心突然OOM挂掉,37个定时任务全部中断,财务侧第二天一早发现昨日对账数据全断档,技术团队被拉进跨部门复盘会坐了四个小时。从那以后,但凡涉及核心业务的定时任务(比如库存同步、风控规则刷新、用户行为归因计算),我坚持一条铁律:XXL-Job调度中心不集群,等于没上线。
所谓“集群部署和高可用”,本质是解决三个刚性问题:单点故障不可接受、并发调度能力有瓶颈、配置与状态必须全局一致。很多人误以为只要把调度中心多起几个实例就算集群了,结果发现任务重复触发、路由策略失效、执行器注册混乱——这恰恰暴露了对XXL-Job集群机制的误解。它不是无状态服务,而是强依赖中心化注册中心+分布式锁+一致性配置分发的三重保障体系。你看到的“高可用”,背后是ZooKeeper或Redis做服务发现、MySQL主从+读写分离扛住调度元数据压力、Quartz集群模式协调触发时机、以及执行器端心跳保活与故障剔除的完整闭环。
这个实战指南,就是把我过去三年在金融、物流、SaaS三条业务线落地XXL-Job集群踩过的所有坑、调过的所有参数、验证过的每一种拓扑结构,掰开揉碎讲清楚。不讲概念,只说“为什么这里必须用ZK而不是Eureka”、“为什么MySQL binlog延迟会导致任务漏触发”、“为什么执行器注册超时阈值设成30秒反而比60秒更稳”。适合正在规划生产环境部署的架构师、被线上调度异常折磨的运维同学,以及想真正搞懂XXL-Job底层逻辑的开发同学。如果你还在用单机版跑核心任务,或者刚配好双节点却发现任务乱跳——这篇就是为你写的。
2. 集群架构设计与核心组件选型逻辑
2.1 调度中心集群不是“多起几个进程”,而是四层协同的精密系统
XXL-Job调度中心集群绝非简单地启动多个xxl-job-admin实例。它的高可用实现,是调度中心、注册中心、存储中心、执行器四者深度耦合的结果。任何一层选型失误,都会导致整个集群“形似神散”。
调度中心层(XXL-Job Admin):负责任务触发、路由分发、日志收集、UI管理。集群中每个实例都是有状态的——它们共享同一套MySQL元数据,但各自维护本地Quartz调度器、本地任务执行线程池、本地缓存(如执行器地址缓存)。关键在于:所有实例必须连接同一个MySQL库,且必须启用Quartz的JDBCJobStore集群模式。否则会出现任务被多个实例重复触发的灾难性问题。
注册中心层(服务发现):这是集群的“神经中枢”。XXL-Job官方支持ZooKeeper、Redis、Etcd三种。我们实测下来,ZooKeeper是唯一能稳定支撑生产级高可用的选项。原因很实在:Redis作为注册中心时,执行器心跳续租依赖
SET key value EX seconds NX,一旦Redis主从切换期间出现短暂脑裂,执行器可能同时向新旧两个调度中心注册,导致任务路由错乱;而ZooKeeper的ZAB协议保证了强一致性,临时节点(ephemeral node)的创建与销毁严格有序,执行器下线感知延迟稳定在3秒内(ZK session timeout默认30秒,但我们调优到15秒)。存储中心层(MySQL):所有任务定义、触发日志、执行器注册信息都存在这里。单点MySQL是最大单点风险。我们采用MySQL 8.0+ MGR(Group Replication)集群,而非传统主从。MGR的多写特性让任意节点都可读写,避免了主库宕机后手动切换VIP的运维黑洞。更重要的是,MGR内置的流控机制(flow control)能自动抑制慢节点,防止binlog复制延迟拖垮整个调度链路——这点在大数据量任务日志写入时尤为关键。
执行器层(XXL-Job Executor):这是常被忽视的“哑终端”。执行器本身不集群,但必须支持多实例注册+负载均衡路由。每个执行器实例启动时,向注册中心注册自己的IP+PORT+AppName,并定期发送心跳。调度中心从注册中心拉取该AppName下的所有存活实例列表,按配置的路由策略(轮询、LRU、一致性哈希等)分发任务。注意:执行器实例数必须≥2,且部署在不同物理机/可用区,否则无法规避单机故障。
提示:不要试图用Nginx反向代理调度中心UI来实现“负载均衡”。XXL-Job Admin的UI请求(如任务操作、日志查看)必须路由到同一实例,因为部分操作依赖本地缓存(如执行器地址缓存)。正确的做法是:UI入口走DNS轮询或VIP,后台API调用由执行器直连注册中心获取调度中心地址列表,自行选择。
2.2 为什么放弃Eureka/Nacos?ZooKeeper的不可替代性在哪?
网络上很多教程推荐用Nacos或Eureka做注册中心,理由是“Spring Cloud全家桶更统一”。但我们在线上压测中发现,当执行器规模超过200个时,Nacos的健康检查接口(/nacos/v1/ns/instance/status)响应延迟飙升至2秒以上,导致调度中心拉取执行器列表超时,进而触发默认路由策略(第一个注册的实例),造成流量倾斜。Eureka的问题更隐蔽:其自我保护模式在大规模实例注册时会主动关闭剔除下线节点的功能,导致“僵尸执行器”长期滞留注册表,任务持续被派发到已宕机的机器。
ZooKeeper则完全不同。它的设计哲学是“宁可拒绝服务,也不提供错误数据”。我们用zkCli.sh直接测试:
# 模拟执行器注册(创建临时节点) create -e /xxl-job/executor/demo-executor/10.10.1.100:9999 "" # 查看所有注册节点(毫秒级响应) ls /xxl-job/executor/demo-executor # 删除节点(立即生效) delete /xxl-job/executor/demo-executor/10.10.1.100:9999ZooKeeper的ZNode操作是原子的,且watch机制让调度中心能实时感知变更。我们甚至用echo stat | nc localhost 2181监控ZK连接数,发现200个执行器注册时,ZK客户端连接数稳定在200左右,无抖动。这种确定性,是AP型注册中心(Nacos/Eureka)无法提供的。
2.3 MySQL MGR集群 vs 主从复制:一次数据库选型的血泪教训
最初我们用MySQL 5.7主从复制,主库写,从库读(调度中心配置spring.datasource.url=jdbc:mysql://master,slave1,slave2/xxl_job?loadBalance=true)。上线两周后,某次主库计划维护,手动切换VIP,结果因从库binlog延迟12秒,导致任务触发记录写入主库,但调度中心从从库读取执行器列表时拿到的是旧数据,任务被派发到已下线的执行器,返回Connection refused。更糟的是,XXL-Job的失败重试机制将该任务标记为“失败”,但实际执行器根本没收到请求——日志里查不到任何痕迹。
换成MySQL 8.0 MGR后,问题彻底解决。MGR的组通信协议(XCom)确保所有节点数据强一致。我们配置了3节点MGR集群(1主2备),并通过SELECT MEMBER_STATE, MEMBER_ROLE FROM performance_schema.replication_group_members;实时监控状态。当主节点宕机,MGR在8秒内自动选举新主(我们调优过group_replication_member_expel_timeout=0),所有调度中心实例无缝切换,任务触发零丢失。关键参数如下:
-- MGR初始化配置(my.cnf) [mysqld] plugin_load_add='group_replication.so' transaction_write_set_extraction=XXHASH64 group_replication_group_name="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" group_replication_start_on_boot=off group_replication_local_address="10.10.1.101:33061" group_replication_group_seeds="10.10.1.101:33061,10.10.1.102:33061,10.10.1.103:33061" group_replication_bootstrap_group=off -- 启动后执行 CHANGE MASTER TO MASTER_USER='repl', MASTER_PASSWORD='repl' FOR CHANNEL 'group_replication_recovery'; START GROUP_REPLICATION;3. 核心组件部署与关键参数调优详解
3.1 ZooKeeper集群部署:从3节点到5节点的演进路径
ZooKeeper集群节点数必须是奇数(3/5/7),这是Paxos协议的硬性要求。我们最初用3节点ZK,但在一次网络分区事件中(机房交换机故障),2个节点失联,剩余1个节点因无法获得多数派(quorum)投票而自动停止服务,导致整个XXL-Job集群不可用。后来升级到5节点,即使2个节点宕机,剩余3个仍能组成多数派,服务持续可用。
部署要点:
- 硬件隔离:5个ZK节点必须部署在不同物理机、不同机柜、不同供电线路。我们曾把3个ZK放在同一台4U服务器的3个VM里,结果宿主机硬盘故障,3个ZK同时挂掉。
- 磁盘IO优化:ZooKeeper的事务日志(
/dataLogDir)必须单独挂载SSD,且禁用atime更新:# /etc/fstab /dev/sdb1 /var/lib/zookeeper/log xfs defaults,noatime 0 0 - JVM参数:ZK对GC极其敏感,必须用G1GC并限制堆内存:
# zoo.cfg JVMFLAGS="-Xms2g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
ZooKeeper关键配置(zoo.cfg):
# 基础配置 tickTime=2000 initLimit=10 syncLimit=5 # 数据目录(务必分开!) dataDir=/var/lib/zookeeper/data dataLogDir=/var/lib/zookeeper/log # 客户端连接端口 clientPort=2181 # 四字命令端口(用于监控) admin.serverPort=8080 # 集群配置(5节点示例) server.1=10.10.1.101:2888:3888 server.2=10.10.1.102:2888:3888 server.3=10.10.1.103:2888:3888 server.4=10.10.1.104:2888:3888 server.5=10.10.1.105:2888:3888 # 启用动态重配置(重要!) dynamicConfigFile=/var/lib/zookeeper/conf/zoo.cfg.dynamic每个ZK节点的myid文件内容必须唯一(/var/lib/zookeeper/data/myid):
# 节点1 echo "1" > /var/lib/zookeeper/data/myid # 节点2 echo "2" > /var/lib/zookeeper/data/myid # ...以此类推注意:ZooKeeper的
initLimit(初始同步时间)和syncLimit(心跳超时)必须根据网络延迟调整。我们机房内网延迟<1ms,所以设为默认值;若跨机房部署,initLimit需调大至20(即40秒),否则节点启动时无法完成初始同步,会卡在LOOKING状态。
3.2 XXL-Job调度中心集群部署:配置文件逐行解析
调度中心的application.properties是集群稳定的核心。以下是我们生产环境的完整配置(已脱敏),重点参数均加注释:
### xxl-job admin port server.port=8080 ### xxl-job admin context-path server.servlet.context-path=/xxl-job-admin ### xxl-job db spring.datasource.url=jdbc:mysql://10.10.1.101:3306,10.10.1.102:3306,10.10.1.103:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true spring.datasource.username=root spring.datasource.password=your_password spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # 关键:必须开启HikariCP连接池的keepalive,防止MySQL空闲连接超时断开 spring.datasource.hikari.connection-timeout=30000 spring.datasource.hikari.validation-timeout=3000 spring.datasource.hikari.idle-timeout=600000 spring.datasource.hikari.max-lifetime=1800000 spring.datasource.hikari.keepalive-time=30000 ### xxl-job, datasource pool spring.datasource.type=com.zaxxer.hikari.HikariDataSource ### xxl-job, quartz init # 关键:必须启用Quartz集群模式,否则多实例会重复触发任务 xxl.job.quartz.jdbc.dataSource=xxlJobDataSource xxl.job.quartz.scheduler.instanceName=XXL-JOB-SCHEDULER xxl.job.quartz.scheduler.instanceId=AUTO xxl.job.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool xxl.job.quartz.threadPool.threadCount=20 xxl.job.quartz.threadPool.threadPriority=5 xxl.job.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true # 关键:Quartz集群表前缀必须与XXL-Job保持一致(默认QRTZ_) xxl.job.quartz.dataSource.myDS.driver=com.mysql.cj.jdbc.Driver xxl.job.quartz.dataSource.myDS.URL=jdbc:mysql://10.10.1.101:3306,10.10.1.102:3306,10.10.1.103:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false&serverTimezone=Asia/Shanghai xxl.job.quartz.dataSource.myDS.user=root xxl.job.quartz.dataSource.myDS.password=your_password xxl.job.quartz.dataSource.myDS.maxConnections=20 xxl.job.quartz.dataSource.myDS.unusedConnectionTimeout=300 xxl.job.quartz.dataSource.myDS.checkoutTimeout=3000 ### xxl-job, access token xxl.job.accessToken=default_token ### xxl-job, i18n (default is zh_CN, and you can choose "zh_CN", "zh_TC" and "en") xxl.job.i18n=zh_CN ### xxl-job, trigger pool size (must be larger than cpu core count) xxl.job.trigger.pool.fast=200 xxl.job.trigger.pool.slow=100 ### xxl-job, log retention days xxl.job.logretentiondays=30 ### xxl-job, registry center (ZooKeeper) # 关键:注册中心必须指向ZK集群,而非单点 xxl.job.admin.registry.address=zookeeper://10.10.1.101:2181?zkSessionTimeout=15000&zkConnectionTimeout=10000 # 关键:调度中心实例名必须全局唯一,用于ZK节点标识 xxl.job.admin.appname=xxl-job-admin-prod # 关键:调度中心地址(供执行器反向调用),必须是本机可访问的IP+PORT xxl.job.admin.address=http://10.10.1.101:8080/xxl-job-admin特别强调三个易错点:
xxl.job.admin.address必须填写本机真实IP,不能写localhost或127.0.0.1。执行器通过此地址回调调度中心,若填错,执行器日志会报Connection refused。xxl.job.admin.registry.address中的zkSessionTimeout=15000(15秒)必须小于ZK的tickTime*2(默认4秒),否则ZK会认为客户端失联过快。我们ZK的tickTime=2000,所以设为15000是安全的。xxl.job.trigger.pool.fast/slow参数决定了任务触发的并发能力。fast池处理高频小任务(如每分钟一次的健康检查),slow池处理低频大任务(如每小时一次的数据清洗)。我们按CPU核心数*10设置,16核机器设为fast=160, slow=80。
3.3 执行器集群部署:不只是多起几个进程
执行器(xxl-job-executor-sample-springboot)的集群部署,核心是注册一致性和故障自愈。
执行器的application.properties关键配置:
### xxl-job admin address list, such as "http://address" or "http://address1,http://address2" # 关键:此处填调度中心集群的VIP或DNS,而非单个地址 xxl.job.admin.addresses=http://xxl-job-admin-vip:8080/xxl-job-admin ### xxl-job, access token xxl.job.accessToken=default_token ### xxl-job executor appname # 关键:所有同类型执行器必须用相同appname,这是注册分组的依据 xxl.job.executor.appname=demo-executor ### xxl-job executor registry address # 关键:执行器注册到ZK的路径,必须与调度中心配置的registry.address一致 xxl.job.executor.registry.address=zookeeper://10.10.1.101:2181?zkSessionTimeout=15000&zkConnectionTimeout=10000 ### xxl-job executor port # 关键:每个执行器实例的端口必须唯一,避免冲突 xxl.job.executor.port=9999 ### xxl-job executor logpath xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler ### xxl-job executor logretentiondays xxl.job.executor.logretentiondays=30执行器启动后,在ZK中生成的节点路径为:/xxl-job/executor/{appname}/{ip:port}。例如:
/xxl-job/executor/demo-executor/10.10.1.201:9999 /xxl-job/executor/demo-executor/10.10.1.202:9999 /xxl-job/executor/demo-executor/10.10.1.203:9999调度中心通过监听/xxl-job/executor/demo-executor子节点变化,实时维护执行器列表。当某个执行器进程崩溃,ZK上的临时节点自动消失,调度中心在3秒内(ZK session timeout)将其从列表剔除,后续任务不再派发。
实操心得:执行器的
xxl.job.executor.port不要用随机端口(如0),必须显式指定。我们曾用随机端口,结果容器重启后端口变化,ZK上残留旧节点(10.10.1.201:34567),新节点(10.10.1.201:45678)同时存在,导致任务被重复派发到同一台机器的两个端口,引发资源竞争。
4. 高可用验证与故障注入实战
4.1 四步验证法:确认集群真正可用
部署完成后,绝不能只看UI上“执行器注册成功”就认为高可用达成。必须进行以下四步验证:
第一步:ZooKeeper节点健康检查
# 在任意ZK节点执行,检查集群状态 echo "stat" | nc 10.10.1.101 2181 | grep "Mode:" # 输出应为 "Mode: follower" 或 "Mode: leader" # 检查所有节点是否在线 echo "mntr" | nc 10.10.1.101 2181 | grep "zk_followers\|zk_synced_followers" # zk_followers应为4(5节点集群,1个leader+4个follower)第二步:调度中心实例注册验证登录ZK客户端,检查调度中心是否注册:
# 进入ZK CLI zkCli.sh -server 10.10.1.101:2181 # 查看调度中心注册路径 ls /xxl-job/admin # 应看到类似输出:[xxl-job-admin-prod-10.10.1.101:8080, xxl-job-admin-prod-10.10.1.102:8080, ...]第三步:执行器路由一致性验证在调度中心UI创建一个测试任务(Cron:0/30 * * * * ?,即每30秒触发),执行器选择“轮询”策略。然后:
- 查看任务触发日志,确认每次触发的执行器IP是否按顺序轮换(
10.10.1.201 → 10.10.1.202 → 10.10.1.203 → 10.10.1.201...) - 手动停掉
10.10.1.202的执行器进程,等待3秒后,观察日志是否跳过该IP,直接轮到10.10.1.203,再回到10.10.1.201
第四步:MySQL MGR故障切换验证模拟主节点宕机:
-- 在当前主节点执行(先确认谁是主) SELECT MEMBER_HOST, MEMBER_ROLE FROM performance_schema.replication_group_members WHERE MEMBER_STATE='ONLINE'; -- 假设10.10.1.101是主,执行 SHUTDOWN;等待8秒后,检查:
-- 在任意节点执行 SELECT MEMBER_HOST, MEMBER_ROLE FROM performance_schema.replication_group_members WHERE MEMBER_STATE='ONLINE'; -- 应看到新主节点(如10.10.1.102)角色变为"PRIMARY" -- 创建一个新任务,确认能正常保存到数据库4.2 故障注入实战:模拟5种典型生产事故
我们用混沌工程方法,对集群进行5种故障注入,记录恢复时间和影响范围:
| 故障类型 | 注入方式 | 观察现象 | 恢复时间 | 关键结论 |
|---|---|---|---|---|
| ZK单节点宕机 | systemctl stop zookeeper(停1个follower) | ZK集群继续服务,调度中心无感知,执行器注册不受影响 | <1秒 | 3节点ZK可容忍1节点故障,5节点可容忍2节点 |
| ZK网络分区 | iptables -A INPUT -s 10.10.1.104 -j DROP(隔离1个节点) | 被隔离节点进入SUSPENDED状态,其余4节点正常服务 | <5秒 | ZK的electionTimeout默认200ms,选举迅速 |
| MySQL主节点宕机 | systemctl stop mysqld(停MGR主) | MGR自动选举新主,调度中心连接新主无中断,任务触发零丢失 | 8秒 | MGR切换比传统主从快10倍以上 |
| 调度中心单实例宕机 | kill -9 $(pgrep -f "xxl-job-admin") | UI访问短暂502(DNS轮询到宕机实例),3秒后自动切到健康实例,任务触发不受影响 | <3秒 | 调度中心实例间无状态依赖,故障隔离性好 |
| 执行器全量宕机 | pkill -f "demo-executor"(停所有执行器) | 调度中心UI显示执行器离线,任务触发日志大量RUNNING但无SUCCESS/FAIL,30秒后自动标记为FAIL | 30秒 | 执行器心跳超时默认30秒,可调优至15秒 |
注意:执行器全量宕机时,XXL-Job的“失败重试”机制会按配置的重试次数(默认0次)重新派发。若业务允许,建议在任务配置中开启重试(如重试2次,间隔10秒),避免单次网络抖动导致任务失败。
4.3 生产环境监控告警清单
集群上线后,必须建立以下7项核心监控:
- ZooKeeper监控:
zk_followers(必须≥4)、zk_outstanding_requests(>1000需告警)、zk_avg_latency(>10ms需告警) - MySQL MGR监控:
MEMBER_STATE=ONLINE(所有节点)、MEMBER_ROLE=PRIMARY(仅1个)、RECEIVED_TRANSACTION_SET(延迟<1000) - 调度中心JVM监控:
heap_used_percent(>80%告警)、thread_count(>500告警)、gc_pause_time(>1s告警) - 执行器心跳监控:通过ZK API
/xxl-job/executor/{appname}节点数,低于预期数量立即告警 - 任务触发延迟监控:采集
xxl_job_log.trigger_code=200的日志,计算trigger_time到handle_time的差值,P95>5s告警 - 数据库连接池监控:HikariCP的
active连接数持续>90%告警,timeout计数>0立即告警 - 网络连通性监控:调度中心到ZK、MySQL、执行器的TCP连通性(
telnet zk_ip 2181)
我们用Prometheus+Grafana搭建监控面板,其中最关键的“集群健康度”仪表盘包含:
- 左上角:ZK节点在线数(5/5)
- 右上角:MGR节点在线数(3/3)
- 中间:调度中心实例数(3/3)
- 左下:执行器注册数(20/20)
- 右下:最近1小时任务失败率(<0.1%)
5. 常见问题排查与独家避坑指南
5.1 “任务重复触发”问题的根因分析与修复
这是集群中最让人抓狂的问题。现象:一个Cron任务(如0 0 * * * ?)在日志里出现两条SUCCESS记录,时间相差几秒。
根因排查路径:
- 首先确认Quartz集群模式是否启用:检查
application.properties中是否有xxl.job.quartz.scheduler.instanceId=AUTO且xxl.job.quartz.dataSource.myDS配置正确。若未配置,Quartz会以单机模式运行,每个调度中心实例独立触发。 - 检查MySQL的
qrtz_locks表:SELECT * FROM qrtz_locks WHERE lock_name='TRIGGERS';正常情况下该行lock_name应被某一个实例持有(locked_by字段为实例名)。若出现多行或locked_by为空,说明Quartz锁未生效。 - 检查MySQL时区:
SELECT @@global.time_zone, @@session.time_zone;若为SYSTEM,而系统时区与JVM时区不一致(如JVM用Asia/Shanghai,MySQL用UTC),会导致Quartz认为触发时间未到,多个实例同时抢锁。
修复方案:
- 强制MySQL使用东八区:
SET GLOBAL time_zone = '+08:00'; - 在
application.properties中添加:spring.jackson.date-format=yyyy-MM-dd HH:mm:ss和spring.jackson.time-zone=GMT+8 - 重启所有调度中心实例,清空
qrtz_*表(备份后),让Quartz重建锁表
5.2 “执行器注册不上”问题的10种可能及解决方案
执行器启动后,UI上始终看不到注册信息。按优先级排查:
| 序号 | 可能原因 | 检查命令 | 解决方案 |
|---|---|---|---|
| 1 | 执行器配置的xxl.job.admin.addresses无法访问调度中心 | curl -v http://调度中心IP:8080/xxl-job-admin/actuator/health | 确保网络连通,防火墙开放8080端口 |
| 2 | xxl.job.executor.appname与调度中心任务配置的执行器名称不一致 | 查看UI任务配置页的“执行器”下拉框 | 保持完全一致,区分大小写 |
| 3 | ZooKeeper连接超时 | echo "ruok" | nc 10.10.1.101 2181 | 检查ZKclientPort,调整zkConnectionTimeout=10000 |
| 4 | 执行器端口被占用 | netstat -tuln | grep 9999 | 修改xxl.job.executor.port为未占用端口 |
| 5 | 调度中心未配置xxl.job.admin.registry.address | 查看调度中心application.properties | 必须配置,且与执行器的xxl.job.executor.registry.address一致 |
| 6 | 执行器JVM时区与调度中心不一致 | java -XshowSettings:properties -version 2>&1 | grep user.timezone | 统一设为-Duser.timezone=Asia/Shanghai |
| 7 | MySQLxxl_job库缺少xxl_job_registry表 | SHOW CREATE TABLE xxl_job_registry; | 执行xxl-job-admin/doc/db/tables.sql建表 |
| 8 | 执行器accessToken与调度中心不匹配 | 查看调度中心xxl.job.accessToken | 保持一致,或设为""禁用token |
| 9 | Docker容器内网DNS解析失败 | docker exec -it executor ping zookeeper | 在docker run中添加--add-host=zookeeper:10.10.1.101 |
| 10 | Kubernetes Service DNS解析超时 | kubectl exec -it executor -- nslookup xxl-job-admin | 增加dnsConfig.options: [{name: timeout, value: "2"}] |
5.3 “任务触发延迟”问题的性能调优三板斧
任务本该在整点触发,却延迟10秒以上。这不是网络问题,而是调度中心内部瓶颈。
第一板斧:调大触发线程池
# 默认fast=200,slow=100,对于高频任务(如每秒10次)明显不足 xxl.job.trigger.pool.fast=500 xxl.job.trigger.pool.slow=200第二板斧:优化MySQL慢查询开启MySQL慢查询日志,发现SELECT * FROM xxl_job_info WHERE ...语句耗时2秒。原因是xxl_job_info表缺乏复合索引。添加:
ALTER TABLE `xxl_job_info` ADD INDEX `idx_trigger_status_next_time` (`trigger_status`,`trigger_next_time`);第三板斧:分离日志写入xxl_job_log表写入频繁,拖慢主库。我们将日志表拆分到独立MySQL实例:
-- 创建日志专用库 CREATE DATABASE xxl_job_log CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 修改调度中心配置,日志写入专用库 xxl.job.log.datasource.url=jdbc:mysql://10.10.1.104:3306/xxl_job_log?...实操心得:我们曾遇到一个极端案例——某任务每秒触发100次,单次执行耗时50ms,导致
xxl_job_log表每秒写入100条,MySQL IOPS打满。最终方案是:执行器本地异步写日志(用Disruptor队列),每100ms批量刷到数据库,将IOPS降低90%。
6. 运维手册:日常巡检与升级 checklist
6.1 每日巡检清单(5分钟完成)
- ZooKeeper健康:
echo "stat" | nc 10.10.1.101 2181 | grep "Mode:"—— 确认leader存在 - MGR状态:
mysql -h10.10.1.101 -e "SELECT MEMBER_HOST,MEMBER_ROLE FROM performance_schema.replication_group_members;"—— 确认3节点online - 调度中心实例:
curl -s http://xxl-job-admin-vip:8080/xxl-job-admin/actuator/health | jq .status—— 返回UP - 执行器在线数:
echo "ls /xxl-job/executor/demo-executor" | nc 10.10.1.101 2181 \| wc -l—— 应等于部署数 - 任务失败率:
grep "FAIL" /data/applogs/xxl-job/jobhandler/*.log \| wc -l—— 24小时内<10次