某次逛论坛,发现使用Systemd管理的Linux发行版,应该使用Systemd Timers管理定时任务,并替代原来的Cron Jobs。
1. Systemd Units
Systemd Units(单元),是System的最小功能单位。其按功能划分为12种类型,例如target、service、timer等。“单元”之间互相依赖和调用,组成Systemd这个任务管理系统。
System Units存放路径:
/usr/lib/systemd/system,系统默认的单元文件/etc/systemd/system,用户安装的软件的单元文件
注意:
- Debian 12中,
/usr/lib映射到/lib,所以/lib/systemd/system与/usr/lib/systemd/system的文件相同。 - Systemd读取Unit文件时,先读
/usr/lib/systemd/system,再读/etc/systemd/system。所以修改原来的Unit文件,或者自定义的Unit文件,建议放在/etc/systemd/system。 - 编辑Unit文件,建议使用命令
systemctl edit --full xxx.service,保存后会自动reload。
配置Systemd定时器时,需要创建两个Unit:
xxx.service,service类型,用于配置需要执行的任务。如果该任务已配置,则不用创建此文件。xxx.timer,timer类型,对xxx.service执行定时触发。
2. 创建Systemd Service
对于Cron Job,会把定时执行的脚本或命令,与定时配置写在一起。配置是简单直接,但是不好管理。Systemd需要把定时执行的任务,配置为oneshot的Service。
例如创建文件/etc/systemd/system/sample-job.service:
[Unit]
Description=sample job
[Service]
Type=oneshot
ExecStart=/bin/bash /path/to/sample-job.sh
[Install]
WantedBy=multi-user.target
注意:
- Type=oneshot,表示只运行一次,不用长期运行。
- WantedBy,配置了多用户命令状态时,才能执行。
3. 创建Systemd Timer
一般可分为指定“时间点”和“时间间隔”两种触发方式。详见:systemd.timer
- 指定时间点,只需配置触发的一个或多个时间点。
OnCalendar=,设置日期时间触发。
- 指定时间间隔,需要配置第一次触发和往后的周期触发。
OnBootSec=,机器启动后多久触发。在容器中,对于系统管理器实例,这被映射到OnStartupSec=,使两者等效。OnStartupSec=,服务管理器首次启动后多久触发。类似于OnBootSec=。它主要适用于在每个用户服务管理器中运行的单元,因为用户服务管理器通常只在首次登录时启动,而不是在启动过程中启动。OnActiveSec=,定时器单元本身被激活后多久触发。OnUnitActiveSec=,在Unit=的上次激活后多久触发。可实现服务多久执行一次。OnUnitInactiveSec=,在Unit=的上次停用后多久触发。可实现服务完成后隔多久才会再次执行。
指定“时间点”的简单示例,例如创建文件/etc/systemd/system/sample-job.timer:
[Unit]
Description=Auto run sample job
[Timer]
OnCalendar=Mon,Thu *-*-* 01:23:49
Unit=sample-job.service
[Install]
WantedBy=multi-user.target
注意:
- Unit,配置对应的Service。
- OnCalendar,按配置的时间点(例如指定日期时间)运行。格式是
DOW yyyy-MM-dd HH:mm:ss,其中DOW可以是Mon,Fri(周一和周五这2天)或者Mon..Fri(周一到周五这5天)- 详细参考:https://www.freedesktop.org/software/systemd/man/latest/systemd.time.html#Calendar%20Events
4. 管理与维护
4.1. 管理命令
# 启动定时器
sudo systemctl start sample-job.timer
# 停止定时器
sudo systemctl stop sample-job.timer
# 查看定时器状态
systemctl status sample-job.timer
# 列出正在运行的定时器
systemctl list-timers
# 设置开机启动
sudo systemctl enable sample-job.timer
# 取消开启启动
sudo systemctl disable sample-job.timer
4.2. 触发问题
对于指点“时间间隔”,记得配置第一次触发,比如开机后多久触发,然后才能周期性触发。
对于指定“时间点”,当前时间满足配置的时间条件则触发,但是要注意错过触发后的“catch up”处理。例如原来设置“10:00:00”触发,在“09:55:00”停止并改为“11:00:00”触发,在“10:10:00”启用。此时检测到上次错过触发,就会在启动后立刻触发。
关于OnCalendar=的“catch up”的解决方案:
[Timer]下添加配置Persistent=true,把上次的触发时间持久化。- 该设置生效后,会在
/var/lib/systemd/timers下创建定时器对应的上次触发时间文件。例如定时器sample-job.timer对应stamp-sample-job.timer。 - 定时器触发后,会把触发时间记录为该文件的修改时间。
- 该设置生效后,会在
- 配置了
OnCalendar=的定时器,修改了“时间点”,在启用前,先更新对应的上次触发时间文件的修改时间。- 例如
sudo touch /var/lib/systemd/timers/stamp-sample-job.timer
- 例如
- 启用修改过的定时器后,该定时器会检测到最近已触发过,不会执行“catch up”。
注意,如果需要限制指定服务的触发次数,比如一天只能触发一次,最好不要依赖定时器,而是业务代码自己处理。例如触发后自己记录一下运行时间,再次触发后检查上次运行时间,并决定是否运行。
4.3. 手工触发
与其它业务集成时,需要根据不同的事件进行触发,但这不是定时器的作用。手工触发时,应该是直接运行对应的service。
Systemd运行service需要root权限。需要避开root权限,可以使用path触发。创建与service同名的path文件,配置其监控指定文件的变化(包括修改时间),触发对应service。
在/etc/systemd/system创建对应的path文件,例如/etc/systemd/system/sample-job.path,内容如下:
[Unit]
Description=Watch to run sample job
[Path]
# 监控指定文件的变化
PathChanged=/run/watch-sample-job
# 监控指定路径的变化,例如该路径下新增了文件
#PathChanged=/path/to/watch/%f
[Install]
WantedBy=multi-user.target
/run/watch-sample-job配置任何用户可修改。需要触发时,执行touch /run/watch-sample-job即可。