第13章 Linux系统定时任务Cron(d)服务应用实践
13.1 Cron(d)介绍
13.1.1 什么是Cron?
Cron是Linux系统中以后台进程模式周期性执行命令或指定程序任务的服务软件。默认情况下,安装完Linux操作系统之后,Cron服务软件便会启动,服务对应的进程名字为Crond,Crond服务会定期(默认每分钟检查一次)检查系统中是否有需要执行的任务工作计划。如果有,则会根据其预先设定的定时任务规则自动执行该定时任务工作。这个Crond定时任务服务就相当于我们平时使用的闹钟(或手机闹钟)。
Linux系统中的Cron定时任务执行的最快频率是每分钟执行一次,因此如果是需要以秒为单位执行的计划任务,Cron就不适用了。面对这种情况,在工作中可以编写Shell脚本,然后作为守护程序执行。以下是一个每秒钟执行一次任务的脚本:
[root@oldboy ~]# cat cron.sh while true do echo "I am oldboy teacher." sleep 1 done
提示:当然也有秒级定时任务人员,但是用途不多,因此,读者不需要过多涉及。
13.1.2 为什么要使用Crond定时任务?
前文讲解过,Linux运维工程师的职责之一就是保障数据安全,那么,其日常工作任务之一就是不断地对数据进行备份,而很多时候白天系统业务繁忙,因此数据备份的工作只能放到晚上执行,例如,网站数据库数据以及用户上传的图片、文件和程序文件等,一般需要每天晚上做一次备份。如果没有定时任务工具,管理员就得每天半夜爬起来,登录到系统中手工执行任务计划,直到执行完才能去睡觉。执行任务的过程可能会持续好几个小时,这样一来,一个晚上管理员就都不用睡觉了,这样显然是不行的。那么有什么办法可以解决这个周期性地执行任务计划的问题呢?
这就需要Linux系统的定时任务Cron工具了,Cron工具可以很好地帮我们完成需要重复的、周期性地、自动备份等运维工作。
13.1.3 不同系统的定时任务种类介绍
1.Windows系统的定时任务
在继续讲解Linux的定时任务之前,我们先来看一看Windows的任务计划,Windows系统也是有定时任务计划工具的。
这里以Windows7系统为例进行说明,选择“开始”→“所有程序”→“附件”→“系统工具”→“任务计划程序”,就会弹出如图13-1所示的窗口。

图13-1 Windows7的任务计划图
图13-2所示的是设定好的,每天晚上自动关机的定时任务。

