ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux定时任务排障实战:从CPU飙升到幽灵任务定位

Linux定时任务排障实战:从CPU飙升到幽灵任务定位 1. 这不是服务器“生病”是定时任务在悄悄吃掉你的CPU——一个真实排障现场的复盘你有没有过这种经历凌晨三点监控告警突然炸开线上服务响应延迟从200ms飙到2秒接口超时率瞬间冲到15%但所有服务进程看起来都“活着”日志里没有ERROR内存没爆磁盘IO也正常——你盯着屏幕手心冒汗却像在迷雾里开车根本找不到刹车在哪。我上周就撞上了这个坑。一台跑着WMS系统和SpringCloud微服务集群的CentOS 7生产服务器连续两天在每天上午10:15准时变慢持续12分钟之后又自动恢复。运维同事第一反应是查网络、查数据库连接池、查JVM GC日志折腾一小时无果。最后是我翻出top -H按线程CPU排序发现一个叫java的进程下有个线程IDLWP长期占着98%的单核CPU而这个线程的堆栈快照里反复出现org.quartz.core.JobRunShell.run——它根本不是业务代码而是Quartz调度器在执行一个没人记得起的定时任务。更讽刺的是这个任务配置在application.yml里cron表达式写的是0 0/5 * * * ?意思是每5分钟执行一次但实际逻辑里有个死循环读取本地CSV文件做数据清洗而那个CSV文件上周被运维误操作同步了1.2GB的测试数据。问题不在服务器而在一个被遗忘的、写得不严谨的定时任务上。这件事让我意识到Linux系统排障最危险的陷阱不是硬件故障或配置错误而是那些“安静运行”的后台任务——它们不报错、不崩溃、不打日志只是默默把CPU、内存、IO这些资源一点点啃光。今天这篇不讲教科书式的命令大全也不列一堆高大上的架构图就带你回到那个真实的两小时排障现场拆解我是怎么从“系统整体变慢”这个模糊现象一步步剥洋葱最终定位到那个藏在/etc/crontab和SpringBootScheduled注解双重夹击下的“幽灵任务”。如果你用的是国产麒麟系统、或者正在搭建农产品销售系统、IM群发系统甚至只是在虚拟机里装Ubuntu练手只要你的服务器上跑着任何一种定时任务——cron、systemd timer、Quartz、XXL-JOB、甚至是Kettle里的spoon脚本——这篇文章里的思路和工具链都能直接抄作业。2. 排障不是靠猜是靠建立“资源流向地图”——我的四层定位法很多人一遇到服务器变慢第一反应就是top看CPUdf -h看磁盘free -h看内存这没错但太浅。就像医生不能只量体温就开药方Linux排障的核心是搞清楚“谁在消耗什么资源为什么消耗消耗得是否合理”。我给自己总结了一套四层定位法不是线性流程而是像侦探画关系网一样层层交叉验证。这套方法在麒麟系统、CentOS、Ubuntu上都验证过关键不在于命令多炫酷而在于每一步都带着明确的“证伪”目的——我要排除什么而不是证明什么。2.1 第一层确认“慢”是全局还是局部锁定影响域很多所谓的“服务器变慢”其实是某个服务或某个用户会话的问题。我第一步永远不是登录服务器而是先看外部表现。比如我们WMS系统前端页面加载慢但API网关的健康检查curl -I http://gateway:8080/actuator/health返回200说明网关本身没挂再用curl -s http://wms-service:8081/api/v1/stock/summary | wc -c测核心库存接口发现耗时从300ms变成2.1s而另一个订单查询接口/api/v1/order/list却只有400ms这就很关键——说明问题不是整个JVM或整个服务器而是特定业务路径。我立刻在WMS服务节点上执行# 查看当前所有Java进程的PID和启动参数重点找带wms字样的 ps aux | grep java | grep wms # 对应PID用jstat看GC情况这里PID是12345 jstat -gc 12345 1000 3 # 同时用jstack抓线程快照重定向到文件方便分析 jstack 12345 /tmp/wms-thread-dump-$(date %s).txt结果发现GC频率正常老年代没满但jstack输出里有大量RUNNABLE状态的线程堆栈都卡在java.io.FileInputStream.readBytes——这明显是IO阻塞不是GC问题。这时候我就知道问题大概率出在文件读取或网络请求上而不是CPU计算瓶颈。这个判断直接跳过了查CPU占用率的步骤节省了至少20分钟。很多新手会在这里陷入误区看到top里Java进程CPU高就以为是代码写得烂其实90%的情况是它在等IOCPU高只是“等待IO完成”这个动作的副产品。2.2 第二层资源消耗者是谁用pidstat代替top看本质top只能告诉你哪个进程CPU高但它无法区分是计算密集型真正在做加减乘除还是IO密集型在等磁盘或网络。我第二步必用pidstat它是sysstat包里的神器能按秒级精度看每个进程的CPU、IO、上下文切换、线程数。安装很简单# CentOS/RHEL yum install -y sysstat # Ubuntu/Debian apt-get install -y sysstat然后执行# 每2秒刷新一次显示所有进程的CPU、IO等待、上下文切换 pidstat -u -r -w -p ALL 2 # 关键看这几列 # %usr用户态CPU时间占比真正在跑代码 # %system内核态CPU时间占比系统调用、中断处理 # %iowaitCPU等待IO完成的时间占比5%就要警惕 # cswch/s每秒上下文切换次数10000通常意味着线程争抢严重 # Command进程名那天的数据显示java进程的%iowait高达65%而%usr只有12%%system是8%。这意味着CPU大部分时间在干等不是在干活。再结合jstack里FileInputStream.readBytes的线索我立刻把矛头指向文件IO。这时候我不会急着去查/var/log因为日志文件通常很小不会导致1.2GB的IO压力。我想到一个更可能的地方定时任务生成的临时文件、数据同步的缓存目录、或者Kettlespoon作业的输出路径。我用lsof命令锁定了具体文件# 查看java进程12345打开的所有文件按文件大小倒序 lsof -p 12345 | awk {print $7, $9} | sort -nr | head -20 # 输出里赫然出现 # 1245678900 /opt/kettle/data/stock_full_sync_20240520.csv # 876543210 /tmp/wms_data_cache/20240520_stock.csv两个文件都超过1GB而且路径都指向数据同步任务。到这里问题已经呼之欲出有一个定时任务在疯狂读写大文件。2.3 第三层谁在驱动这个IO揪出定时任务的“双面人”Linux里定时任务有两大阵营系统级的cron和应用级的调度框架。cron在/etc/crontab、/etc/cron.d/、用户家目录的crontab -e里而Java应用常用Quartz、SpringBootScheduled、XXL-JOB它们的配置分散在代码、配置文件或数据库里。那天我先查cron# 查看root用户的crontab crontab -u root -l # 查看系统级crontab cat /etc/crontab # 查看/etc/cron.d/下所有文件 ls -la /etc/cron.d/结果干净得让人失望——没有可疑任务。这时候我转向应用层。WMS系统用的是SpringBoot 2.7 Quartz定时任务配置在application-prod.yml里。我直接搜索关键词# 在项目jar包解压目录或源码目录搜索cron、schedule、quartz grep -r cron\|Scheduled\|quartz ./src/main/resources/ --include*.yml --include*.properties # 输出关键行 # quartz.job-store-classorg.quartz.impl.jdbcjobstore.JobStoreTX # spring.quartz.properties.org.quartz.jobStore.driverDelegateClassorg.quartz.impl.jdbcjobstore.PostgreSQLDelegate # # 数据同步任务每5分钟执行一次 # wms.job.stock-sync.cron: 0 0/5 * * * ?找到了wms.job.stock-sync.cron这个配置项。但问题来了0 0/5 * * * ?是标准Quartz表达式意思是“每5分钟触发一次”可为什么故障只在上午10:15发生我立刻意识到这个cron表达式可能被动态覆盖了或者有其他条件触发。我让开发同事导出Quartz的数据库表qrtz_triggers果然发现一条记录SELECT TRIGGER_NAME, CRON_EXPRESSION, NEXT_FIRE_TIME FROM qrtz_triggers WHERE TRIGGER_NAME LIKE %stock%; -- 结果 -- stock-full-sync-trigger | 0 15 10 * * ? | 1716200100000 (对应2024-05-20 10:15:00)原来这个任务有两个触发器一个是每5分钟的基础同步另一个是每天10:15的全量同步。全量同步的任务逻辑正是读取那个1.2GB的CSV文件做全量库存导入。而基础同步任务因为数据量小一直没暴露问题。这个细节光看配置文件是发现不了的必须查数据库。这就是为什么我说排障不能只看表面配置要深入到任务调度框架的存储层。2.4 第四层任务逻辑本身有没有“地雷”代码级审查定位到任务后下一步是看它到底在干什么。我让开发把StockFullSyncJob类的代码发给我。核心逻辑是public void execute(JobExecutionContext context) { // 1. 从FTP服务器下载最新CSV String csvPath downloadFromFTP(stock_full.csv); // 2. 逐行读取CSV解析后插入数据库 try (BufferedReader reader new BufferedReader(new FileReader(csvPath))) { String line; while ((line reader.readLine()) ! null) { // 这里是性能杀手 StockData data parseLine(line); stockMapper.insert(data); // 单条插入没批量 } } }问题一目了然BufferedReader.readLine()在读取1.2GB文件时会频繁触发GC且单条插入数据库网络往返和事务开销巨大。更致命的是downloadFromFTP方法没有超时控制如果FTP服务器响应慢整个任务就会卡住导致后续所有任务堆积。我立刻让开发加了三重防护用Files.lines(Paths.get(csvPath))替代BufferedReader利用Stream API的惰性求值批量插入每1000条提交一次事务downloadFromFTP加上connectTimeout30000和readTimeout60000。但这只是治标。真正治本是把这个全量同步任务从“每天一次”改成“只在数据源变更时触发”通过监听FTP目录的inotifywait事件来驱动。这才是从根源上消除定时任务的盲目性。3. 定时任务排障的“黄金工具链”——不是命令越多越好是组合最准网上教程动辄列出20个命令但实战中真正高频、精准、不可替代的就那么几个。我把它们按“发现问题”、“定位源头”、“验证修复”三个阶段打包成工具链每个都附上真实场景下的参数详解和避坑点。记住工具是死的思路是活的参数选错效果差十倍。3.1 发现问题pidstatiotopdmesg—— 三位一体看IO真相pidstat我已经介绍过它是资源消耗的“体温计”。但光知道“热”不够得知道“热源在哪”。这时候iotop就是X光机# 必须用root权限否则看不到所有进程 sudo iotop -o -b -n 1 # 关键列解读 # TID线程ID注意不是PID # PRIOIO优先级负数表示高优先级 # READ_RATE / WRITE_RATE实时IO速率 # COMMAND线程命令名常显示为java或python那天iotop输出里TID为12346的线程READ_RATE稳定在80MB/sCOMMAND显示java -Djava...这和pidstat里java进程的高%iowait完全吻合。但iotop有个致命缺陷它只显示实时速率不显示历史累计IO。所以当我想确认“这个线程是不是从10:15开始就一直在读”就得用dmesg查内核日志# 查看最近100行内核日志过滤IO相关 dmesg -T | grep -i io\|disk\|ata\|nvme | tail -100 # 输出里有一行 # [Mon May 20 10:15:03 2024] ata1: exception Emask 0x0 SAct 0x0 SErr 0x0 action 0x0 # 这说明硬盘在10:15:03有异常但不是错误是高负载下的正常日志。dmesg的价值在于它能告诉你IO压力是否触发了内核级别的调度调整。比如如果看到cfqCompletely Fair Queuing调度器被激活的日志就说明IO队列已经拥堵需要优化IO策略。提示iotop默认刷新间隔是1秒但在高IO场景下1秒太长会错过峰值。用-d 0.5可以设为0.5秒刷新但会增加CPU开销建议只在排查时临时使用。3.2 定位源头lsofstracejournalctl—— 穿透进程看文件与系统调用lsof是“谁在用什么文件”的终极答案。但有时lsof显示的文件路径是符号链接或者文件已被删除但句柄还在deleted状态这时候就需要strace# 跟踪java进程12345的系统调用只关注文件IO sudo strace -p 12345 -e traceopen,openat,read,write,close -s 256 -o /tmp/strace.log 21 # 等待10秒然后kill %1停止跟踪 # 查看log找read系统调用的文件描述符 grep read( /tmp/strace.log | head -10 # 输出类似 # read(15, SKU001,100,InStock\nSKU002,200,......, 8192) 8192read(15, ...)里的15就是文件描述符再用lsof -p 12345 | grep 15u就能找到这个fd对应的文件。那天我们就是这么确认fd 15指向的就是那个1.2GB的CSV文件。对于systemd管理的服务比如你用systemctl start myapp启动的journalctl比tail -f /var/log/myapp.log更可靠因为它能捕获标准输出、标准错误甚至内核消息# 查看myapp服务最近1小时的日志按时间倒序 journalctl -u myapp.service --since 1 hour ago -o short-iso | tail -50 # 关键技巧用_GID过滤只看特定用户组的日志比如所有定时任务都用wms用户运行 journalctl _GID1001 --since 2024-05-20 10:10:00 --until 2024-05-20 10:20:00注意journalctl默认只保存最近三天日志生产环境务必修改/etc/systemd/journald.conf设置SystemMaxUse2G和MaxRetentionSec3month否则排障时日志早没了。3.3 验证修复sarcrontab -lcurl健康检查 —— 用数据说话修复后不能只说“好了”要用数据证明。sarSystem Activity Reporter是sysstat包里的历史性能库它每10分钟自动采集一次系统指标存在/var/log/sa/下# 查看昨天同一时段的CPU和IO统计sar -u CPU, sar -b IO sar -u -f /var/log/sa/sa20 | grep 10:1[0-5] sar -b -f /var/log/sa/sa20 | grep 10:1[0-5] # 修复前的数据 # 10:15:01 AM 12.34 65.43 10.21 0.00 0.00 12.02 # 修复后的数据 # 10:15:01 AM 3.21 2.15 1.02 0.00 0.00 3.38 # %iowait从65%降到2%这才是硬指标。同时用crontab -l确认任务配置已更新用curl做端到端验证# 模拟定时任务触发后的业务效果 curl -s http://wms-api:8081/api/v1/health/stock-sync | jq .status # 返回SUCCESS才算真正闭环。4. 定时任务的“七宗罪”与防御清单——来自血泪教训的12条实操守则排障的终点不是修复一个bug而是建立一套防御体系。我把过去十年踩过的坑总结成定时任务的“七宗罪”每一条都配了可落地的防御守则。这些不是理论是我在麒麟系统、阿里云ECS、本地VMware虚拟机上用血换来的经验。4.1 罪之一cron表达式写错任务“假死”不报错最常见的错误是0 0 12 * * ?Quartz和0 0 12 * * *cron混用。前者是6字段秒分时日月周后者是5字段分时日月周少一个字段Quartz就认为表达式无效任务永远不会触发但日志里只有一行WARN根本不起眼。防御守则1所有cron表达式必须用在线校验器二次验证推荐工具https://crontab.guru/针对标准cronQuartz专用https://www.freeformatter.com/cron-expression-generator-quartz.html实操把application.yml里的wms.job.stock-sync.cron粘贴进去确认它显示的“Next execution”时间符合预期。比如0 15 10 * * ?应该显示“Next: Today at 10:15 AM”。防御守则2在任务执行方法里第一行强制打日志Slf4j Component public class StockSyncJob { Scheduled(cron ${wms.job.stock-sync.cron}) public void syncStock() { log.info(【定时任务启动】StockSyncJob 开始执行当前时间{}, LocalDateTime.now()); // 后续逻辑... } }这样哪怕任务没触发日志里也会缺这条记录运维一眼就能发现。4.2 罪之二任务没加超时一个失败拖垮全局那个FTP下载没超时的例子就是典型。任务卡住Quartz线程池被占满其他所有任务排队等待形成雪崩。防御守则3所有外部依赖HTTP、FTP、DB必须设超时// FTPClient示例 FTPClient ftp new FTPClient(); ftp.setConnectTimeout(30000); // 连接超时30秒 ftp.setDataTimeout(60000); // 数据传输超时60秒 ftp.setSoTimeout(60000); // Socket读超时60秒 // Spring RestTemplate示例 RestTemplate restTemplate new RestTemplate(); HttpComponentsClientHttpRequestFactory factory new HttpComponentsClientHttpRequestFactory(); factory.setConnectTimeout(5000); factory.setReadTimeout(10000); restTemplate.setRequestFactory(factory);防御守则4任务方法用Async或独立线程池隔离// 不要这样共用Web线程池 Scheduled(cron 0 */5 * * * ?) public void badSync() { ... } // 要这样用专用线程池 Scheduled(cron 0 */5 * * * ?) Async(taskExecutor) // 指向配置好的线程池 public void goodSync() { ... } // 配置线程池 Bean(taskExecutor) public Executor taskExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); executor.setCorePoolSize(2); // 核心线程数2防止单个任务占满 executor.setMaxPoolSize(5); // 最大5留余量 executor.setQueueCapacity(10); // 队列容量10超了就拒绝 executor.setThreadNamePrefix(async-task-); return executor; }4.3 罪之三日志没分级关键信息被淹没任务里log.info(处理完成)和log.error(FTP连接失败, e)混在一起当任务每5分钟执行一次一天就是288条INFO日志错误日志埋在里面肉眼根本找不到。防御守则5为定时任务单独配置Logback日志文件!-- logback-spring.xml -- appender nameTASK_FILE classch.qos.logback.core.rolling.RollingFileAppender filelogs/task.log/file rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicy fileNamePatternlogs/task.%d{yyyy-MM-dd}.%i.log/fileNamePattern timeBasedFileNamingAndTriggeringPolicy classch.qos.logback.core.rolling.SizeAndTimeBasedFNATP maxFileSize100MB/maxFileSize /timeBasedFileNamingAndTriggeringPolicy /rollingPolicy encoder pattern%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n/pattern /encoder /appender logger namecom.example.wms.job levelINFO additivityfalse appender-ref refTASK_FILE/ /logger这样所有定时任务日志都在task.log里grep ERROR logs/task.log就能直达问题。防御守则6关键步骤加“耗时埋点”long start System.currentTimeMillis(); log.info(【任务启动】开始下载FTP文件); String csvPath downloadFromFTP(stock.csv); log.info(【任务耗时】FTP下载完成耗时{}ms, System.currentTimeMillis() - start); start System.currentTimeMillis(); log.info(【任务启动】开始解析CSV); parseAndInsert(csvPath); log.info(【任务耗时】CSV解析完成耗时{}ms, System.currentTimeMillis() - start);当task.log里出现“FTP下载完成耗时120000ms”你就知道该查FTP了。4.4 罪之四没做幂等重复执行引发数据错乱全量同步任务如果被意外触发两次数据库里就会有重复库存记录WMS系统直接崩。防御守则7所有写操作前先加分布式锁// 用Redis实现简单锁 public boolean tryLock(String lockKey, long expireSeconds) { String value UUID.randomUUID().toString(); Boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, value, expireSeconds, TimeUnit.SECONDS); return locked ! null locked; } // 在任务开头加锁 if (!tryLock(stock-full-sync-lock, 3600)) { log.warn(【任务拒绝】全量同步锁已被占用本次跳过); return; }防御守则8任务状态表唯一索引防重CREATE TABLE job_execution_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, job_name VARCHAR(100) NOT NULL, trigger_time DATETIME NOT NULL, status ENUM(STARTED, SUCCESS, FAILED) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_job_time (job_name, trigger_time) );每次任务执行前先INSERT INTO job_execution_log (job_name, trigger_time, status) VALUES (stock-full-sync, NOW(), STARTED)如果唯一索引冲突说明已执行直接return。4.5 罪之五资源没限制一个任务吃光整机那个1.2GB CSV读取就是典型的内存和IO失控。BufferedReader默认缓冲区8KB读1.2GB要分配15万次缓冲区GC压力山大。防御守则9JVM启动参数强制限制内存# 启动脚本里必须指定-Xmx和-Xms java -Xms512m -Xmx2g -XX:UseG1GC -jar wms.jar-Xmx2g是底线再小Quartz线程池和业务代码就抢内存了。防御守则10用cgroups限制单个进程的IO和CPU适用于容器或物理机# 创建一个cgroup限制java进程的IO带宽为50MB/s sudo cgcreate -g blkio:/wms-job echo 8:0 52428800 | sudo tee /sys/fs/cgroup/blkio/wms-job/blkio.weight_device # 把java进程加入cgroup echo 12345 | sudo tee /sys/fs/cgroup/blkio/wms-job/cgroup.procs这样就算任务逻辑有缺陷最多也只能吃掉50MB/s的IO不会拖垮整台服务器。4.6 罪之六没做监控告警问题发生后才被动响应靠人盯监控太原始。我们必须让系统自己“喊疼”。防御守则11为每个定时任务配置Prometheus指标// Micrometer Prometheus Component public class JobMetrics { private final Counter successCounter Counter.builder(job.success) .description(Job execution success count) .register(Metrics.globalRegistry); private final Timer executionTimer Timer.builder(job.execution.time) .description(Job execution time in seconds) .register(Metrics.globalRegistry); public void recordSuccess(String jobName) { successCounter.tag(job, jobName).increment(); } public void recordExecutionTime(String jobName, long durationMs) { executionTimer.tag(job, jobName).record(durationMs, TimeUnit.MILLISECONDS); } }然后在/actuator/prometheus端点里就能看到job_execution_time_seconds_count{jobstock-full-sync}这样的指标用Grafana画图设置告警如果rate(job_execution_time_seconds_sum[1h]) / rate(job_execution_time_seconds_count[1h]) 300平均耗时超5分钟就发企业微信告警。4.7 罪之七没做灰度发布新任务直接上生产新写的定时任务第一版就部署到生产风险极大。防御守则12所有新定时任务必须走“三步灰度”Step 1本地开发机验证用Profile(dev)注解只在开发环境生效且cron设为0 * * * * ?每分钟一次快速验证逻辑。Step 2测试环境全量跑在测试环境用真实数据源跑满24小时观察task.log和jstat输出。Step 3生产环境“单实例低频”首发先在一台服务器上部署cron改为0 15 10 * * 1-5工作日10:15观察一周无异常再推全量。5. 常见问题速查表与独家避坑技巧——那些文档里不会写的细节最后我把排障过程中最常遇到的10个“ WTF”问题整理成速查表。每个问题都附上真实原因、排查命令、和一句“我试过最有效的解决办法”。问题现象可能原因关键排查命令我的独家技巧top里CPU很高但pidstat -u显示%usr很低进程在等IO不是真正在计算pidstat -u -r -w 1看%iowait列5%就查iotopcrontab -e改了但任务没按新时间执行crond服务没重载或用户crontab没生效sudo systemctl restart crondcrontab -l确认内容改完立刻执行sudo systemctl status crond看日志里有没有RELOAD字样SpringBootScheduled不执行日志也没报错EnableScheduling注解没加或类没被Spring管理grep -r EnableScheduling src/grep -r Component src/在启动日志里搜Scheduling应该有Started Scheduler字样任务执行很快但数据库里数据没更新事务没提交或用了Transactional但方法是privategrep -r Transactional src/检查方法访问修饰符把方法改成public或用TransactionTemplate手动控制事务lsof显示文件deleted但任务还在读文件被rm删除但进程还持有句柄空间没释放lsof -p PID | grep deleted重启对应进程或用echo /proc/PID/fd/FD_NUM清空句柄高危慎用strace输出太多找不到关键系统调用默认跟踪所有系统调用噪音太大strace -p PID -e traceopen,read,write,close加-s 256显示完整字符串加-o file.log重定向到文件journalctl查不到定时任务日志日志级别设太高或服务没用systemd管理journalctl -u service-name -o verbosesystemctl cat service-name在/etc/systemd/system/service-name.service里确认StandardOutputjournalsar数据里IO等待很高但iotop没看到大IO进程IO发生在内核态比如RAID卡重建、SSD垃圾回收iostat -x 1 5cat /proc/diskstats看iostat的%util列100%说明设备饱和不是进程问题国产麒麟系统里cron不执行但CentOS上正常麒麟默认禁用cron或SELinux策略不同sudo systemctl status cronsudo setsebool -P cron_can_network on麒麟系统务必执行sudo systemctl enable cron sudo systemctl start cron虚拟机里Ubuntu的定时任务总比物理机慢1分钟虚拟机时间不同步导致cron触发时间偏移timedatectl statussudo ntpdate pool.ntp.org在VMware设置里勾选“启用客户机时间同步”并用chrony替代ntpd实操心得我曾经为一个Scheduled任务调试了3小时最后发现是cron表达式里用了中文逗号“”而不是英文逗号“,”IDEA没报错但Spring解析时直接忽略整个表达式。从此我养成习惯所有定时任务配置一律用vim打开用:set list显示不可见字符确保标点全是ASCII。6. 写在最后排障不是技术是建立对系统的“肌肉记忆”两小时定位那个定时任务听起来很快但背后是十年积累的“肌肉记忆”看到%iowait高手指就自动敲出iotop看到readBytes脑子就跳出BufferedReader的缓冲区陷阱看到qrtz_triggers表就知道要去查数据库而不是配置文件。这种直觉
返回列表