回到Linux基础系列文章大纲
回到Shell系列文章大纲
systemdtimer:代替cron和at的定时任务cron和systemdtimer实现定时任务的比较
Linux环境下,cron是使用最广泛的定时任务工具,但它有一些不便捷的地方。例如它默认:
由于cron不原生支持以上功能,所以当有以上相关需求时,只能在要调度的命令层次上找寻解决方案。
systemd系统中包含了timer计时器组件,timer可以完全取代cron+at,它具有以下特点:
可对定时任务做资源限制可取代cron和at工具,且支持比cron愈发灵活丰富的定时规则不会重复执行定时任务集成到journal日志,便捷调试任务,便捷查看任务调度情况…
但systemdtimer相比cron也有不足支持:
systemdtimer入门示例:每3秒运行一次
使用systemdtimer定时任务时,须要同时编撰两个文件:
这两个文件名称一般保持一致(不仅后缀部份),它们可以置于:
比如:
1
2
3
4
5
6
7
8
/usr/lib/systemd/system/foo.service
/usr/lib/systemd/system/foo.timer
/etc/systemd/system/foo.service
/etc/systemd/system/foo.timer
~/.config/systemd/user/foo.timer
~/.config/systemd/user/foo.service
假定定义一个每3秒执行一次的任务,该任务用于测量页面是否正常,对应命令为curl-s-o/dev/null-w'%{http_code}'#39;,其结果为访问页面时响应的HTTP状态码。
先编撰对应服务配置文件:
shell
1
2
3
4
5
6
cat /usr/lib/systemd/system/page_test.service
[Unit]
Description = "test page: https://www.junmajinlong.com"
[Service]
ExecStart = /usr/bin/curl -s -o /dev/null -w '%{http_code}' 'https://www.junmajinlong.com'
由于命令每次调用都只执行一次且快速退出,所以Service中使用了默认的Type=simple。其实linux makefile,也可以使用Type=oneshot。
再编撰定时器配置文件:
shell
1
2
3
4
5
6
7
8
9
10
11
12
cat /usr/lib/systemd/system/page_test.timer
[Unit]
Description = "test page: https://www.junmajinlong.com every 3 seconds"
[Timer]
OnActiveSec = 1s
OnUnitInactiveSec = 3s
AccuracySec = 1us
RandomizedDelaySec = 0
[Install]
WantedBy = timers.target
再执行如下命令即可让定时器生效:
shell
1
2
systemctl daemon-reload
systemctl start page_test.timer # 启动定时器
其实,还支持如下命令来管理定时器:
shell
1
2
3
4
5
6
systemctl status xxx.timer
systemctl stop xxx.timer
systemctl restart xxx.timer
和WantedBy的值有关,若WantedBy=timers.target,则本命令多余
systemctl enable xxx.timer
回头来剖析一下定时器配置文件中涉及到的指令。
首先是该文件[Install]段中的最后一行WantedBy=timers.target,它表示在开机时会手动启动该定时器,之所以会开机手动执行这种timers定时计划,是由于在basic.target中定义了timer.target依赖。
shell
1
2
3
systemctl list-dependencies --reverse timers.target | head -2
timers.target
● └─basic.target
再看Timer段中定义定时器属性的指令。
OnActiveSec表示从该定时器启动(即systemctlstartxxx.timer)以后,多长时间触发定时器对应的任务,即执行对应的Service服务。本例是启动定时器后一秒,开始第一次执行任务单元page_test.service。
OnUnitInactiveSec表示从上一次任务单元退出后,多长时间再度触发定时器对应的任务。诸如在本例中,表示的涵义是每次page_test.service执行完成(即页面测量完成后退出)后3秒,再度触发该任务。
剩余两个指令AccuracySec和RandomizedDelaySec,稍后再详尽解释。由于在解释它们之前,须要学会观察定时任务的执行情况。
观察定时任务的执行时间点
使用systemctllist-timers可以列举当前早已生效的定时器(即假如不停止它,则迟早会触发对应的定时任务)。它会根据上次要执行的时间点先后进行排序,最快要执行的任务在最上面。
shell
1
2
3
4
systemctl list-timers --no-pager
NEXT LEFT LAST PASSED UNIT ACTIVATES
Sat 2020-07-04 18:43:34 CST 20s ago Sat 2020-07-04 18:43:34 CST 20s ago page_test.timer page_test.service
Sun 2020-07-05 07:03:39 CST 12h left Fri 2020-07-03 17:27:44 CST 1 day 1h ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
其中:
其实里面的含意都比较清晰,并且想要理解透彻,还真不容易。
不过,还有其它观察定时任务执行情况的方法。因为systemdservice默认集成了journald日志系统,命令的标准输出和标准错误就会输出到journal日志中。
例如,可以使用systemctlstatusxxx.service观察定时器对应任务的执行状况,即每次执行任务的时间点以及定时任务执行过程中的标准输出、标准错误信息。
shell
1
2
3
4
5
6
7
8
9
10
systemctl status page_test.service # 注意是.service不是.timer
● page_test.service - "test page: https://www.junmajinlong.com"
Loaded: loaded
Active: inactive (dead) since Sat 2020-07-04 18:36:56 CST; 2s ago
Process: 22316 ExecStart=/usr/bin/curl ... (code=exited, status=0/SUCCESS)
Main PID: 22316 (code=exited, status=0/SUCCESS)
Jul 04 18:36:54 host.junmajinlong.com systemd[1]: Started "test page:....
Jul 04 18:36:56 host.junmajinlong.com curl[22316]: 200
Hint: Some lines were ellipsized, use -l to show in full.
里面的结果表明,近来一次test_page定时任务是在18:36:54开始执行的,18:36:56执行完成并返回执行结果,即HTTP响应状态码200,这个200是来自于curl的输出。
还可以使用journalctl工具来查看定时任务的日志信息:
shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
查看指定服务的所有journal日志信息
xxx.service是定时任务的名称
journalctl -u xxx.service
实时监控尾部日志,类似tail -f
journalctl -f -u xxx.service
显示指定时间段内的日志
--since:从指定时间点内开始的日志
--until:到指定时间点为止的日志
journalctl -u xxx.service --since="2020-07-04 19:06:23"
journalctl -u xxx.service --since="60s ago"
journalctl -u xxx.service --since="1min ago"
journalctl -u page_test.service --since="-60s"
比如:
shell
1
2
3
4
5
6
7
journalctl -u page_test.service --since="-30s"
-- Logs begin at Tue 2020-06-30 14:34:48 CST, end at Sat 2020-07-04 19:13:57 CST. --
Jul 04 19:13:32 host.junmajinlong.com curl[23592]: 200
Jul 04 19:13:35 host.junmajinlong.com systemd[1]: Started "test page: https://www.junmajinlong.com".
Jul 04 19:13:43 host.junmajinlong.com curl[23602]: 200
Jul 04 19:13:46 host.junmajinlong.com systemd[1]: Started "test page: https://www.junmajinlong.com".
Jul 04 19:13:57 host.junmajinlong.com curl[23605]: 200
从结果可以看出linux计划任务没执行,在19:13:35、19:13:46都执行了page_test任务。
精确触发任务:理解AccuracySec和RandomizedDelaySec
AccuracySec表示任务延后执行的延后范围,即从每次指定要执行任务的精确时间点到延后时间段内的一个随机时间点启动任务。使用这些延后,主要是为了防止systemd频繁触发定时器风波进而频繁唤起CPU,进而让一定时间段内附近的定时任务可以集中在这个时间段内启动。
比如:
Code
1
2
3
4
5
6
# 定时器启动后,再过10分钟第一次触发定时任务
OnActiveSec=10m
# 每次执行完任务后,再过15分钟后再次触发定时任务
OnUnitInactiveSec=15m
# 触发事件后,允许推迟0-10分钟再执行被触发的任务
AccuracySec=10m
所以,以上指令的疗效是:
AccuracySec的默认值为1分钟,所以假如不定义AccuracySec的话,就算用户期盼的是每秒触发一次定时任务,但事实却是会在1s-61s时间段内的一个随机时间点触发一次定时任务。(可以自己去观察一下定时任务执行情况)
然而,触发定时任务的时间点并不表示这是执行任务的时间点。触发了定时任务,还须要依照RandomizedDelaySec的值来决定何时执行定时任务。
RandomizedDelaySec指定触发定时任务后还需延后一个指定范围内的随机时长才执行任务。该指令默认值为0,表示触发后立刻执行任务。
使用RandomizedDelaySec,主要是为了在一个时间范围内分散大量被同时触发的定时任务,进而防止这种定时任务集中在同一时间点执行而CPU争抢。
可见linux手机,AccuracySec和RandomizedDelaySec的目的是相反的:
按照以上描述,假如用户想要让定时任务十分精确度地执行,须要将它们设置的足够小。诸如:
Code
1
2
AccuracySec = 1ms # 定时器到点就触发定时任务
RandomizedDelaySec = 0 # 定时任务一触发就立刻执行任务
systemdtimer支持的单调定时规则
不仅前面介绍的两个OnxxxSec类定时规则外,systemdtimer还支持几种其它的定时器规则。
定时器指令涵义
OnBootSec
从开机启动后,即从内核开始运行算起,多长时间触发定时器对应任务
OnStartupSec
从systemd启动后,即内核启动init进程算起,多长时间触发定时器对应任务
OnActiveSec
从该定时器启动后,多长时间触发定时器对应的任务
OnUnitInactiveSec
从下次任务单元退出后,多长时间再度触发定时器对应的任务
OnUnitActiveSec
从先前触发的任务开始执行(状态达到active)算起,多长时间再度触发定时器对应的任务
注:(1)当Service文件中Type=oneshot,这类任务不会出现active状态,除非配置了RemainAfterExit指令(参考mansystemd.service)
(2)这个定时器用的不如OnUnitInactiveSec多,由于这个定时器是以启动时间为基准的,有可能上次触发任务时,先前任务还没有执行完成,systemd会忽视上次任务
其中OnBootSec和OnStartupSec比较特殊,由于定时器自身的启动比这两个时间点要晚,假如定时器配置文件中以这两个指令为定时任务的触发基准,可能会出现超期现象。例如某定时器设置OnBootSec=1s,但倘若从启动内核到启动定时器早已过了2s,这么这个定时任务就超期了。好在,systemd会对这两个特殊的指令特殊对待,倘若这类定时任务超期了,将立刻执行定时任务实现补救。
但对其它三个指令定义的定时器,超期了就超期了,不会再尝试去补救。
也就是说,虽然过了有效期,这两类定时任务还是有效的,而其它定时任务则失效。
事实上,这几个定时器指令都是单调定时器,即:这种任务的触发时机,总是以某个时间点为基准单调降低的。
更为灵活的定时规则:OnCalendar
cron定时任务支持*****来定义定时任务,这5个位置分别表示分时日月周。
后面早已介绍的systemdtimer的定时规则早已就能实现只执行一次和每隔多久执行一次的定时规则。下边要介绍的OnCalendar基于月历的定时规则完全可以胜任cron的定时规则。
比如:
Code
1
OnCalendar = Thu,Fri 2012-*-1,5 11:12:13
这表示2012年每位月的1或5号的11点12分13秒,同时要求是周六或周日。
OnCalendar支持的时间格式很灵活,所以下边介绍它的内容也稍多,请渐渐享用。
systemdtimer可辨识的时间单位包括以下几种:
月的单位:months,month,M年的单位(一年以365.25天算):years,year,y
多个时间单位可结合使用,且时间的出现次序无关。
比如下边的时间单位都是有效的:
Code
1
2
3
4
5
6
2 h --> 2小时
2hours --> 2小时
48hr --> 48小时
1y 12month --> 1年12个月,即2年
55s500ms --> 55秒+500毫秒
300ms20s 5day --> 5天+20秒+300毫秒,顺序无关
在定时器里,就会常常用到表示某年某月某天、某时某分某秒的时间戳格式。systemd内部的标准时间戳格式为:
Code
1
2
Fri 2012-11-23 11:12:13
Fri 2012-11-23 11:12:13 UTC
对于时区而言,假如要客场区,则必须只能加UTC三个字符,否则只能省略,此时表示本地时区(注:此处叙述是不对的,由于还支持其他更复杂的格式,但时区相关的内容太多,这儿略去)。
最上面的周几可以省略,如非须要周几符号,强烈建议省略它。但若果不省略,则必须只能使用三字母表示法或中文全称,即合理的周几符号包括:
Code
1
2
3
4
5
6
7
Monday Mon
Tuesday Tue
Wednesday Wed
Thursday Thu
Friday Fri
Saturday Sat
Sunday Sun
年-月-日与时:分:秒两者可省其二,但不可全国。若省后者,则表示使用当前日期,若省前者则表示使用00:00:00。
时:分:秒可以省略:秒,相当于使用:00。
年-月-日中的年可以省略为2位数字表示,相当于20xx,但强烈建议不要使用这些方法。
倘若指定的礼拜与年-月-日(虽然此部份已被省略)与实际不相符,这么该时间戳无效。
还可以使用一些时间戳关键字:now,today,yesterday,tomorrow。
还可以使用一些相对时间表示法:时长加上+前缀或则'left'后缀(注意有空格),表示借此时间为基准向未来前进指定的时长,时长加上-前缀或则'ago'后缀(注意有空格)linux计划任务没执行,表示借此时间为基准向过去倒退指定的时长。