图源:
什么是任务计划
而Linux也具备执行定时任务的能力。
当然Windows也可以执行定时任务,但是从我使用的情况来看,还是不要折腾的比较好。
Linux任务计划的种类
Linux上可以通过两种命令(工具)来制定任务计划:
-
at
:可以制定一次性任务,由服务进程atd
负责执行。 -
crontab
:可以制定周期性任务,有服务进程crond
负责执行。
CentOS上常见的例行性工作
CentOS上有一些已经设定好的周期性任务:
-
执行日志文件的轮询(logrotate)
会定时地检查和整理日志文件,将新日志和旧日志分别存放。
-
日志文件分析(logwatch)
程序
logwatch
会定期分析登录相关地日志,并发送标题为logwatch
的邮件。 -
更新locate数据库
之前说过,
locate
命令查询依赖于数据库,所以系统会定期更新locate数据库。 -
更新manpage查询数据库
同样,
man
命令依赖于manpage
查询数据库,所以也需要定期扫描,以将新安装的软件的manpage
信息更新入数据库中。 -
更新PRM数据库
PRM是CentOS的包管理工具,如果不是通过PRM安装的软件,PRM的数据库中是查不到相应信息的,所以同样要定期执行更新操作。
-
删除缓存
随着程序的运行,会基类越来越多的缓存和临时文件,所以需要定期执行缓存的清理工作。
-
与网络服务有关的分析操作
如果安装了网络服务(Apache等),同样会附带一些定时任务。
仅执行一次的计划任务
atd的启动与at运行方式
命令at
的执行依赖于服务进程atd
,所以我们先要确认是否已经启用该服务:
[icexmoon@xyz ~]$ systemctl status atd
● atd.service - Job spooling tools
Loaded: loaded (/usr/lib/systemd/system/atd.service; enabled; vendor preset: enabled)
Active: active (running) since 三 2021-08-25 15:42:49 CST; 1min 1s ago
Main PID: 1264 (atd)
Tasks: 1
CGroup: /system.slice/atd.service
└─1264 /usr/sbin/atd -f
8月 25 15:42:49 xyz.icexmoon.centos systemd[1]: Started Job spooling tools.
systemctl
用于管理服务进程,以后会详细介绍。
这里的Active: active (running)
说明服务已经在正常运行中了,而后边的atd.service; enabled
说明该服务会开机自动启动。
如果进程没有运行,或者不是开机自动启动,可以:
[root@xyz ~]# systemctl restart atd
[root@xyz ~]# systemctl enable atd
[root@xyz ~]# systemctl status atd
● atd.service - Job spooling tools
Loaded: loaded (/usr/lib/systemd/system/atd.service; enabled; vendor preset: enabled)
Active: active (running) since 三 2021-08-25 15:47:48 CST; 18s ago
Main PID: 2116 (atd)
CGroup: /system.slice/atd.service
└─2116 /usr/sbin/atd -f
8月 25 15:47:48 xyz.icexmoon.centos systemd[1]: Started Job spooling tools.
at的运行方式
事实上,当我们通过at
创建一次性定时任务时,会在/var/spool/at/
目录下生成一个包含命令内容的文本文件,服务进程atd
会读取该文件并在合适的时候并执行。
所有的定时任务创建都属于高危权限,所以需要进行权限设置和管理,at
通过两个文件来控制权限:
-
/etc/at.allow
:相当于白名单,只要写入这里的账户都可以执行at
,反之则不能。 -
/etc/at.deny
:相当于黑名单,只要写入这里的账户都不能执行at
,反之则可以。
这里的两个配置文件类似于黑白名单,且白名单的优先级高于黑名单,如果同时两个配置文件都存在,则白名单生效,黑名单不会起作用。
实际运行单一任务计划
最常见的是创建一个若干时间后执行的任务:
[root@xyz ~]# at now + 5 minutes
at> mail -s 'test at' root
at> a email test the at command
at> .
at> <EOT>
job 1 at Wed Aug 25 16:01:00 2021
at
后跟设定的时间,使用Enter
后换行,会出现at>
这个标识符,之后就可以像正常输入Bash
命令那样输入了,输入完毕后需要使用Ctrl+D
以结束输入。
定时任务创建后会输出一行任务相关的信息:job 1 at Wed Aug 25 16:01:00 2021
。其中job 1
是给任务分配了一个at
中的编号,之后的时间就是任务将会执行的时间。
使用任务编号我们可以通过at
查询任务内容:
[root@xyz ~]# at -c 1
# atrun uid=0 gid=0
# mail icexmoon 0
umask 22
XDG_SESSION_ID=1; export XDG_SESSION_ID
...省略...
LESSOPEN=\|\|/usr/bin/lesspipe.sh\ %s; export LESSOPEN
cd /root || {
echo 'Execution directory inaccessible' >&2
exit 1
}
${SHELL:-/bin/sh} << 'marcinDELIMITER0a436847'
mail -s 'test at' root
a email test the at command
.
marcinDELIMITER0a436847
可以看到实际上是创建了一个包含指定命令的Bash
脚本,atd
会以执行该脚本的方式执行命令。
如果想在一个确切的时间点执行任务:
[root@xyz ~]# at 23:00 2021-08-25
at> sync
at> sync
at> shutdown -h now
at> <EOT>
job 2 at Wed Aug 25 23:00:00 2021
通过at
创建的任务将由服务进程atd
执行,所以和用户当前的终端是没有关系的,终端注销或断开连接不会影响到任务的执行,并且任务执行后输出的正常信息和错误信息也不会在当前终端中显示,而是会以邮件的形式发送到创建用户的mailbox中。
如果一定需要将结果输出到当前终端,可以使用stdout重定向:
[root@xyz ~]# finger
Login Name Tty Idle Login Time Office Office Phone Host
icexmoon 魔芋红茶 pts/0 Aug 25 15:43 123456 123456 (icexmoon-book)
[root@xyz ~]# at now + 1 minutes
at> echo 'test at' > /dev/pts/0
at> <EOT>
job 4 at Wed Aug 25 16:24:00 2021
[root@xyz ~]# test at
[root@xyz ~]#
终端
tty1
或pts/0
都以文件的形式存在于/dev/
目录中,所以可以当作文件来重定向输出。如果
at
创建的任务执行后不会产生输出,则也不会有邮件发送给用户,如果无论如何也要发送邮件,可以使用at -m
的方式创建任务。
at任务的管理
如果需要删除已存在的at
任务,可以:
[root@xyz ~]# atq
2 Wed Aug 25 23:00:00 2021 a root
[root@xyz ~]# at -c 2 | less
[root@xyz ~]# atrm 2
[root@xyz ~]# atq
可以先用atq
查询已经存在的at
任务列表,然后使用at -c 2 | less
的方式查看任务的具体内容,如果的确不需要了,可以使用atrm
进行删除。
事实上
atq
和atrm
都是at
命令带指定参数的别名,可以使用man at
查看详情。
batch
batch
实际上也是通过at
来执行定时任务,不同的是通过加入一些控制参数,可以实现特殊的用途,比如:在CPU的任务负载小于0.8时才执行任务。
所谓的CPU任务负载,其实就是同一时间内排队等待CPU执行的任务数,一般来说时间单位是分钟,并且会取最近一段时间的平均值,所以会出现0.8
这样的小数。
下面进行模拟,先通过添加一些后台任务拉高CPU负载:
[root@xyz ~]# echo 'scale=100000; 4*a(1)' | bc -lq &
[1] 2801
[root@xyz ~]# echo 'scale=100000; 4*a(1)' | bc -lq &
[2] 2803
[root@xyz ~]# echo 'scale=100000; 4*a(1)' | bc -lq &
[3] 2805
[root@xyz ~]# echo 'scale=100000; 4*a(1)' | bc -lq &
[4] 2807
命令结尾添加
&
可以让命令在后台执行,相关详细介绍在下篇内容。
过十秒左右使用uptime
命令可以观察到CPU负载升高:
[root@xyz ~]# uptime
16:42:21 up 59 min, 1 user, load average: 2.92, 0.94, 0.37
此时可以使用batch
添加一个CPU低负载时候才会执行的任务:
[root@xyz ~]# batch
at> updatedb
at> <EOT>
job 5 at Wed Aug 25 16:42:00 2021
[root@xyz ~]# date;atq
2021年 08月 25日 星期三 16:42:54 CST
5 Wed Aug 25 16:42:00 2021 b root
可以看到任务执行时间已经过了,但任务还没有被执行。
现在需要杀死后台job以降低CPU负载:
[root@xyz ~]# jobs
[1] 运行中 echo 'scale=100000; 4*a(1)' | bc -lq &
[2] 运行中 echo 'scale=100000; 4*a(1)' | bc -lq &
[3]- 运行中 echo 'scale=100000; 4*a(1)' | bc -lq &
[4]+ 运行中 echo 'scale=100000; 4*a(1)' | bc -lq &
[root@xyz ~]# kill -9 %1 %2 %3 %4
[root@xyz ~]# jobs
[1] 已杀死 echo 'scale=100000; 4*a(1)' | bc -lq
[2] 已杀死 echo 'scale=100000; 4*a(1)' | bc -lq
[3]- 已杀死 echo 'scale=100000; 4*a(1)' | bc -lq
[4]+ 已杀死 echo 'scale=100000; 4*a(1)' | bc -lq
jobs
和kill
等相关命令会在下篇文章进行介绍。
接下来可以不断使用uptime;atq
查看CPU负载和at
任务队列中的任务:
[root@xyz ~]# uptime;atq
16:44:21 up 1:01, 1 user, load average: 2.03, 1.49, 0.65
5 Wed Aug 25 16:42:00 2021 b root
[root@xyz ~]# uptime;atq
16:44:25 up 1:01, 1 user, load average: 1.87, 1.47, 0.65
5 Wed Aug 25 16:42:00 2021 b root
...省略...
[root@xyz ~]# uptime;atq
16:48:21 up 1:05, 1 user, load average: 0.05, 0.68, 0.51
当前一分钟的负载低于0.8时,任务就会被执行。
uptime
显示的三个负载值load average
:分别代表前1分钟、5分钟、15分钟的平均任务负载。
循环执行的计划任务
设置用户计划任务
用户想要创建定期执行的任务(一周、一个月、一年等),可以使用crontab
命令。
因为之前说过的,定时任务创建是一个敏感权限,所以同样的,crontab
也有类似于at
那样的黑白名单权限控制:
-
/etc/cron.allow
:白名单,存在的用户名可以使用crontab
,反之则不能。 -
/etc/cron.deny
:黑名单,存在的用户不可以使用crontab
,反之则可以。
黑白名单同时存在时,黑名单不起作用,这点和at
是一样的。
使用白名单可以看作是严格机制,黑名单则可以看作是一种宽松机制。
实际上用户通过crontab
命令修改的定时任务配置文件是保存在/var/spool/cron
目录下的:
[root@xyz ~]# ll /var/spool/cron/
总用量 4
-rw-------. 1 icexmoon icexmoon 107 8月 25 17:08 icexmoon
虽然该文件的拥有者是当前用户,但是目录/var/spool/cron
本身的权限却是drwx------. 2 root root
,所以实际上用户是没有办法直接修改该文件的,只能通过crontab
命令来间接修改。
设置定时任务也很简单:
[icexmoon@xyz ~]$ crontab -e
#分钟 小时 日 月 周 命令
10 17 * * * mail -s 'crontab test' icexmoon < /home/icexmoon/test.py
时间设定的格式为分钟 小时 日 月 周
,其中分钟的范围为0~59
,小时为0~23
,日为1~31
,月为1~12
,周比较独特,是0~7
,其中0
和7
都表示周日。
还有几个比较特殊的符号可以使用:
-
*
:类似于通配符的用法,可以代表任何值,比如上边的10 17 * * *
就表示任何月任何日的17点10分(其实就是每天的17:10)。 -
,
:可以指定多个值,比如1,5,10,15 * * * *
就可以表示每个小时的1、5、10、15分钟。 -
-
:可以指定一个范围,比如0 12 1-3 * *
就表示每个月的前三天的12点整。 -
/n
:可以表示一个时间间隔,比如0/20 * * * *
就表示每个小时的整点、20分、40分。
/n
的方式其实相当于编程中的整除,也就是说只要能被整除除净的值,都是可以触发任务的合法时间。
要查看当前账户设置的定时任务,可以:
[icexmoon@xyz ~]$ crontab -l
#分钟 小时 日 月 周 命令
10 17 * * * mail -s 'crontab test' icexmoon < /home/icexmoon/test.py
要删除某条任务计划,可以使用crontab -e
直接删除该条记录,或者使用#
注释掉。如果要删除全部的任务计划,可以:
[icexmoon@xyz ~]$ crontab -r
[icexmoon@xyz ~]$ crontab -l
no crontab for icexmoon
设置系统计划任务
如果要设置系统的计划任务,需要修改配置文件/etc/crontab
。
在某些Linux发行版中,服务进程
crond
会将配置文件/etc/crontab
加载到内存中用于执行计划任务,这种情况下修改配置文件/etc/crontab
后并不能让计划任务立即生效,必须重启crond
以重新加载配置文件:systemctl restart crond
。
这个文件内容长这样:
[icexmoon@xyz ~]$ cat /etc/crontab
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
# For details see man 4 crontabs
# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed
SHELL
表示执行命令时使用的shell
,PATH
表示使用的PATH
环境变量,MAILTO
表示将执行结果用邮件发送给哪个用户。再下面就是计划任务的设置了,形式与用户计划任务类似,不过多出一个设置项“user-name”,因为用户计划任务执行的时候肯定是使用该用户的账户身份执行,所以不需要指定账户,但系统计划任务就必须要指定一个账户,作为执行该任务时使用的身份了。
系统相关的计划任务除了可以写入/etc/crontab
配置文件,还可以放在/etc/cron.d
目录下:
[icexmoon@xyz ~]$ ll /etc/cron.d
总用量 12
-rw-r--r--. 1 root root 128 8月 9 2019 0hourly
-rw-r--r--. 1 root root 108 9月 30 2020 raid-check
-rw-------. 1 root root 235 4月 1 2020 sysstat
[icexmoon@xyz ~]$ cat /etc/cron.d/0hourly
# Run the hourly jobs
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
01 * * * * root run-parts /etc/cron.hourly
通过查看0hourly
这个文件可以发现,其实内容和/etc/crontab
是相似的,也就是说我们可以编写同样的配置文件,放在/etc/cron.d
中,同样会被服务进程crond
执行。
此外0hourly
配置文件中的run-parts
命令可以在5分钟之内随机选择一个时间执行气候指定的目录中的可执行文件。也就是说每小时的1~5分钟内,/etc/cron.hourly
目录中的可执行文件会被执行。
所以如果我们有系统相关的shell
脚本需要每个小时执行一次,就可以放置在/etc/cron.hourly
目录中。
类似的,/etc
目录中还有cron.daily
、cron.weekly
、cron.monthly
目录,同样用于放置需要周期性执行的可执行文件,不过它们是由anacron
而非crontab
执行,所以执行机制和效果略区别。
虽然除了
/etc/crontab
配置文件,许多地方都可以放置计划任务,但是依然建议尽量将任务计划集中在/etc/crontab
中集中管理。
特别的,如果你在Linux上开发程序和应用,且需要设置计划任务,则应该在/etc/cron.d
目录下创建配置文件。
一些注意事项
有一些计划任务相关的注意事项:
-
资源分配不均
需要尽量避免在同一个时间点执行大量长时间运行的任务,可以通过合理安排,让计划任务错开时间执行。
-
屏蔽不必要的输出
默认情况下如果任务计划会输出信息,则会发邮件给
MAILTO
指定的账户,如果不希望受到该任务的信息,可以使用stdout重定向,重定向到文件或者/dev/null
,后者将吞掉所有的内容。 -
安全的检验
黑客往往会通过添加计划任务的方式定期手机敏感信息,以进一步加以利用,所以需要检查
/var/log/cron
中的计划任务执行日志来检查是否有非正常用户设定的计划任务被执行。 -
周与日月不可同时使用
周往往应当和模糊日期结合使用,比如“每个星期五”之类的,如果结合确切的日期,比如
9月11日 星期五
这种,可能会造成不可预计的后果。
可唤醒停机期间的工作任务
我们之前说的crontab
方式创建的任务计划,是针对时间点设置的定期执行任务,这存在一个致命问题,即如果到了需要执行任务的时间点,但是Linux主机无法执行任务(比如非正常关机),任务就会错过被执行的机会,只能等到下一个触发的时间点。如果是短时间任务还好说,如果是长时间间隔的任务,比如说每个月执行一次的,就很致命了。
所以我们需要一个即使错过执行时间,也可以在系统重新启动后执行任务的机制。
anacron
就可以做到。
什么是anacron
anacron
存在的意义上边已经说过了,anacron
实际上也是通过crond
进行触发的,每个小时触发一次。
anacron
启动后会根据比对其上次执行的时间来确定是否应该对相关的计划任务进行执行。比如说某个可执行文件存在于/etc/cron.daily
目录下,理论上每天至少应当执行一次,但是Linux主机关机了2天,开机后anacron
被crond
触发,然后检查到自己上次执行时间已经是2天前了,自然会将/etc/cron.daily
下的可执行文件都执行一遍。
anacron 与 /etc/anacrontab
上面已经说过了,anacron
由crond
每小时触发一次,准确地说,是通过/etc/cron.hourly/0anacron
这个脚本触发:
[icexmoon@xyz cron.hourly]$ cat 0anacron
#!/bin/sh
# Check whether 0anacron was run today already
if test -r /var/spool/anacron/cron.daily; then
day=`cat /var/spool/anacron/cron.daily`
fi
if [ `date +%Y%m%d` = "$day" ]; then
exit 0;
fi
# Do not run jobs when on battery power
if test -x /usr/bin/on_ac_power; then
/usr/bin/on_ac_power >/dev/null 2>&1
if test $? -eq 1; then
exit 0
fi
fi
/usr/sbin/anacron -s
这个脚本先会获取上次anacron
的执行时间,如果是同一天内执行的,则直接结束脚本,否则继续检查Linux主机的供电是否正常(on_ac_power
),如果不正常,则结束脚本,如果正常,则执行anacron -s
。
anacron -s
会根据之前的执行时间进一步判断其下的任务计划是否应该被执行。
anacron
的核心配置文件是/etc/anacrontab
:
[icexmoon@xyz cron.hourly]$ sudo cat /etc/anacrontab
# /etc/anacrontab: configuration file for anacron
# See anacron(8) and anacrontab(5) for details.
SHELL=/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
# the maximal random delay added to the base delay of the jobs
RANDOM_DELAY=45 # 随机给予的最大延迟时间
# the jobs will be started during the following hours only
START_HOURS_RANGE=3-22 #随机产生的开始执行小时
#period in days delay in minutes job-identifier command
#天数 延迟时间(分钟) 标识 命令
1 5 cron.daily nice run-parts /etc/cron.daily
7 25 cron.weekly nice run-parts /etc/cron.weekly
@monthly 45 cron.monthly nice run-parts /etc/cron.monthly
anacron
会依据配置中的period in days
这个配置项并结合上次执行的记录决定是否执行相关命令。
上次的相关执行记录可以这样查看:
[icexmoon@xyz cron.hourly]$ sudo more /var/spool/anacron/*
::::::::::::::
/var/spool/anacron/cron.daily
::::::::::::::
20210825
::::::::::::::
/var/spool/anacron/cron.monthly
::::::::::::::
20210729
::::::::::::::
/var/spool/anacron/cron.weekly
::::::::::::::
20210820
这里的记录表明cron.daily
这个标识的anacron
任务上次执行的时间为2021年8月25日,cron.monthly
这个标识的anacron
任务上次执行时间为2021年7月29日,以此类推。
和/etc/anacron
配置文件结合起来就可以决定是否应当执行任务,比如:
cron.daily
这个任务的period in days
是1,而上次执行时间是20210825
,则说明不需要执行该任务。如果上次执行时间是20210824
,则会执行任务,即run-parts /etc/cron.daily
,这意味着/etc/cron.daily
目录下所有的可执行文件都会被执行。
当然,即使任务的确需要执行,anacron
也不会一股脑同时执行,为了避免集中执行任务,就会使用配置文件中delay
这个设置,不同的任务会延迟不同的时间后开始执行。
正因为anacron
具有上边的特性,所以我们可以将“错过时间也应当执行的周期性任务”通过anacron
执行。具体方式即可以简单地将可执行文件放置在/etc/cron.daily
之类的anacron
预定义的目录下,也可以自行编辑/etc/anacron
配置文件,以满足一些个性化的需求。
文章评论