图13-2 每天晚上自动关机的设置图解
更多信息可见:http://wenku.baidu.com/view/174cb0cc2cc58bd63186bd2a.html
2.Linux系统下的定时任务软件
严格地说,Linux系统下的定时任务软件还真不少,例如,at、Cron和anacron等,下面分别来简单介绍一下。
·at定时任务软件(依赖于atd服务)适合于仅执行一次就结束的调度任务工作。例如,某天夜里需要处理一个工作任务,仅仅是这一天的夜里,对于这种突发性的工作任务,那就可以使用该软件。要使得at这个软件设定的配置生效,还需要提前启动一个名为atd的服务才行,这个工具在工作中的使用需求很少,读者简单了解一下即可。
·Cron定时任务软件依赖于Crond服务。正如前面所说,通过crontab命令可以配置周期性执行的定时任务工作计划,例如,每五分钟做一次服务器时间同步。要使得crontab命令设定的配置生效,还需要事先启动一个服务Crond才行。这个Cron工具(对应的crontab命令以及依赖的Crond服务)就是运维工程师在生产工作中最常使用的工具,请大家务必掌握。
·anacron定时任务软件主要是为非7×24小时开机的服务器准备的,anacron并不能指定具体时间执行任务工作,而是以天为周期或者在系统每次开机后执行任务工作。它会检测服务器停机期间应该执行但是并没有进行的任务工作,并将该任务执行一遍。
3.Linux系统定时任务Cron(d)
在Linux系统中,Cron是定时任务的软件名,Crond是服务进程名,而crontab命令则是用来设置定时任务规则的配置命令。
为了更好地理解和学习Cron定时任务,本书将Cron定时任务人工划分为用户定时任务计划和系统定时任务计划两类,分别阐述如下。
(1)用户定时任务计划
Crond服务在工作时会以分钟为单位查看/var/spool/cron路径下以系统用户名命名的定时任务文件,以确定是否有需要执行的任务计划。如果有,就会将定时任务调度到内存中执行,笔者将这部分文件称为用户定时任务文件,使用crontab命令编辑的文件最终都会以当前用户名作为文件名存在于/var/spool/cron路径下。
如果是系统管理员或某个普通用户定期要做的任务工作,例如每隔5分钟与互联网上的时间服务器进行一次时间同步,每天晚上0点备份网站站点数据以及数据库数据,就可以使用crontab命令配置在/var/spool/cron路径下。
(2)系统定时任务计划
Crond服务在工作时除了查看/var/spool/cron下的定时任务文件之外,还会查看/etc/cron.d目录以及/etc/anacrontab下的文件内容,里面通常是每天、每周或每月需要执行的任务,如果有需要执行的任务则会执行,系统定时任务的路径通常如下:
[root@oldboy ~]# ls -l /etc|grep cron -rw-------. 1 root root 541 Aug 24 2016 anacrontab drwxr-xr-x. 2 root root 4096 Mar 8 09:55 cron.d #<==系统定时任务的目录。 drwxr-xr-x. 2 root root 4096 Mar 8 09:55 cron.daily #<==按天轮询配置的目录。 drwxr-xr-x. 2 root root 4096 Mar 8 09:54 cron.hourly #<==按小时轮询配置的目录。 drwxr-xr-x. 2 root root 4096 Mar 8 09:55 cron.monthly #<==按月轮询配置的目录。 drwxr-xr-x. 2 root root 4096 Sep 27 2011 cron.weekly #<==按周轮询配置的目录。
特别注意:系统路径下的定时任务配置格式与前文讲解的用户定时任务的配置格式是不同的。
Crond服务除了执行用户定时任务计划(/var/spool/cron目录)以外,还会周期性地自动执行与操作系统相关的定时任务工作,例如轮询系统日志、备份系统数据、清理系统缓存等,这些任务无需我们人为干预。示例代码如下:
[oldboy@C64_x86_64 ~]$ ls -l /var/log/messages* #<==Linux系统的日志被自动轮询,以时间结尾。 -rw-------. 1 root root 470941 9月 7 09:06 /var/log/messages -rw-------. 1 root root 77868 8月 24 19:35 /var/log/messages-20130825 -rw-------. 1 root root 153778 8月 31 17:27 /var/log/messages-20130901 [oldboy@C64_x86_64 ~]$ ls -l /var/log/secure* -rw-------. 1 root root 6969 9月 7 09:07 /var/log/secure -rw-------. 1 root root 17640 8月 25 08:25 /var/log/secure-20130825 -rw-------. 1 root root 6494 9月 1 08:52 /var/log/secure-20130901
类似于上述的日志轮询工作就是由系统自身来完成的,不需要系统管理员来设置,命令如下:
[root@oldboy ~]# ll /etc/logrotate.conf /etc/cron.daily/logrotate -rwx------. 1 root root 219 Oct 31 03:12 /etc/cron.daily/logrotate #<==每天执行的日志轮询定时任务配置。 -rw-r--r--. 1 root root 662 Jul 31 2013 /etc/logrotate.conf #<==日志轮询切割 配置文件。
本章讨论的重点是Cron的用户定时任务计划内容,当然,读者也可以自行学习系统任务计划相关的内容,从而让工作中的任务计划按系统任务计划的格式执行也是可以的(此部分内容不是必须的,也不是本文讲解的重点)。
13.2 用户定时任务Cron(d)使用说明
13.2.1 crontab定时任务编辑命令语法
在Linux运维工作中,通常使用crontab命令编辑定时任务,crontab的相关参数及说明如表13-1所示。
表13-1 crontab的相关参数及说明

特别强调:
-i、-r参数在生产中很少使用,另外,使用crontab命令编辑的文件实际上就是在操作“/var/spool/cron/当前用户名”这样的文件。
13.2.2 使用定时任务权限及配置文件说明
表13-2所示的是定时任务软件相关文件及路径说明。
表13-2 定时任务软件相关文件及路径说明

13.2.3 以用户定时任务crontab命令编辑内容格式
默认情况下,待用户建立定时任务规则之后,该规则所记录的对应配置文件将会存在于/var/spool/cron中,其crontab配置文件对应的文件名与登录的用户名应一致,例如,root用户的定时任务配置文件为/var/spool/cron/root。
利用crontab命令编写定时任务的书写格式很简单,规则一般可分为6个段(每个段之间均通过空格来分隔),前5段为时间设定段,第6段为所要执行的命令或脚本任务段。
用户定时任务编写基本格式如下:
01 * * * * cmd 02 4 * * * cmd
提示:
1)cmd为要执行的命令或脚本,例如/bin/sh/server/scripts/oldboy.sh。
2)每个列之间必须要有一个空格,可以存在多个空格。
1.用户定时任务编写语法中时间段的含义
使用crontab命令编辑的用户定时任务中的时间段对应的含义如表13-3所示。
表13-3 crontab命令编辑的用户定时任务时间段说明

时间记忆口诀:分时日月周。取值范围记忆:正常日期时间范围。
2.用户定时任务编写语法中特殊字符的含义
在使用crontab命令编辑的用户定时任务的语法中,除了时间段以外,还会包含很多特殊字符应用,对应的含义如表13-4所示。
表13-4 用户定时任务编写语法中的特殊字符及含义

