不少站点的問题不是出在白天,而是出在凌晨三点:定时任務跑了一半,队列堆了几十萬條,資料库连接被占满,第二天早上訪客打開頁面看到的就是 502。這類故障排查起来很花時間,因為触發它的脚本平时看起来「一直都没事」。花半小时把定时任務和後台队列過一遍,成本很低。
先列出所有會自動跑的東西
自查第一步不是優化,而是搞清楚到底有谁在跑。常见的入口有這几處:
- 系統的 crontab,包括 root 和各站点用戶各自的;
- systemd timer,容易被忽略,因為它不在 crontab -l 的輸出里;
- 框架自带的任務調度,比如定时命令、計划任務配置;
- 常驻的队列消費者進程,通常由 supervisor、pm2 一類工具托管;
- 資料库自身的定时事件、备份脚本、日誌轮轉。
把這些列成一張表,寫清楚:做什么、多久跑一次、大概跑多久、跑的时候占用多少 CPU 和磁盘 IO。很多人第一次做完這張表就會發現,有两三個任務其實是重复的。
最容易踩的四個坑
任務重叠执行
一個任務每 5 分钟跑一次,但偶尔要跑 8 分钟,于是第二轮在上一次還没結束时又啟動了,两邊同时改同一批資料。解决办法是加鎖:用文件鎖、Redis 鎖或資料库标记,拿不到鎖就直接登出,並且记一條日誌說明這次被跳過了。
挑错了時間窗口
全量重建索引、生成站点地图、批量压缩图片這類重活,如果排在訪問高峰,CPU 和磁盘 IO 被抢走,頁面响應就會變慢。把它們挪到流量低谷,並且给任務设一個明确的結束時間,超时就中断,不要放任它一直跑。
失敗没人知道
定时任務失敗最典型的表現是「安静」。脚本报错登出, cron 把错誤邮件發到一個没人看的信箱,然後就没有然後了。至少要做到三件事:非零登出碼寫進日誌、连續失敗触發告警、每天早上一封匯總。
队列只涨不消
队列堆积通常有两個原因:生产者寫入速度超過消費者處理速度,或者消費者因為一條坏消息卡住反复重试。要盯的是队列長度随時間的變化曲线,而不是某一时刻的數字。
队列相關的几個參數
- 並發數:調大不一定更快,先確認下游資料库能不能承受;
- 重试次數與退避:無限制重试會把一條坏消息放大成几百次請求;
- 可见性超时:设得太短,同一條消息可能被多個消費者重复處理;
- 死信队列:處理不了的消息要有地方去,而不是被直接丢掉;
- 幂等:任務重跑一遍,不應该产生重复資料。
一份可执行的检查清單
- 列出全部定时任務,标注频率、耗时和资源占用;
- 给每一個會寫資料的任務加鎖並做幂等處理;
- 把重任務移出訪問高峰时段;
- 確認失敗有日誌、有告警、有人负责看;
- 监控队列長度和消費者進程的存活狀態;
- 定期清理僵尸進程和過期的鎖文件;
- 每季度复查一次,任務會随着业務增長悄悄變重。
定时任務和队列不需要做得多复杂,但需要被看见。能在出問题的十分钟内發現它,比事後寫一份复盘报告划算得多。