之前做了一个订阅博客文章的页面,由于需要下载订阅博客的feed文件,造成打开速度很慢的困惑。虽然也设置了 3 小时内直接读取缓存数据,超过 3 小时才会再次下载 feed 文件。可由于访问量不大,有可能 1 天就几个人访问,更有可能访客访问时离前一个访客的来访时间差已过了 3 小时,点击订阅页面理所当然就要下载feed文件了,因此也就造成了不好的体验。

为确保 3 小时一定会有人访问,我想了一个笨办法。使用服务器的定时任务表(crontab),每 3 小时访问一次订阅页面。

一、思路

首先,用 vim 创建一个 auto.sh 的文件,并写入下面的语句(不喜欢的可以用 nano 创建)

vim ~/auto.sh
#!/bin/bash
echo $(date +"%Y-%m-%d %H:%M:%S") >>abc.txt

保存退出后,赋可执行权限

chmod +x ~/auto.sh

然后使用 crontab -e 命令创建一个定时任务,写入定时任务,每隔1分钟执行一次 auto.sh 文件

0-59/1 * * * * ~/auto.sh

过2-3分钟看看abc.txt文件中,就会有几条内容了,每隔分钟写入了一个时间

cat ~/abc.txt
2025-03-03 22:07:01
2025-03-03 22:08:01
2025-03-03 22:09:01

定时任务没有问题,接下来继续用 crontab -e 命令将定时任务改为每3隔小时执行1次(此时,我已将feed 缓存设为了178分钟,刚好超出2分钟执行定时任务)

0 */3 * * * ~/auto.sh

再将 auto.sh 的第二行的内容修改为curl 订阅页面地址 >/dev/null

vim ~/auto.sh
#!/bin/bash
curl https://www.hollowman.cn/feed.html > /dev/null

至此,每隔3小时,服务器自己就会读取一下订阅地址,很快,缓存就会由定时任务主导了。下面是我回到服务器的操作,顺序没按上面来,无关紧要。

二、修正

流情大佬的友情提问下,我重新回顾了一下我站点定时任务的执行过程,从缓存文件下载(此时定时任务开始)之后到距离下次需要下载缓存文件的时间为178分,此后到180分(定时任务再次运行)会有2分钟处于定时任务失管状态,若此时有人访问,将再次刷新缓存文件,然后:下次需要刷新缓存文件的时间会在2分钟基础上加上此次提前刷新缓存的时间。若是每次都这么碰巧,则每次都会增加0-2分钟的失管状态,我只能说“倒霉鬼”,再想办法吧。

首先,我想到了一个办法。就是定时任务中的执行程序中先删除缓存文件,再访问页面。这样失管时间就永远都不会大于2分钟了。

修改后的 auto.sh 文件内容为下面的样子:

#!/bin/bash
rm -f /path/to/tmp/*.xml
curl https://www.hollowman.cn/feed.html > /dev/null
为什么要将失管时间控制在 2 分钟而不是 1 分钟?主要是减少意外情况的发生。比如我所在服务器或者订阅网站带宽过窄,我下载缓存都用了1分多钟,那设置 1 分钟不就白瞎了?

然后,我再次优化上面的修正办法,想着,既然 180 分钟时反正要删除缓存重新下载并重新刷新缓存时间,那我就是将缓存时间设置为 182 分钟甚至 1820 分钟,这时定时器小于缓存时间,一切都在定时器掌握中,应该没问题了吧。

三、关于 crontab 学习手册(man 5 crontab 并 AI翻译后整理)

NAME

crontab - 定时任务表

DESCRIPTION

crontab 文件包含对 cron(8) 守护进程的指令,格式为:"在指定日期时间的这个时刻运行此命令"。每个用户都有自己的 crontab 文件,其中包含的命令将以该用户的身份执行。Uucp 和 News 通常有自己的 crontab 文件,避免了在 cron 命令中显式运行 su(1) 的需求。

空行、前导空格和制表符将被忽略。以井号(#)开头的行视为注释并被忽略。注意:注释不能与 cron 命令同行,否则会被视为命令的一部分。同样,环境变量设置行也不能包含注释。

crontab 中的有效行可以是环境变量设置或 cron 命令。环境变量设置的格式为 name=value,等号两侧的空格可选,value 部分的后续非前导空格将作为变量的值。值可以用引号(单引号或双引号,需匹配)包裹以保留前后空白。

cron(8) 守护进程会自动设置几个环境变量:SHELL 被设置为 /bin/sh,LOGNAME 和 HOME 根据 crontab 所有者的 /etc/passwd 文件设置。HOME 和 SHELL 可以通过 crontab 设置覆盖,但 LOGNAME 不可覆盖。(注:在 BSD 系统中,LOGNAME 有时被称为 USER,此时 USER 也会被设置)

除了 LOGNAME、HOME 和 SHELL,cron(8) 还会根据 MAILTO 的设置发送邮件。如果 MAILTO 被定义且非空,则将邮件发送给指定用户;若 MAILTO 定义为空字符串则不发送邮件;否则邮件发送给 crontab 的所有者。此功能在选择 /bin/mail 作为邮件程序时特别有用,因为 /bin/mail 不处理别名,而 UUCP 通常不会读取邮件。

cron 命令的格式遵循 V7 标准并做了扩展兼容。每行包含五个时间和日期字段,如果是系统 crontab 文件则包含用户名,最后是命令。当分钟、小时和月份字段与当前时间匹配,并且日期字段(日或星期几)至少有一个匹配时,命令将被执行。cron(8) 每分钟检查一次任务表。

时间和日期字段说明:

      字段            允许值
      -----          --------------
      分钟            0-59
      小时            0-23
      月中的日        0-31
      月份            0-12(或月份名称,见下文)
      星期几          0-7(0 或 7 表示周日)

字段可以包含星号(*),表示"首尾都包括"。支持数值范围(用连字符分隔)、列表(逗号分隔)和步长(用斜杠指定间隔)。

数值范围是允许的。范围由两个用连字符(-)分隔的数字组成,且包含起始和结束值。例如,在"小时"字段中使用8-11表示命令将在8点、9点、10点和11点各执行一次。

列表允许使用。列表是由逗号分隔的一组数值(或范围)组成的集合。例如:1,2,5,9 或 0-4,8-12。

步长值可以与范围结合使用。在范围后添加 / 表示以指定数值为步长跳过范围中的值。例如,在小时字段中使用 0-23/2 表示每两小时执行一次命令(V7 标准的等效写法为 0,2,4,6,8,10,12,14,16,18,20,22)。步长值也允许出现在星号之后,因此若需表示"每两小时执行",可直接使用 */2。

