15.1 什么是例行性工作调度

Linux 的例行性工作调度通过 atcrontab 两个工具实现,让我们可以把备份、日志分析、提醒等任务交给系统自动执行,无需手动操作。

15.1.1 Linux 工作调度的种类:at, cron

类型工具服务用途
一次性(突发性)atatd执行一次就结束
循环性(例行性)crontabcrond按分钟/小时/日/月/周循环执行
  • at:处理仅执行一次的任务。需要 atd 服务支持,CentOS 默认启动。
  • crontab:循环执行的任务,可设置分钟、小时、每周、每月或每年。除了 crontab 指令外,也可编辑 /etc/crontab

15.1.2 CentOS 上常见的例行性工作

Linux 系统会自动进行很多例行性任务,这些都由 crond 调度:

例行工作说明相关指令/文件
登录文件轮替(log rotate)防止登录文件无限增大,定期分割归档logrotate
登录文件分析主动分析登录信息,root 会收到 logwatch 邮件logwatch
locate 数据库更新更新 /var/lib/mlocate/ 文件名数据库updatedb
man page 数据库创建创建快速查询的 man page 数据库mandb
RPM 软件登录文件创建追踪软件安装/变更产生的文件名差异
移除暂存盘删除超时的暂存盘,释放磁盘空间tmpwatch
网络服务分析WWW 服务器(apache)日志分析、凭证过期检查等取决于安装的软件

系统例行性工作的多少,跟安装了哪些软件有关——很多服务会附带自己的分析工具和 cron 配置。

15.2 仅执行一次的工作调度

15.2.1 atd 的启动与 at 运行的方式

启动 atd 服务

systemctl restart atd      # 重启 atd
systemctl enable atd       # 开机自动启动
systemctl status atd       # 查看状态(需要看到 enabled + running)

at 的运行机制

  • 使用 at 指令产生工作,以文本文件写入 /var/spool/at/ 目录
  • atd 服务从该目录读取并执行工作
  • at 工作独立于当前 bash 环境,由 atd 接管,下达后即可离线——适合长时间任务,不怕断网

安全控制(at.allow / at.deny)

先找 /etc/at.allow → 存在:只有列出的用户能用
                    → 不存在:找 /etc/at.deny → 在 deny 中的不能用,不在的都能用
两个文件都不存在 → 只有 root 能用 at
  • at.allow 是严格白名单管理
  • at.deny 是宽松黑名单管理(CentOS 默认为空文件 = 所有人可用)
  • 一个帐号一行

15.2.2 at 命令的语法与操作实务

基本语法

at [-mldv] TIME
at -c 工作号码      # 查看工作的实际指令内容
选项说明
-m工作完成后即使无输出也发 email 通知
-l相当于 atq,列出当前用户的 at 调度
-d相当于 atrm,删除调度中的一个工作
-v用更详细的时间格式列出 at 调度
-c列出工作号码对应的实际指令内容

TIME 时间格式

格式示例说明
HH:MM04:00今日 HH:MM 执行;若已过则明天
HH:MM YYYY-MM-DD04:00 2015-07-30指定具体日期时刻
HH:MM[am|pm] [Month] [Date]04pm July 30同指定具体日期
now + N [minutes|hours|days|weeks]now + 5 minutes, 04pm + 3 days相对时间(推荐)

操作实务

# 5 分钟后把 .bashrc 寄给 root
at now + 5 minutes
at> /bin/mail -s "testing at job" root < /root/.bashrc
at> <EOT>              # Ctrl+d 结束输入
 
# 指定日期关机
at 23:00 2015-08-04
at> /bin/sync
at> /bin/sync
at> /sbin/shutdown -h now
at> <EOT>

⚠️ at 中的指令应使用绝对路径,因为 at 执行时的环境变量(PATH)和登录 shell 不同。

💡 at 执行时的工作目录是下达 at 指令时所在的目录。例如在 /tmp 下达 at,里面引用 ./file/tmp/file

at 的输出去哪了?

  • at 的执行环境与终端机无关——所有 stdout/stderr 都会寄到执行者的 mailbox
  • 想在终端机看到输出 → 重导向到终端设备:echo "Hello" > /dev/tty1
  • 指令无输出则默认不发 mail;用 at -m 强制发通知

管理 at 工作

atq                    # 列出待执行的工作(等同于 at -l)
atrm [jobnumber]       # 删除指定工作(等同于 at -d)

batch:系统空闲时执行

batch 利用 at 机制,但会在 CPU 工作负载 < 0.8 时才执行任务。CentOS 7 中 batch 不再支持时间参数。

💡 工作负载(load)≠ CPU 使用率。load 是 CPU 在单一时间点负责的工作数量。一只持续运算的程序占满 CPU(使用率 100%),load 约等于 1;同时跑 4 只,load ≈ 4,CPU 在频繁切换工作导致系统忙碌。

