systemd 教程 / Timer 定时任务
Timer 定时任务
一、Timer 简介
systemd Timer 是 cron 的现代替代方案。Timer 与 Service 配合使用——Timer 负责调度,Service 负责执行。
1.1 Timer vs crontab
| 特性 | systemd Timer | crontab |
|---|---|---|
| 日志集成 | ✅ journalctl | 需要 MAILTO 或重定向 |
| 依赖管理 | ✅ 与其他 Unit 集成 | ❌ |
| 错误处理 | ✅ Restart 策略 | ❌ |
| 错过执行 | ✅ Catch-up(Persistent) | ❌ |
| 随机延迟 | ✅ RandomizedDelaySec | ❌ |
| 资源限制 | ✅ CGroup 集成 | ❌ |
| 调试 | ✅ systemctl status | 需手动排查 |
| 精度 | 秒级 | 分钟级 |
二、Timer Unit 文件
Timer 需要两个文件:.timer(调度器)和 .service(执行器),两者名称必须一致。
Timer 文件 (/etc/systemd/system/mytask.timer):
[Unit]
Description=My Scheduled Task Timer
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.target
Service 文件 (/etc/systemd/system/mytask.service):
[Unit]
Description=My Scheduled Task
[Service]
Type=oneshot
ExecStart=/usr/local/bin/mytask.sh
User=myuser
# 启用并启动 Timer
systemctl enable --now mytask.timer
# 查看 Timer 状态
systemctl list-timers mytask.timer
三、[Timer] 段详解
3.1 时间触发器
| 指令 | 说明 |
|---|---|
OnCalendar | 日历事件触发 |
OnBootSec | 系统启动后延迟 |
OnStartupSec | systemd 启动后延迟 |
OnUnitActiveSec | 上次激活后间隔 |
OnUnitInactiveSec | 上次停用后间隔 |
3.2 OnCalendar 日历事件
格式:星期 年-月-日 时:分:秒
| 表达式 | 说明 |
|---|---|
*-*-* *:*:00 | 每分钟 |
*-*-* *:00:00 | 每小时 |
*-*-* 02:00:00 | 每天凌晨 2 点 |
Mon *-*-* 09:00:00 | 每周一上午 9 点 |
*-*-01 00:00:00 | 每月 1 号 |
Mon..Fri *-*-* 09:00:00 | 工作日上午 9 点 |
*~07 00:00:00 | 每月最后一天 |
# 验证日历表达式的下次触发时间
systemd-analyze calendar "Mon *-*-* 09:00:00"
# 输出: Next elapse: Mon 2026-05-12 09:00:00 CST
# 显示多次迭代
systemd-analyze calendar --iterations=5 "*-*-* 02:00:00"
3.3 相对时间触发器
[Timer]
OnBootSec=5min # 系统启动后 5 分钟
OnStartupSec=1min # systemd 启动后 1 分钟
OnUnitActiveSec=1h # 上次激活后 1 小时
OnUnitInactiveSec=30min # 上次停用后 30 分钟
| 指令 | 计时起点 |
|---|---|
OnBootSec | 内核启动完成 |
OnStartupSec | systemd 启动完成 |
OnUnitActiveSec | 同名 Service 变为 active |
OnUnitInactiveSec | 同名 Service 变为 inactive |
⚠️ 注意:OnBootSec 和 OnStartupSec 在系统运行足够长时间后不再触发,除非配合 Persistent=true 或再次重启。不要将它们与 OnCalendar 混用——两者是独立的触发器,任一满足即执行。
3.4 其他 Timer 指令
[Timer]
AccuracySec=1s # 调度精度(默认 1min)
RandomizedDelaySec=30min # 随机延迟上限
Persistent=true # 错过时是否补执行
Unit=my-other.service # 指定目标 Service(默认同名)
四、Persistent 与 Catch-up
如果系统在计划执行时间处于关机状态,开机后的行为:
| Persistent 值 | 行为 |
|---|---|
no(默认) | 不补执行,等待下次计划时间 |
yes | 开机后立即补执行一次 |
💡 提示:每日备份、日志清理等任务建议设置 Persistent=true。
五、RandomizedDelaySec 随机延迟
集群中避免同时执行造成资源峰值:
[Timer]
OnCalendar=*-*-* 02:00:00
RandomizedDelaySec=30min # 任务在 02:00 到 02:30 之间随机执行
AccuracySec=1s
六、查看与管理 Timer
# 列出所有 Timer
systemctl list-timers
# 包括未激活的
systemctl list-timers --all
# 启用/禁用
systemctl enable --now mytask.timer
systemctl disable mytask.timer
# 查看 Timer 日志
journalctl -u mytask.timer
# 查看 Service 执行日志
journalctl -u mytask.service -n 50
# 手动触发测试
systemctl start mytask.service
输出示例:
NEXT LEFT LAST PASSED UNIT ACTIVATES
Mon 2026-05-12 02:00:00 CST 15h left Sun 2026-05-11 02:00:00 CST 8h ago backup.timer backup.service
Mon 2026-05-12 03:00:00 CST 16h left Sun 2026-05-11 03:00:00 CST 7h ago logrotate.timer logrotate.service
七、实际案例
7.1 日志清理
# /etc/systemd/system/log-cleanup.timer
[Unit]
Description=Log Cleanup Timer
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=15min
[Install]
WantedBy=timers.target
# /etc/systemd/system/log-cleanup.service
[Unit]
Description=Log Cleanup
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'find /var/log/myapp -name "*.log" -mtime +30 -delete; journalctl --vacuum-time=30d'
StandardOutput=journal
7.2 数据库备份
# /etc/systemd/system/db-backup.timer
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
RandomizedDelaySec=10min
# /etc/systemd/system/db-backup.service
[Unit]
Description=Database Backup
After=postgresql.service
[Service]
Type=oneshot
User=postgres
ExecStart=/usr/local/bin/backup-db.sh
TimeoutStartSec=7200
MemoryMax=1G
CPUQuota=50%
StandardOutput=journal
SyslogIdentifier=db-backup
7.3 每周报告
# /etc/systemd/system/weekly-report.timer
[Timer]
OnCalendar=Mon *-*-* 08:00:00
Persistent=true
7.4 证书续期
# /etc/systemd/system/cert-renew.timer
[Timer]
OnCalendar=*-*-* 04:00:00
Persistent=true
RandomizedDelaySec=1h
# /etc/systemd/system/cert-renew.service
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx"
八、从 crontab 迁移
| crontab | systemd Timer |
|---|---|
0 2 * * * | OnCalendar=*-*-* 02:00:00 |
*/5 * * * * | OnUnitActiveSec=5min |
0 9 * * 1 | OnCalendar=Mon *-*-* 09:00:00 |
0 0 1 * * | OnCalendar=*-*-01 00:00:00 |
@reboot | OnBootSec=0 或 OnStartupSec=0 |
@daily | OnCalendar=*-*-* 00:00:00 |
@weekly | OnCalendar=Mon *-*-* 00:00:00 |
迁移步骤:
# 1. 查看现有 crontab
crontab -l
# 2. 为每个任务创建 Timer + Service
# 3. 启用并验证
systemctl enable --now mytask.timer
journalctl -u mytask.service -f
# 4. 确认无误后删除 crontab 条目
九、调试 Timer
# 1. 检查 Timer 状态
systemctl status mytask.timer
# 2. 检查下次执行时间
systemctl list-timers mytask.timer
# 3. 验证日历表达式
systemd-analyze calendar "*-*-* 02:00:00"
# 4. 查看 Service 执行日志
journalctl -u mytask.service -n 50
# 5. 手动执行测试
systemctl start mytask.service
| 问题 | 原因 | 解决方案 |
|---|---|---|
| Timer 不触发 | 未 enable | systemctl enable mytask.timer |
| Service 不执行 | 名称不匹配 | Timer 和 Service 文件名一致 |
| 日历格式错误 | 语法不对 | systemd-analyze calendar "..." 验证 |
| 错过执行 | Persistent 未设 | 添加 Persistent=true |
| 同时执行 | 无随机延迟 | 添加 RandomizedDelaySec |
十、生产场景
场景 1:分布式备份
[Timer]
OnCalendar=*-*-* 01:00:00
Persistent=true
RandomizedDelaySec=2h # 集群中随机延迟 0-2 小时
场景 2:健康检查(高频)
[Timer]
OnUnitActiveSec=30s # 每 30 秒
AccuracySec=1s
场景 3:月度清理
[Timer]
OnCalendar=*-*-01 04:00:00
Persistent=true
RandomizedDelaySec=30min