13.2.4 crontab命令编辑的定时任务依赖服务说明
crontab命令编辑的定时任务依赖于crond服务,下面来看一下它的运行情况,示例代码如下:
[root@oldboyedu ~]# systemctl status crond.service #<==查看Cron定时任务服务启动状态。 ● crond.service - Command Scheduler #下面的enabled说明开机Crond为自启动状态,running为当前启动状态。 Loaded: loaded (/usr/lib/systemd/system/crond.service; enabled; vendor preset: enabled) Active: active (running)since Thu 2020-10-08 01:56:12 CST; 41min ago Main PID: 13189 (crond) CGroup: /system.slice/crond.service └─13189 /usr/sbin/crond -n …省略若干不重要信息… [root@oldboyedu ~]# systemctl restart crond.service #<==定时任务重启命令。
特别强调:
1)在编辑定时任务文件之后,无须重启定时任务,Crond会自动检查更改的变化。
2)当定时任务不能正确执行时,请查看定时任务日志文件(/var/log/cron)以获取故障信息。
13.3 用户定时任务Cron实例说明
下面来看一下用户定时任务Cron的使用实例,示例代码如下:
命令实例1:*/1 * * * * /bin/sh /scripts/data.sh
在本例中,除了数字与命令脚本之外,还使用到了符号“*”,*号的意思是“每一”。
第一列的意思为分钟,特殊符号“/”表示每隔的意思,即表示每隔一分钟执行一次/bin/sh/scripts/data.sh程序。
命令实例2:30 3,12 * * * /bin/sh /scripts/oldboy.sh
在本例中,第一列为30,表示30分钟;第二列为“3,12”,表示3点和12点,此定时任务的意思是每天凌晨3点和中午12点的半点时刻(或描述为每天凌晨3:30和中午12:30)执行一次/scripts/oldboy.sh脚本任务。
命令实例3:30 */6 * * * /bin/sh /scripts/oldboy.sh
在本例中,第一列为30,表示30分钟;第二列“*/6”代表每6个小时,相当于就是6、12、18、24的作用。此定时任务的意思是每隔6个小时的半点时刻执行一次/scripts/oldboy.sh脚本任务。
命令实例4:30 8-18/2 * * * /bin/sh /scripts/oldboy.sh
在本例中,其中的第一列为30,表示30分钟;第二列的“8-18/2”代表在早晨8点到下午18点之间每隔2小时,也相当于是将8、10、12、14、16、18单独列出。
那么,此定时任务的意思就是早晨8点到下午18点之间,每隔2小时的半点时刻执行一次/scripts/oldboy.sh脚本任务。
命令实例5:30 21 * * * /application/apache/bin/apachectl graceful
本例表示每晚的21:30重启Apache。
命令实例6:45 4 1,10,22 * * /application/apache/bin/apachectl graceful
本例表示每月1、10、22日的凌晨4:45分重启一次Apache。
命令实例7:10 1 * * 6,0 /application/apache/bin/apachectl graceful
本例表示每周六、周日的凌晨1:10分重启一次Apache。
命令实例8:0,30 18-23 * * * /application/apache/bin/apachectl graceful
本例表示在每天18:00至23:00之间每隔30分钟重启一次Apache。
命令实例9:00 */1 * * * /application/apache/bin/apachectl graceful
本例表示每隔一小时整点重启一次Apache。
命令实例10:* 23,00-07/1 * * * /application/apache/bin/apachectl graceful
本例并不是表示晚上23点和早上0~7点之间每隔一小时重启一次Apache。
需要说明的是,以上结果是不规范的,也是不对的。大家想想为什么?
以上定时任务的第一列为“*”,表示每分钟都执行一次任务,即晚上23点和早上0~7点之间每一分钟都重启一次Apache,很可怕吧。
命令实例11:00 11 * 4 1-3 /application/apache/bin/apachectl graceful
本例表示4月的每周一到周三的上午11点整重启一次Apache。
命令实例12:30 09 * * 0 去老男孩教育上课
本例表示每周日上午9:30去老男孩教育上课,这是周末班的上课频率。
命令实例13:30 08 * * *去老男孩教育上课
本例表示每天上午8:30去老男孩教育上课,这是脱产班的上课频率。
通过上述13个例子,相信读者已经能够理解定时任务的配置规则了。
13.4 生产环境下用户Cron配置专业实践案例
前文是通过定时任务规则来理解定时任务计划,下文是根据需求来设定定时任务规则的案例,读者可以自己解答一下,看是否搞懂了。
范例13-1:每分钟打印一次oldboy字符串到“/server/log/oldboy.log”中。
新手编辑定时任务时很容易出错,因此,一定要养成一个好的编辑习惯才行,解答本题的详细步骤如下。
第一步:先在命令行实现要处理的任务,这一步很重要,很多新手直接编写定时任务,结果出现了各种错误,在练习场景下还好,若是在工作中,可能就会错过任务执行时间了。在命令行下进行测试的步骤如下:
[root@oldboy ~]# echo oldboy>> /server/log/oldboy.log #<==命令行执行,将old-boy追加到文件里。 -bash: /server/log/oldboy.log: No such file or directory #<==报错,提示没有文件或目录。 [root@oldboy ~]# mkdir -p /server/log #<==创建对应不存在的目录。 [root@oldboy ~]# echo oldboy>> /server/log/oldboy.log #<==重新在命令行执行。 [root@oldboy ~]# cat /server/log/oldboy.log #<==查看执行后的结果。 oldboy
第二步:在Linux命令行执行crontab-e。进入vi编辑文本的状态之后,输入如下内容:
#print my name to log by oldboy at 201805 #<==这一行为注释。 * * * * * echo oldboy>> /server/log/oldboy.log #<==这个命令应该是复制,不是从头书写。
第三步:保存,查看编辑完的定时任务配置。以下是具体命令:
[root@oldboy ~]# crontab -l|head -2 #print my name to log by oldboy at 201805 * * * * * echo oldboy>> /server/log/oldboy.log
第四步:观察生效情况。以下是具体命令:
[root@oldboy ~]# tail -f /server/log/oldboy.log oldboy oldboy oldboy
提示:思考以及配置的规范过程比答案更重要,如果出现问题,请查看/var/log/cron排错。
本示例的知识小结具体如下。
·先确认Crond服务进程是否开启。
·书写定时任务规则前应尽量先写注释,以方便自己以及同事阅读。
·这里的/server/log目录必须要事先存在才能出结果,因此,在命令行测试执行成功很重要。
·定时任务中的所有路径(包含文件和命令等的路径)都尽量使用绝对路径(本题中不加echo也可以)。
·如果命令中有重定向符号等,那么结尾不要再加>/dev/null 2>&1,否则会出错。
·注意,定时任务的书写操作步骤具体如下。
1)先在命令行调试成功。
2)再将命令复制到定时任务配置里。
3)然后保存,并使用tail-f测试观察结果。
4)如果遇到问题,则可根据输出以及定时任务日志/var/log/cron文件内容排错。
范例13-2:让服务器时间每5分钟与互联网时间做一次同步。
本题的解答思路比较简单,下面直接给出答案如下:
[root@oldboy ~]# crontab -l|tail -3 #time sync by oldboy at 20180429 */5 * * * * /usr/sbin/ntpdate ntp1.aliyun.com &>/dev/null #<==主时间同步配置。 */5 * * * * /usr/sbin/ntpdate ntp3.aliyun.com &>/dev/null #<==辅助时间同步配置。
提示:CentOS7.6默认没有安装ntpdate命令,需要事先执行yum安装,命令是:yum install ntpdate-y。
服务器的时间同步很重要,因此,在工作中最好是配置两个不同地址的同步任务,在互联网中,可用ntp时间服务器地址信息查看老男孩的博客地址:
http://blog.51cto.com/oldboy/2109063
范例13-3:每天晚上0点,将站点目录/var/www/html下的内容打包备份到/data目录下,并且要求每次生成不同的备份包名。
第一步:确认要备份的数据目录和备份的目的地路径是否存在,如果不存在就创建一个,以便测试。以下是具体的操作步骤:
[root@oldboy ~]# ls -ld /var/www/html /data ls: cannot access /var/www/html: No such file or directory #<==目录都不存在。 ls: cannot access /data: No such file or directory [root@oldboy ~]# mkdir -p /var/www/html /data #<==创建目录。 [root@oldboy ~]# touch /var/www/html/oldboy{1..5}.txt #<==同时创建几个文件。 [root@oldboy ~]# ls /var/www/html/ oldboy1.txt oldboy2.txt oldboy4.txt oldboy3.txt oldboy5.txt
第二步:备份数据,一般是采用压缩打包的形式。以下是命令行测试:
[root@oldboy ~]# cd /var/www/ #<==打压缩包,最好是到备份数据目录的上一级目录打包。 [root@oldboy www]# tar zcvf /data/bak_$(date +%F).tar.gz ./html #<==带日期打包,目的是不同次备份生成不同的文件。 ./html/ ./html/oldboy4.txt ./html/oldboy3.txt ./html/oldboy1.txt ./html/oldboy2.txt ./html/oldboy5.txt [root@oldboy www]# ls -l /data -rw-r--r--. 1 root root 226 Apr 29 15:03 bak_2018-04-29.tar.gz
第三步:将测试成功的命令写入到文件里执行。注意,定时任务执行的命令最好是以Shell脚本的形式来执行,这样可以规避很多潜在的运行错误,例如,由“%”导致的错误,代码如下:
[root@oldboy www]# mkdir /server/scripts -p #<==规范定时任务脚本存放的路径。 [root@oldboy www]# cd /server/scripts/ #<==切换到路径下。 [root@oldboy scripts]# cat bak.sh #<==查看编辑后的脚本内容,其实就是命令行的命令集合。 cd /var/www/&&\ #<==&&表示本条命令成功之后再执行下面的tar命令,\表示换行。 /bin/tar zcf /data/bak_$(date +%F).tar.gz ./html #<==注意:这里去掉了-v参数,即不输出信息,打包的文件名中使用了日期变量,这样才能按天生成不同的压缩包文件。 [root@oldboy scripts]# rm -f /data/bak_2018-04-29.tar.gz #<==删除以前生成的文件。 [root@oldboy scripts]# /bin/sh /server/scripts/bak.sh #<==使用/bin/sh加全路径执行脚本。 [root@oldboy scripts]# ls -l /data total 4 -rw-r--r--. 1 root root 226 Apr 29 15:11 bak_2018-04-29.tar.gz #<==测试结果依然正确。
第四步:编写定时任务。在Linux命令行执行crontab-e,进入vi编辑状态之后,输入以下内容:
[root@oldboy scripts]# crontab -l|tail -2 #backup site dir by oldboy at 201805 #<==清晰的注释。 00 00 * * * /bin/sh /server/scripts/bak.sh >/dev/null 2>&1 #<==复制命令行脚本内容到这里,结尾要加>/dev/null 2> &1,将所有输出定向到空。
针对以上两个定时任务的实际应用例子,可以总结一下企业生产环境中定时任务的专业编写规范和操作步骤。
13.5 生产环境下的定时Cron书写要领
13.5.1 要领1:为定时任务规则加上必要的注释
书写定时任务规则时应尽可能地加上注释(最好是英文注释),这是个很好的运维习惯和规范。例如,什么人,在什么时间,因为谁(需求方),做了什么定时任务计划。如果这些都标记清楚了,那么其他的运维人员(同事)可以很容易理解任务的信息,从而提升团队工作的效率。若不写注释,则会给以后的维护和任务交接带来麻烦。带注释的定时任务示例代码如下:
[root@oldboy scripts]# crontab -l #backup site dir by oldboy at 201805 #<==清晰的注释,是专业、资深运维的习惯。 00 00 * * * /bin/sh /server/scripts/bak.sh >/dev/null 2>&1
13.5.2 要领2:所有的定时任务尽量都以脚本的形式执行
如果定时任务计划直接使用Linux命令执行,不但看着不规范,而且也很容易出错,特别是带系统时间变量(如果含有“%”,则必须要转义,即“\%”)的任务命令,如果能以文件的形式书写,则可以减少很多潜在的错误,并提升效率、规范,这是一个好习惯。在定时任务中执行命令也会有一些限制,如时间变量问题,多个重定向命令混用问题等,示例代码如下:
[root@oldboy scripts]# crontab -l #backup site dir by oldboy at 201805 00 00 * * * /bin/sh /server/scripts/bak.sh >/dev/null 2>&1 #<==写成脚本文件执行最佳。 [root@oldboy scripts]# cat bak.sh #<==脚本文件内容如下。 cd /var/www/&&\ /bin/tar zcf /data/bak_$(date +%F).tar.gz ./html
13.5.3 要领3:在执行的Shell脚本前加上/bin/sh
要确保Cron对应的执行者有访问Shell脚本所在目录的权限,并且可执行该Shell脚本(可用chmod和chown修改脚本权限和所有者)。当然,最佳方法是在要执行的任务脚本前加上/bin/sh,然后执行,否则就有可能会因为忘了为脚本设定执行权限,而无法完成当次任务执行计划。本条要领是一个经验型的好习惯。执行其他语言的脚本也要加上对应语言的解释器,以下是相应的执行方法示例。
规范的Shell脚本定时任务执行方法:/bin/sh/server/scripts/bak.sh
规范的Perl脚本定时任务执行方法:/usr/bin/perl/server/scripts/bak.sh
规范的Python脚本定时任务执行方法:/usr/bin/python/server/scripts/bak.py
13.5.4 要领4:定时任务中在命令或脚本的结尾加上>/dev/null 2>&1
定时任务(一般是脚本任务)规则的结尾最好加上“>/dev/null 2>&1”,如果需要打印日志,则可以追加到指定的日志文件里(此时不要与/dev/null同时存在),总之,定时任务计划脚本的结尾尽量不要留空。因为在默认情况下,定时任务每一次执行完毕之后,都会向对应的用户发邮件,如果不加将输出(正确或错误)定向到空的内容(>/dev/null 2>&1),则可能会由于系统未开启邮件服务而导致邮件临时目录文件数猛增的隐患发生,大量小文件占用磁盘Inode节点数量(每个文件占一个Inode),以致磁盘Inode写满而无法再写入正常数据(故障提示:no space left on device.)的故障发生。
在CentOS5下,小文件过多的目录一般为sendmail临时邮件文件目录/var/spool/clientmqueue,而CentOS 6下则是postfix临时队列目录/var/spool/postfix/maildrop/。
在“>/dev/null 2>&1”中,“>”表示重定向,“/dev/null”为特殊的字符设备文件,表示黑洞设备或空设备。“2>&1”表示让标准错误和标准输出一样,本命令的意思是将前面脚本的正常和错误输出都重定向到/dev/null,也就是什么都不输出。
下面三种让标准错误和标准输出都重定向到空的写法是等价的:
>/dev/null 2>&1 等价于 1>/dev/null 2>/dev/null 等价于 &>dev/null
13.5.5 要领5:在指定用户下执行相关定时任务
需要root权限执行的任务可以登录到root用户下然后进行设置,如果执行任务不需要root权限,则可以登录到普通用户下(也可以直接在root下通过命令crontab-u oldboy-e直接设置)进行设置。这里需要特别注意不同用户的环境变量问题,如果是调用了系统环境变量,例如/etc/profile等文件下的变量(如生产场景中Java程序的定时任务计划),那么最好是在程序脚本中将用到的环境变量重新export下(下文有案例)。
13.5.6 要领6:生产任务计划程序中不要随意打印输出信息
在开发定时任务程序或脚本时,调试好脚本程序之后,应尽量将Debug及命令输出的内容信息屏蔽掉,如果确实需要输出日志,则可定向到指定的日志文件里,以避免随意输出不做重定向,从而导致系统垃圾的产生。
例如,有的读者打包时喜欢用tar命令的zcvf这几个参数。其中的v参数就是用于查看打包信息的。在做定时任务计划时,命令里就不要再带这个参数了。
13.5.7 要领7:定时任务执行的脚本要存放到规范路径下
定时任务执行的脚本要存放到规范路径下,其实,系统中所有的脚本存放都要有规范,这里推荐统一使用/server/scripts作为脚本的存放路径。
13.5.8 要领8:配置定时任务要规范操作过程,减少出错
配置定时任务时,规范的操作过程具体如下。
1)尽量先在命令行测试成功,然后将成功的命令复制到脚本里,新手要在各个细小环节减少出错的机会。
2)然后执行测试脚本,测试成功后,将执行脚本的命令完整复制到定时任务配置里,尽量做到少手动输入命令。
3)先在测试环境下进行测试,然后在正式环境下规范部署。
4)要有检验任务是否正确执行的手段,例如,检查/var/log/cron日志文件,如果任务执行计划频率较低,也要想法确保任务的可执行性,此处可见下文调试定时任务的技巧。
13.5.9 要领9:定时任务脚本中程序命令及路径尽量使用全路径
定时任务脚本中,程序命令及路径应尽量使用全路径,这是个防止定时任务执行错误的好习惯,否则可能会导致命令行操作命令及执行脚本是正常的,但是放到定时任务中却无法正确执行的问题。当然,对于命令,除了写全路径之外,还可以在脚本中重新定义PATH环境变量,示例代码如下:
[root@oldboy scripts]# crontab -l #backup site dir by oldboy at 201805 00 00 * * * /bin/sh /server/scripts/bak.sh >/dev/null 2>&1 #<==/bin/sh命令使用全路径。 [root@oldboy scripts]# cat bak.sh #<==任务脚本内容如下。 cd /var/www/&&\ /bin/tar zcf /data/bak_$(date +%F).tar.gz ./html #<==/bin/tar备份命令使用全路径。
重新定义PATH环境变量的方法如下:
[root@oldboy scripts]# echo $PATH #<==打印输出,根据输出选择路径进行重新定义。 /usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin [root@oldboy scripts]# cat bak.sh #<==脚本内容如下。 export PATH='/sbin:/bin:/usr/sbin:/usr/bin' #<=重新定义环境变量,包含脚本中所有执行命令所在的路径。 cd /var/www/&&\ tar zcf /data/bak_$(date +%F).tar.gz ./html #<==这里可以取消全路径了。
13.5.10 要领10:时间变量%号要使用反斜线转义
“%”号在Cron任务配置中被认为是newline,需要要用“\”来转义。使用crontab编辑任务时,如果有类似于“date+%F”的时间变量,则必须做如下转义“date+\%F”,但是如果是在脚本中编写,那么“%”就不需要转义了。这也是笔者推荐定时任务使用脚本文件来执行的原因之一,定时任务使用命令执行时,执行命令中带有时间变量的写法(不建议使用此法)示例如下:
#tar comment by oldboy at 201805 */1 * * * * tar zcf /data/bak_$(date +\%F).tar.gz /var/www/html &>/dev/null
13.5.11 要领11:若脚本中调用了系统环境变量,则要重新定义
crontab执行Shell等脚本时只能识别很少的系统环境变量,用户在/etc/profile等文件中定义的普通变量一般是无法被定时任务服务识别的,如果在编写的脚本中需要使用这些环境变量,那么最好是使用export重新声明下该变量,这样脚本才能正常执行。
例如,在调试Java程序任务计划的时候,需要多注意环境变量的问题,务必要将环境变量的定义加到定时任务计划脚本里。Task.sh的作用是执行Java相关的程序,这里需要在脚本中重新定义相关的环境变量,示例代码如下:
[root@oldboy ~]# cat /scripts/resin/shell/Task.sh #!/bin/bash export JAVA_HOME=/application/jdk1.6 #<==如下三行是安装Java相关环境需要的特殊变量。 export PATH=$JAVA_HOME/bin:$PATH export SH_HOME=/application/resin/webapps/ROOT/ export LIB=$SH_HOME/WEB-INF/lib ...省略部分...
定时任务的配置结果如下:
#JAVA Shell by oldboy 201007 00 9,14 * * * nohup /scripts/resin/shell/Task.sh & >/app/log.log 2>&1
13.6 调试Cron定时任务的技巧总结
13.6.1 增加执行任务的频率以调试任务
在调试时,将任务执行频率调快一点,例如,每天执行的任务,可以改为每分钟、每5分钟执行一次,或者以当前时间为准,推迟5分钟以后,看能否执行,而且是不是按照你想要的去执行。如果正常没问题了,那么再改成需要的任务的执行时间。
需要强调一点的是,有些计划任务是不允许频繁执行的。例如,定时向数据库里插入数据,这样的任务就要在测试机上先测试好,然后再部署到正式线上,这样正式工作时出问题的机率就少了。
专业、规范的公司开发和运维人员配置服务器的操作流程至少是:办公室测试环境→IDC机房测试环境→IDC机房正式环境,在不同的环境上测试成功,再到更高级的环境是保障操作人员在正式环境下不出错的关键。
13.6.2 调整系统时间调试任务(不能用于生产环境)
用正确的执行任务的时间测试完成以后,即可修改系统的当前时间,改成任务执行时间的前几分钟来进行测试(或者重启定时任务服务)。例如,定时任务于9:00执行,那么我们可以将系统时间改成8:55分,然后观察是不是正确执行了,当前时间比任务时间建议提前3分钟以上,否则可能就不会执行,如果是生产环境服务器,则尽量不要修改时间测试,在测试环境下才可以使用这个手段。例如,若是要在周三的2:00执行,则可以将系统时间调整为周三凌晨1:55分查看执行结果。
13.6.3 通过脚本日志输出调试定时任务
要输出调试定时任务,可在脚本中加入日志输出,然后将输出打印到指定的日志中,并观察日志内容结果,以查看是否执行或正确执行。或者像下面一样,将脚本结果定向到一个log文件里。这里使用重定向符号“>”即可,不需要使用“>>”符号,这样日志就不会一直变大,如/app/log.log。示例代码如下:
#study task by oldboy at 20121213 00 9,14 * * 6,0 /bin/sh /server/scripts/oldboy.sh >/app/log.log 2>&1
提示:对于不好查看执行结果的定时任务计划可以这样调试。
也可以在脚本命令中通过参数打印信息输出,然后将输出重定向到指定的文件,示例代码如下:
[root@oldboy scripts]## cat tar.sh cd / tar zcvf /tmp/etc_$(date +%F).tar.gz ./etc >/tmp/tmp.log 2>&1 #<==加v参数,结尾重定向到文件。
13.6.4 通过Crond定时任务服务日志调试定时任务
查看定时任务服务的日志,可以发现执行的以及不能执行的任务问题所在,示例日志信息如下:
[root@oldboy scripts]# tail -f /var/log/cron Nov 10 11:40:01 oldboy crond[2605]: (root)CMD (echo "==" >> /tmp/oldboy.log >/dev/null 2>&1) Nov 10 11:40:01 oldboy crond[2608]: (root)CMD (/usr/sbin/ntpdate pool.ntp.org >/dev/null 2>&1) Nov 10 11:41:01 oldboy crond[2615]: (root)CMD (/bin/sh /server/scripts/echo.sh >/dev/null 2>&1 #print date) Nov 10 11:42:01 oldboy crond[2625]: (root)CMD (/bin/sh /server/scripts/echo.sh >/dev/null 2>&1 #print date) Nov 10 11:42:01 oldboy crond[2627]: (root)CMD (echo "==" >> /tmp/oldboy.log >/dev/null 2>&1) Nov 10 11:42:01 oldboy crond[2628]: (root)CMD (echo + >> /tmp/oldboy.log) tar.gz ./html
13.7 crontab生产案例故障分析及解决
13.7.1 No space left on device常见企业故障案例
1.故障描述及说明
工作中可能会出现这样的问题:在保存设置定时任务的规则时,系统提示“No space left on device”,此时用df-h命令检查磁盘,发现还有剩余空间,再用df-i命令检查则显示Inode已被100%占用了,导致系统无法在/var目录下创建文件。因为定时任务配置在/var/spool/cron下,在ext3、ext4文件系统中,每个文件至少要占用一个Inode(前文已讲,此处不再赘述)。
最后,经过检查发现在/var/spool/clientmqueue/下有大量的小文件,执行ls/var/spool/clientmqueue命令查看,很长时间都没能显示出结果,执行cd/var/spool/clientm-queue;rm-f*命令则会自动跳出来,无法实现删除。最后的解决方法是使用命令cd/var/spool/clientmqueue&&ls|xargs rm-f进行清理。
在清理时,如果文件的数量特别多,那么执行ls|xargs rm-f命令也会长时间无反应,不要着急,这是命令正在处理中的正常表现。当然,我们也可以使用更快的删除方法,如直接使用cd/var/spool&&rm-fr clientmqueue删除上级目录,然后执行如下命令:
mkdir clientmqueue && chmod 770 clientmqueue && chown smmsp.smmsp -R /var/spool/clientmqueue
修改回/var/spool/clientmqueue目录在系统中原有的默认权限:
[root@admin-3-3 ~]# ls -ld /var/spool/clientmqueue/ drwxrwx--- 2 smmsp smmsp 4096 12-12 13:46 /var/spool/clientmqueue/
2.故障原因分析
当系统中Crond定时任务执行的程序包含输出内容时,输出内容会以邮件的形式发回给执行任务的用户(默认是root),而sendmail、postfix等mail服务没有启动时,这些输出内容就会在邮件队列临时目录中产生大量很小的文件,导致消耗大量的Inode和block数量(在ext文件系统中,默认情况下格式化block的数量会远大于Inode的数量),一旦Inode数量耗尽,就会导致系统无法写入文件而报出上述错误“No space left on device”。
提示:上述为CentOS5系统中的故障案例,同样适合于CentOS6、CentOS7,只是后两者小文件多的路径改为postfix的临时邮件队列目录了。
3.预防方法
1)尽量在Cron任务中的命令或脚本中的命令的结尾加上“>/dev/null 2>&1”,或者在写定时执行脚本时,将输出定向到指定文件中(适合于所有情况)。
2)当然也可以开启邮件服务,不过最好不做,因为邮件服务会带来额外的安全问题。
3)添加定时清理任务,比如,将find/var/spool/clientmqueue/-type f-mtime+30|xargs rm-f放入定时任务,每周处理一次(适合于CentOS5),如果是CentOS6或CentOS7,则处理的路径为/var/spool/postfix/maildrop/。
13.7.2 Crond export变量生产案例
曾有网友在生产环境下碰到了问题,笔者为其解答后,他针对该案例进行了总结,一起来看看。
编写一个重启resin的脚本,由于业务原因,需要在某一个时间定时重启一次resin服务器,于是就在Cron里面配置了如下内容:
50 17 * * 1-5 root /usr/local/bin/resin_restart.sh
其中,resin_restart.sh的内容具体如下:
#!/bin/sh /usr/local/bin/xxresin_stop.sh /usr/local/bin/xxresin_start.sh
现在问题来了,服务器虽然定时重启了,但是系统却报出了如下错误:
Resin can't load com.sun.tools.javac.Main. Usually this means that the JDK tools.jar is missing from the classpath, possibly because of using a JRE instead of the JDK. You can either add tools.jar to the classpath or change the compiler to an external one with <java compiler='javac'/> or jikes.
为什么已经在profile里配置了环境变量,却还是找不到呢?
后来,在QQ交流群385168604中找到了热心的老男孩教育在线的老师,并请教,得到的回答是:这是因为export变量问题而导致的。
具体原因为Crond执行Shell时只能识别为数不多的系统环境变量,普通环境变量一般是无法识别的,如果在编写的脚本中需要使用变量,那么最好是使用export重新声明下该变量,以确保脚本能够正确执行。以后要将这一点做为一个开发的基本规范。
然后再在resin重启脚本里重新定义了一下环境变量,脚本如下:
#!/bin/sh
下面就是环境变量的具体定义:
JAVA_HOME="/opt/jdk1.6.0_18" CLASSPATH=$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar PATH=$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:/opt/nginx-0.7.61/sbin:/opt/jdk1.6.0_18/bin:/opt/resin-3.0.25/bin:$PATH export JAVA_HOME PATH USER LOGNAME MAIL HOSTNAME HISTSIZE INPUTRC CLASSPATH /usr/local/bin/xxresin_stop.sh /usr/local/bin/xxresin_start.sh
经过测试,修改后的定时任务可以顺利重启,问题解决完毕。
上述问题的相关内容发表于:http://oldboy.blog.51cto.com/2561410/1541515
13.8 有关Cron定时任务的企业面试题
1)在每周6的凌晨3:15执行一次/home/shell/collect.pl,并将标准输出和标准错误输出到/dev/null设备,请写出crontab中的语句。
2)crontab在11月份内,每天的早上6点到中午12点之间,每隔2小时执行一次/usr/bin/httpd.sh,如何实现?
3)crontab文件由六个域组成,每个域之间用空格进行分割,其排列正确的为下面哪一项()。
A、MIN HOUR DAY MONTH YEAR COMMAND
B、MIN HOUR DAY MONTH DAYOFWEEK COMMAND
C、COMMAND HOUR DAY MONTH DAYOFWEEK
D、COMMAND YEAR MONTH DAY HOUR MIN
13.9 定时任务知识逻辑图(学习方法)
学习方法、学习能力的提高,远大于学到的知识,下面就来分享下老男孩Linux课程学员总结的定时任务知识逻辑图,如图13-3所示。
通过图13-3,我们可以清晰了解定时任务的编写及调试前后的相关知识,这个方法值得所有读者学习。
13.10 本章重点
1)定时任务的时间及特殊字符书写语法。
2)编写定时任务的众多要领,都是规范。
3)两个企业故障案例总结。
4)调试Cron定时任务技巧总结。

图13-3 定时任务知识逻辑图