月份和星期几字段还支持名称缩写。使用特定日期或月份名称的前三个字母(不区分大小写),例如 JAN(一月)、FRI(星期五)等。名称不允许使用范围或列表形式。

第六个字段(行的剩余部分)指定要执行的命令。整条命令部分(直到换行符或 % 符号为止)将由 /bin/sh 或 crontab 文件中 SHELL 变量指定的 shell 执行。命令中的百分号(%)若未用反斜杠(\)转义,将被替换为换行符,且 % 后的所有数据将作为标准输入传递给命令。

注意:命令的执行日期可通过两个字段共同指定——月中的日和星期几。若这两个字段均被限制(即非 ),则当任一字段匹配当前时间时,命令就会执行。例如,30 4 1,15 5 表示每月1日和15日的4:30am执行该命令,外加每周五执行。

EXAMPLE CRON FILE

强制使用 /bin/sh 执行命令,忽略/etc/passwd中的设置

SHELL=/bin/sh

所有输出邮件发送给 paul,无论此crontab属于哪个用户

MAILTO=paul

每天 00:05 执行

5 0 * * * HOME/bin/daily.job>>HOME/tmp/out 2>&1

每月 1 日 14:15 执行,并将输出发送给 paul

15 14 1 * * $HOME/bin/monthly

每周工作日(周一至周五) 22:00 提醒 Joe

0 22 * * 1-5 mail -s "It's 10pm" joe%Joe,%%Where are your kids?%

每天 00:23、02:23、04:23...22:23 执行

23 0-23/2 * * * echo "run 23 minutes after midn, 2am, 4am ..., everyday"

每个周日 4:05 执行

5 4 * * sun echo "run at 5 after 4 every sunday"

每9天运行一次,即使跨越周、月和年的边界(我还没看懂):

33 22 * * * expr $(date +s) / 60 / 60 / 24 9 > /dev/null || echo Wax the floor.

SEE ALSO

cron(8), crontab(1)

EXTENSIONS

星期几字段:在指定星期几时,0 和 7 均被视为周日。但 BSD 和 ATT 系统对此处理方式存在分歧。

​字段混合规则:同一字段中允许同时使用列表和范围(如 1-3,7-9),但 ATT 或 BSD cron 可能拒绝此类写法,仅接受纯范围(如 1-3)或纯列表(如 7,8,9)。

​步长支持:范围中可包含步长(如 1-9/2),等价于每隔一个数的序列 1,3,5,7,9。

​名称缩写:月份和星期几字段支持名称缩写(如 JAN、FRI),但不允许通过名称进行范围或列表操作。

​环境变量:可在 crontab 中设置环境变量。在 BSD 或 ATT 系统中,子进程的环境变量继承自 /etc/rc。

​邮件功能差异:命令输出默认发送给 crontab 所有者(BSD 不支持此功能)。可将邮件发送给非所有者(SysV 不支持此功能)。可完全禁用邮件功能(SysV 也不支持此操作)。

AUTHOR

Paul Vixie paul@vix.com