# 高负载时下达 batch,任务会等待负载降低后才执行
batch
at> /usr/bin/updatedb
at> <EOT>
# 用 uptime 查看 load,atq 确认工作是否还在等待

at 和 crontab 最小时间单位都是分钟——每分钟检查一次,整分(秒为 0)时处理。

15.3 循环执行的例行性工作调度

15.3.1 使用者的设置

安全控制(与 at 类似):

/etc/cron.allow 存在 → 只有列出的用户能用 crontab
                不存在 → 看 /etc/cron.deny → 不在 deny 中的都能用

CentOS 默认保留空的 /etc/cron.deny,即所有用户都可用 crontab。

crontab 指令

crontab [-u username] [-l | -e | -r]
选项说明
-u只有 root 可用,帮其他用户创建/移除 crontab
-e编辑 crontab 工作内容(会进入 vi 编辑器)
-l查看 crontab 工作内容
-r删除全部 crontab 工作(⚠️ 只删一项也用 -e!)

⚠️ crontab -r 是删除整个 crontab!只删某一项请用 crontab -e 编辑删除。

用户 crontab 存放位置:/var/spool/cron/<帐号名>(不要直接用 vi 编辑该文件,用 crontab -e 才安全)。cron 的执行日志记录在 /var/log/cron

crontab 格式(6 个字段)

分  时  日  月  周  指令
字段范围说明
0-59
0-23
1-31
1-12
0-70 和 7 都代表星期天
指令要执行的命令

特殊字符

