引言

说到 Linux 下定时执行任务,大多数人可能会想到 crontab?没错,它的确是 Linux 下比较通用和方便的方式,但是今天我来介绍一种新的方法来创建定时任务并且支持更多更强大的功能。

Systemd

很多小伙伴应该听说过 Systemd,它是 Linux 自带的系统工具,已经成为大多数发行版的标准配置。它是用来代替 initd 进程的,解决了 initd 进程的一些问题,成为了系统的1 号进程(PID=1)。

Systemd 相比于 crontab,有很多优点:

  • 一个任务可以拆分成多个任务,任务之间可以有依赖关系
  • 方便的查询日志,自带日志工具和命令
  • 可以对任务启用资源限制,例如限制 CPU 和内存的使用

关于 Systemd 的使用和介绍,这里不讲太多,后面再详细讲。今天分享通过 Systemd 来实现定时任务的方法。

一个小例子

先来准备一个小例子:

echo `date` >> /tmp/data.txt

执行脚本,脚本会在 tmp 目录的 data 文件中输出当前的时间,正好可以验证脚本是否在正常运行,别忘了给脚本加可执行权限(chmod +x hello.sh)。

先执行脚本看下效果:

image-20250120224144129

脚本可以正常运行了。

Systemd unit

想要使用 Systemd 实现定时任务,要先创建一个Systemd 单元,Systemd 的单元是 Systemd 中最小功能单位,可以理解为一个进程的描述。一个任务就是一个单元,单元之间可以相互依赖也可以相互调用,多个单元可以组成一个大的任务管理系统。

Systemd 单元有很多种类,最常见的 Service 负责后台服务,Slice 负责资源分配,mount 负责挂载文件系统,Timer 负责定时任务等等。

单元描述符一般在系统中的三个目录中保存,分别是:

  • /usr/lib/systemd/system:用户自己定义的单元描述符文件
  • /etc/systemd/system:用户安装的软件所对应的单元描述符文件
  • /lib/systemd/system:系统自带的单元描述符文件

可以使用 systemctl 命令查看所有的单元描述符文件,也可以单独查看某一个分类的文件。

# 查看所有 Service 单元
systemctl list-unit-files --type service
# 查看所有的 timer 单元
systemctl list-unit-files --type timer
# 查看所有 mount 单元
systemctl list-unit-files --type mount
# 查看所有的单元
systemctl list-unit-files

管理命令

现在有了单元描述符文件后,我们还需要通过 systemctl 命令来管理进程。

常用的命令有:

# 启动进程
systemctl start 单元名
# 停止进程
systemctl stop 单元名
# 查看单元状态
systemctl status 单元名
# 开机自启
systemctl enable 单元名
# 关闭开机自启
systemctl disable 单元名

Service 单元

要想使用 Systemd 来实现定时任务,还需要新建两个文件,一个用来描述单元的基本信息,例如启动命令,执行的路径,单元依赖等等。还有一个文件是用来定时执行任务,类似于 crontab,用来定义什么时候执行任务。

先说 Service 单元,新建一个单元,例如 demo.service,放到 /usr/lib/systemd/system 目录下,内容如下:

[Unit]
Description=my hello.sh

[Service]
ExecStart=/bin/bash /root/hello.sh

先查看一下刚才新建的单元状态:

root@debian:~# systemctl status hello.service 
○ hello.service - hello.sh
     Loaded: loaded (/lib/systemd/system/hello.service; static)
     Active: inactive (dead)

现在是停止的状态,因为我们还没有启动它。执行命令 systemctl start hello.service 启动它。

root@debian:~# systemctl status hello.service 
○ hello.service - hello.sh
     Loaded: loaded (/lib/systemd/system/hello.service; static)
     Active: inactive (dead)
root@debian:~# systemctl start hello.service 
root@debian:~# systemctl status hello.service 
○ hello.service - hello.sh
     Loaded: loaded (/lib/systemd/system/hello.service; static)
     Active: inactive (dead)

Jan 21 08:41:43 debian systemd[1]: Started hello.service - hello.sh.
Jan 21 08:41:43 debian systemd[1]: hello.service: Deactivated successfully.

可以看到, 程序正常启动了,但是后来又停止了,因为它只有一行代码,执行了 echo 之后就停止了,符合预期,下面我们让它来定时执行。

注意:在 ExecStart 字段中的命令要写绝对路径,因为 Systemd 不会自动加载环境变量,不写绝对路径会找不到可执行文件。

Timer 单元

为了可以定时执行任务,还需要新建一个 timer 单元来定义如何定时执行刚才创建的单元。新建一个 hello.timer,还是放到刚才的目录中,内容为:

[Timer]
OnUnitActiveSec=5s # 每隔多久执行任务
AccuracySec=1ms #设置时间精度,如果不设置的话,具体的执行时间就不一定了
Unit=hello.service # 要执行哪个任务

[Install]
WantedBy=multi-user.target

有一个 Install 字段,这个字段在几乎每个单元中都会有,可以说是很常用或者说比较基本的一个字段,它的意思是多用户模式,可以简单理解为只要系统是以多用户模式启动的,那么这个任务就会被定时执行。

现在我们启动定时器看看:

# 别忘了编辑了单元文件后先执行下面的命令重载单元文件,不然可能会不生效
systemctl daemon-reload
# 启动定时器
systemctl start hello.timer

查看一下命令是否正常执行,看下输出的日志:

root@debian:~# cat /tmp/data.txt 

Tue Jan 21 09:17:13 AM EST 2025
Tue Jan 21 09:17:18 AM EST 2025
Tue Jan 21 09:17:23 AM EST 2025
Tue Jan 21 09:17:28 AM EST 2025

发现日志可以正常输出,说明我们新建的单元被正常的定时调度执行了。

开机自启

上面的配置只能保证系统不重启的情况下定时执行,如果要想实现开机自启的话,要记得执行命令:

root@debian:~# systemctl enable hello.timer
Created symlink /etc/systemd/system/multi-user.target.wants/hello.timer → /lib/systemd/system/hello.timer.

它会创建一个软连接到 user.target.wants 目录中,在系统启动的时候自动加载并执行这些任务。

如果想要关闭开机自启的话,执行 systemctl disable hello.timer 命令即可。

查看日志

Systemd 和 crontab 最大的优点我觉得是日志,crontab 只能通过程序打印的日志才能知道它到底有没有执行,Systemd 自带完善的日志系统可以很方便的查看日志。

因为刚才的脚本只输出了时间到文件中,没有打印日志到控制台,所以我们加一行 echo,然后再来查看日志。

echo `date` >> /tmp/data.txt
echo `date`

查看日志使用 journalctl 命令:

journalctl -u hello.service

image-20250121223941914

总结

使用 Systemd 来设置定时任务,虽然上面说了那么多,其实就两件事,写两个文件,一个文件定义需要执行什么命令,另一个文件定义什么时候执行命令,然后记得重载单元文件,然后启动定时器并且设置开机自启即可。

本文章首发于个人博客 LLLibra146’s blog


LLLibra146
35 声望6 粉丝

会修电脑的程序员