字符含义示例
*任意时刻* * * * * = 每分钟
,分隔多个时段0 3,6 * * * = 3:00 和 6:00
-时间范围20 8-12 * * * = 8,9,10,11,12 点的 20 分
/n每隔 n 单位*/5 * * * * = 每 5 分钟(等同于 0-59/5

常用示例

# 每天 12:00 发信给自己
0 12 * * * mail -s "at 12:00" dmtsai < /home/dmtsai/.bashrc
 
# 每年 5 月 1 日 23:59 发信(5 月 2 日生日的提醒)
59 23 1 5 * mail kiki < /home/dmtsai/lover.txt
 
# 每 5 分钟执行脚本
*/5 * * * * /home/dmtsai/test.sh
 
# 每星期五 16:30 发信提醒
30 16 * * 5 mail friend@his.server.name < /home/dmtsai/friend.txt

15.3.2 系统的配置文件:/etc/crontab, /etc/cron.d/*

crontab -e 是个人化的,系统的例行性任务通过编辑 /etc/crontab 来管理。注意 /usr/bin/crontab 是可执行文件,/etc/crontab 是纯文本文件——cron 每分钟读取一次这些配置文件,编辑后不用重启任何服务(但某些 Unix 系统需 systemctl restart crond)。

/etc/crontab 格式(7 个字段)

分  时  日  月  周  使用者  指令

与用户 crontab 的区别:

  • 多了一个”使用者”字段——指定以什么身份执行命令
  • 可以设置 MAILTOPATHSHELL 等环境变量
cat /etc/crontab
# SHELL=/bin/bash
# PATH=/sbin:/bin:/usr/sbin:/usr/bin
# MAILTO=root              # 错误/输出寄给谁(建议改为自己的邮箱)
# 分 时 日 月 周 用户 指令

💡 MAILTO 建议改为自己能收到的邮箱(root 无法用 POP3 收信)。


crond 读取配置的三个位置

位置用途
/etc/crontab系统级例行任务的主配置文件
/etc/cron.d/*第三方软件的 cron 配置(每个软件一个独立文件)
/var/spool/cron/*每个用户的个人 crontab

/etc/cron.d/*:如果你开发软件需要定时任务,直接在 /etc/cron.d/ 下放一个带”分时日月周 用户 指令”的配置文件即可,升级时覆盖文件就行,比手动编辑 /etc/crontab 干净得多。

例如 /etc/cron.d/0hourly

01 * * * * root run-parts /etc/cron.hourly

这行让系统在每整点的第 1 分钟执行 run-parts /etc/cron.hourly——也就是执行 /etc/cron.hourly/ 目录下所有可执行文件。


/etc/cron.*/ 目录

目录触发方式执行时机
/etc/cron.hourly/由 crond 直接调用 run-parts每小时第 1 分起的 5 分钟内随机
/etc/cron.daily/anacron 调用 run-parts每天
/etc/cron.weekly/anacron 调用 run-parts每周
/etc/cron.monthly/anacron 调用 run-parts每月

注意:/etc/cron.hourly/ 是 crond 直接驱动,而 daily/weekly/monthly 是 anacron 驱动的(见 15.4 节)。

使用场景总结

场景方法
个人化需求crontab -e(安全,不被其他用户看到)
系统维护管理vim /etc/crontab(集中管理,易追踪)
第三方软件vim /etc/cron.d/newfile(独立配置,方便升级)
固定周期的简单任务直接把可执行脚本丢进 /etc/cron.{hourly,daily,weekly,monthly}/

15.3.3 一些注意事项

① 资源分配不均

当多个工作都设为 */5 * * * *,它们会在同一时刻全部启动。解决方案:用逗号把分钟错开。

1,6,11,16,21,26,31,36,41,46,51,56 * * * * root CMD1
2,7,12,17,22,27,32,37,42,47,52,57 * * * * root CMD2
3,8,13,18,23,28,33,38,43,48,53,58 * * * * root CMD3
4,9,14,19,24,29,34,39,44,49,54,59 * * * * root CMD4

逗号分隔的时段之间不要加空白字符

② 取消不要的输出

当 cron 任务有输出或出错时,会将内容发给 MAILTO 指定的帐号。如果某个任务一直出错,你会被邮件轰炸。

# 丢弃所有输出
*/5 * * * * /path/to/script > /dev/null 2>&1

③ 安全检查

检查 /var/log/cron 日志,确认是否有非你设置的 cron 在运行——木马常用例行任务方式植入。

④ 周与日月不可同时并存

# ❌ 错误——想设"9月11号且星期五",实际上可能变成:每个周五都执行,或每年9月11日执行
30 12 11 9 5 root echo "just test"

⚠️ crontab 中日和周不能同时指定。要么用日期,要么用星期,不要两个都写。

15.4 可唤醒停机期间的工作任务

假设你有一台周末会关机的 Linux 主机,crontab 里的周日凌晨备份任务就因为关机错过了。anacron 就是用来解决这个问题的——它会在开机后补执行错过的任务。

15.4.1 anacron

  • anacron 不是替代 crontab,而是弥补 cron 不支持”补执行”的缺陷
  • anacron 每小时被 crond 调用一次,然后检测相关任务有没有执行过
  • 超出期限的 → 执行;没超的 → 跳过
  • 默认以1 天、7 天、一个月为期检测

检测原理:anacron 对比当前时间和时间戳记文件(/var/spool/anacron/*)中记录的上次执行时间,有差异说明关机期间错过了任务。

15.4.2 crond 与 anacron 的关系

anacron 的调用链

crond 每分钟读取 /etc/cron.d/0hourly
  → 每小时执行 /etc/cron.hourly/0anacron
    → 0anacron 调用 /usr/sbin/anacron -s
      → anacron 读取 /etc/anacrontab
        → 检查并补执行 /etc/cron.{daily,weekly,monthly}/ 中的任务

/etc/anacrontab 格式

SHELL=/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
RANDOM_DELAY=45           # 随机最大延迟(分钟),避免多机同时涌入
START_HOURS_RANGE=3-22    # 允许执行的时间范围(3 点 ~ 22 点)

# 天数  延迟(分)  工作名称       实际指令
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 执行流程(以 cron.daily 为例)

步骤说明
1/etc/anacrontab 获知 cron.daily 的天数为 1 天
2/var/spool/anacron/cron.daily 读取上次执行的时间戳记
3比较时间戳记与当前时间,相差 ≥1 天就准备执行
4根据配置延迟 5 分钟 + 随机最大 45 分钟(RANDOM_DELAY
5延迟结束后执行 run-parts /etc/cron.daily
6执行完毕,anacron 结束

时间戳记文件/var/spool/anacron/):

$ cat /var/spool/anacron/cron.daily
20150731

anacron 命令

anacron [-sfn] [job]       # 执行调度
anacron -u [job]           # 仅更新时间戳记,不执行
选项说明
-s根据时间戳记依次执行各项 job
-f强制执行,不看时间戳记
-n立即执行,不等延迟
-u只更新戳记不执行

/etc/cron.hourly/0anacron 文件名前加 0 是为了在 hourly 目录中第一个被执行,从而确保时间戳记最先更新,避免 anacron 误判。


crond 与 anacron 的关系总结

调度方式触发链特点
crond 直接调度/etc/crontab/etc/cron.d/*/var/spool/cron/*定时执行,过了就过了
crond → 每小时目录/etc/cron.d/0hourlyrun-parts /etc/cron.hourly/定时执行
crond → anacron/etc/cron.hourly/0anacron/etc/anacrontabrun-parts /etc/cron.{daily,weekly,monthly}/定期执行,错过会补

💡 关键区别:放在 /etc/crontab 里的任务,过期就过期了。放在 /etc/cron.weekly/ 里的任务,几乎一定会在一周内执行一次——即使关机超过一周,开机几小时内就会被 anacron 补上。

完整执行链

1. crond 读取 /etc/crontab, /var/spool/cron/*, /etc/cron.d/* → 按时间执行
2. /etc/cron.d/0hourly → 每小时执行 /etc/cron.hourly/ 下的可执行文件
3. /etc/cron.hourly/0anacron → 每小时调用 anacron,读取 /etc/anacrontab
4. anacron 根据 /etc/anacrontab → 检测并按需补执行 /etc/cron.{daily,weekly,monthly}/