【问题标题】:Running a cron every 30 seconds每 30 秒运行一次 cron
【发布时间】:2012-03-26 00:52:34
【问题描述】:

好的,我有一个需要每 30 秒运行一次的 cron。

这是我所拥有的:

*/30 * * * * /bin/bash -l -c 'cd /srv/last_song/releases/20120308133159 && script/rails runner -e production '\''Song.insert_latest'\'''

它运行,但它是每 30 分钟还是每 30 秒运行一次?

另外,我一直在读到,如果我经常运行 cron,它可能不是最好的工具。我可以在 Ubuntu 11.04 上使用或安装另一个更好的工具吗?有没有办法修复上面的cron?

【问题讨论】:

  • CommaToast,如果您的 Javascript 或 Java 应用程序由于某种原因崩溃并退出,会发生什么情况?它将如何重新启动? :-)
  • 添加一个小的 NodeJS 应用程序,哈哈。为什么不是一个小的 c++ 应用程序?当我们使用它时,我们可以将其命名为“cron”并将其作为服务运行。
  • 我刚刚在查看用户资料时发现了这个,并且看到您在线不到 1 小时(只是为了检查 acc 是否仍在使用中),您是否有任何具体原因不接受以下任何答案?

标签: ubuntu cron scheduled-tasks


【解决方案1】:

您在 minutes 说明符中有 */30 - 这意味着每分钟但步长为 30(换句话说,每半小时)。由于cron 没有达到亚分钟的分辨率,因此您需要找到另一种方法。

一种可能性,虽然有点杂乱无章(a),但有两个工作,一个偏移 30 秒:

# Need these to run on 30-sec boundaries, keep commands in sync.
* * * * *              /path/to/executable param1 param2
* * * * * ( sleep 30 ; /path/to/executable param1 param2 )

您会看到我添加了 cmets 并进行了格式化,以确保它们很容易保持同步。

cron 两个作业实际上每分钟运行一次,但后者会等待半分钟,然后再执行作业的“内容”/path/to/executable

对于其他(非cron-based)选项,请参阅此处的其他答案,尤其是提到fcronsystemd 的答案。假设您的系统能够使用它们(例如安装 fcron 或拥有带有 systemd 的发行版),这些可能更可取。


如果您不想想要使用 kludgy 解决方案,您可以使用基于循环的解决方案并稍作修改。您仍然需要设法让您的流程以某种形式运行,但是一旦排序,以下脚本应该可以工作:

#!/bin/env bash

# Debug code to start on minute boundary and to
# gradually increase maximum payload duration to
# see what happens when the payload exceeds 30 seconds.

((maxtime = 20))
while [[ "$(date +%S)" != "00" ]]; do true; done

while true; do
    # Start a background timer BEFORE the payload runs.

    sleep 30 &

    # Execute the payload, some random duration up to the limit.
    # Extra blank line if excess payload.

    ((delay = RANDOM % maxtime + 1))
    ((maxtime += 1))
    echo "$(date) Sleeping for ${delay} seconds (max ${maxtime})."
    [[ ${delay} -gt 30 ]] && echo
    sleep ${delay}

    # Wait for timer to finish before next cycle.

    wait
done

诀窍是使用sleep 30,但在您的有效负载运行之前在背景中启动它。然后,payload完成后,等待后台sleep完成即可。

如果负载需要n 秒(其中n <= 30),则负载之后的等待时间将为30 - n 秒。如果花费的时间超过 30 秒,则下一个周期将延迟到有效负载完成,但不会再延迟。

您会看到我在那里有调试代码,从一分钟的边界开始,使最初的输出更容易理解。我也逐渐增加了最大有效载荷时间,所以你最终会看到有效载荷超过了 30 秒的循环时间(输出了一个额外的空白行,因此效果很明显)。

下面是一个示例运行(循环通常在上一个循环后 30 秒开始):

Tue May 26 20:56:00 AWST 2020 Sleeping for 9 seconds (max 21).
Tue May 26 20:56:30 AWST 2020 Sleeping for 19 seconds (max 22).
Tue May 26 20:57:00 AWST 2020 Sleeping for 9 seconds (max 23).
Tue May 26 20:57:30 AWST 2020 Sleeping for 7 seconds (max 24).
Tue May 26 20:58:00 AWST 2020 Sleeping for 2 seconds (max 25).
Tue May 26 20:58:30 AWST 2020 Sleeping for 8 seconds (max 26).
Tue May 26 20:59:00 AWST 2020 Sleeping for 20 seconds (max 27).
Tue May 26 20:59:30 AWST 2020 Sleeping for 25 seconds (max 28).
Tue May 26 21:00:00 AWST 2020 Sleeping for 5 seconds (max 29).
Tue May 26 21:00:30 AWST 2020 Sleeping for 6 seconds (max 30).
Tue May 26 21:01:00 AWST 2020 Sleeping for 27 seconds (max 31).
Tue May 26 21:01:30 AWST 2020 Sleeping for 25 seconds (max 32).
Tue May 26 21:02:00 AWST 2020 Sleeping for 15 seconds (max 33).
Tue May 26 21:02:30 AWST 2020 Sleeping for 10 seconds (max 34).
Tue May 26 21:03:00 AWST 2020 Sleeping for 5 seconds (max 35).
Tue May 26 21:03:30 AWST 2020 Sleeping for 35 seconds (max 36).

Tue May 26 21:04:05 AWST 2020 Sleeping for 2 seconds (max 37).
Tue May 26 21:04:35 AWST 2020 Sleeping for 20 seconds (max 38).
Tue May 26 21:05:05 AWST 2020 Sleeping for 22 seconds (max 39).
Tue May 26 21:05:35 AWST 2020 Sleeping for 18 seconds (max 40).
Tue May 26 21:06:05 AWST 2020 Sleeping for 33 seconds (max 41).

Tue May 26 21:06:38 AWST 2020 Sleeping for 31 seconds (max 42).

Tue May 26 21:07:09 AWST 2020 Sleeping for 6 seconds (max 43).

如果您想避免笨拙的解决方案,这可能会更好。您仍然需要cron 作业(或等效的)来定期检测此脚本是否正在运行,如果没有,则启动它。但是脚本本身会处理时间。


(a) 我的一些同事会说拼凑是我的专长 :-)

【讨论】:

  • 这是一个很好的解决方法,所以我认为它超越了它的笨拙
  • @rubo77,只有在运行时间少于一秒的情况下 :-) 如果需要 29 秒,它将发生在 0:00:00、0:00.59、0:01:00、 0:01:59 等等。
  • 第二行周围的圆括号是什么?
  • 睡眠是运行自定义秒的完美主意
  • 这是一个很好的解决方案,可以解决一个问题,否则会削弱某些需要在几分钟内执行的任务的 crons 效率。谢谢。
【解决方案2】:

你不能。 Cron 的粒度为 60 秒。

* * * * * cd /srv/last_song/releases/20120308133159 && script/rails runner -e production '\''Song.insert_latest'\''
* * * * * sleep 30 && cd /srv/last_song/releases/20120308133159 && script/rails runner -e production '\''Song.insert_latest'\''

【讨论】:

  • 这个语法是否等同于 paxdiablo 的?还是有细微的差别?
  • 不同之处在于:我使用了二进制文件的原始路径。 @paxdiablo 使用了一种元语法。 (并调用一个子shell)
  • 我的意思是,使用&& 而不是( ; )
  • 对不起。不,有区别; && 运算符短路,因此如果前一个命令失败,则不会执行链中的下一个命令。
  • 这种粒度对于亚分钟分辨率来说应该不是问题。
【解决方案3】:

Cron 的粒度以分钟为单位,并非设计为每隔 x 秒唤醒一次以运行某些东西。在循环中运行您的重复任务,它应该可以满足您的需求:

#!/bin/env bash
while [ true ]; do
 sleep 30
 # do what you need to here
done

【讨论】:

  • 请记住,这并不完全相同。如果作业需要 25 秒(例如),它将每 55 秒而不是每 30 秒启动一次。这可能无关紧要,但您应该意识到可能的后果。
  • 您可以在后台运行该作业,然后它将在几乎正好 30 秒内运行。
  • while [true]do sleep 30 # do you need to here done --------- done 应该是小写的
  • while [ true ] 会不会导致您拥有许多相同脚本的实例,因为 cron 每分钟都会启动一个新实例?
  • 您可以使用sleep $remainingTime,其中剩余时间为 30 减去作业花费的时间(如果花费 > 30 秒,则将其上限设为零)。所以你把实际工作前后的时间,计算一下。
【解决方案4】:

你可以查看我对this similar question的回复

基本上,我在其中包含了一个名为“runEvery.sh”的 bash 脚本,您可以使用 cron 每 1 分钟运行一次,并将您希望运行的真实命令和您希望运行的频率(以秒为单位)作为参数传递它。

类似的东西

* * * * * ~/bin/runEvery.sh 5 myScript.sh

【讨论】:

    【解决方案5】:

    Cron 作业不能用于以秒为间隔安排作业。即您不能安排 cron 作业每 5 秒运行一次。另一种方法是编写一个在其中使用sleep 5 命令的shell 脚本。

    使用 bash while 循环每 5-seconds.sh 创建一个 shell 脚本,如下所示。

    $ cat every-5-seconds.sh
    #!/bin/bash
    while true
    do
     /home/ramesh/backup.sh
     sleep 5
    done
    

    现在,使用nohup 在后台执行这个shell 脚本,如下所示。即使您从会话中注销,这也会继续执行脚本。这将每 5 秒执行一次您的 backup.sh shell 脚本。

    $ nohup ./every-5-seconds.sh &
    

    【讨论】:

    • 时间会漂移。例如,如果backup.sh 需要 1.5 秒运行,它将每 6.5 秒执行一次。有一些方法可以避免这种情况,例如sleep $((5 - $(date +%s) % 5))
    • 我是 nohup 的新手,在执行您的示例时,nohup 返回“没有这样的文件或目录”。经过一番搜索,您似乎在 nohup 之后错过了“sh”。像这样: $ nohup sh ./every-5-seconds.sh &
    • 这不仅仅是时间漂移,它是双重和三重执行,因为 crontab 将在另一个脚本仍在运行时启动一个新脚本
    【解决方案6】:

    使用 fcron (http://fcron.free.fr/) - 以秒为单位提供粒度,比 cron (vixie-cron) 更好、功能更丰富,而且也很稳定。我曾经做过一些愚蠢的事情,比如在一台机器上以非常愚蠢的设置运行大约 60 个 php 脚本,但它仍然完成了它的工作!

    【讨论】:

    • 一个 PHP 开发者的自白; )
    • 其实是一位系统工程师的自白让 PHP 开发者成为可能...... :)
    【解决方案7】:

    不需要两个cron条目,你可以把它合二为一:

    * * * * * /bin/bash -l -c "/path/to/executable; sleep 30 ; /path/to/executable"
    

    所以在你的情况下:

    * * * * * /bin/bash -l -c "cd /srv/last_song/releases/20120308133159 && script/rails runner -e production '\''Song.insert_latest'\'' ; sleep 30 ; cd /srv/last_song/releases/20120308133159 && script/rails runner -e production '\''Song.insert_latest'\''"

    【讨论】:

    • 注意:只有在脚本运行时间少于一秒时才能正确运行
    • Rubo - 如果作业需要几秒钟的时间(而不是毫秒或微秒)才能完成,那么您不会每 30 秒运行一次才能每分钟运行两次。所以是的,从 30 开始,然后减去每次运行的大致秒数,如果大于 1 秒。
    • 如果您想单独接收每次运行的错误报告,这也无济于事。
    • joelin - 我给出的命令不会阻止获取日志或输出数据,我简化了命令来解决这个问题。要捕获日志记录,如果您需要日志记录,每个命令都可以/应该重定向输出,例如 script/rails runner -e production '\''Song.insert_latest'\'' 可以写成 script/rails runner -e production '\' 'Song.insert_latest'\'' 2>&1 > /path_to_logfile 可以为单个 cron 条目中的每个命令再次执行。
    【解决方案8】:

    Crontab 作业可用于以分钟/小时/天为单位安排作业,但不能以秒为单位。替代方案:

    创建一个每 30 秒执行一次的脚本:

    #!/bin/bash
    # 30sec.sh
    
    for COUNT in `seq 29` ; do
      cp /application/tmp/* /home/test
      sleep 30
    done
    

    使用crontab -e 和一个 crontab 来执行这个脚本:

    * * * * * /home/test/30sec.sh > /dev/null
    

    【讨论】:

    • 如果我理解正确的话,这个脚本会运行 30 次并在每次迭代之间等待 30 秒。在 cron 中每分钟运行一次有什么意义?
    【解决方案9】:

    使用手表:

    $ watch --interval .30 script_to_run_every_30_sec.sh
    

    【讨论】:

    • 我可以使用$ watch --interval .10 php some_file.php之类的东西吗?或 watch 仅适用于 .sh 文件?
    • 你可以用手表运行任何东西。但是间隔是在下一个命令的结束和开始之间,所以--interval .30 不会每分钟运行两次。即watch -n 2 "sleep 1 && date +%s",它将每 3 秒递增一次。
    • 请注意 watch 是为终端使用而设计的,所以 - 虽然它可以在没有终端的情况下工作(使用 nohup 运行然后注销)或使用假终端(例如 screen) - 它无法提供类似 cron 的行为,例如从故障中恢复、启动后重新启动等。
    【解决方案10】:

    我刚刚完成了一项类似的任务并使用以下方法:

    nohup watch -n30 "kill -3 NODE_PID" &
    

    我需要每隔 30 秒定期执行一次 kill -3(以获取程序的堆栈跟踪),持续几个小时。

    nohup ... & 
    

    这是为了确保如果我失去外壳(网络问题、Windows 崩溃等)我不会丢失 watch 的执行

    【讨论】:

      【解决方案11】:

      在目录/etc/cron.d/

      新建文件excute_per_30s

      * * * * * yourusername  /bin/date >> /home/yourusername/temp/date.txt
      * * * * * yourusername sleep 30; /bin/date >> /home/yourusername/temp/date.txt
      

      每 30 秒运行一次 cron

      【讨论】:

        【解决方案12】:

        感谢所有好的答案。为了简单起见,我喜欢混合解决方案,在 crontab 上进行控制,在脚本上进行时间划分。所以这就是我每 20 秒(每分钟 3 次)运行一个脚本的方法。 crontab 行:

         * * * * 1-6 ./a/b/checkAgendaScript >> /home/a/b/cronlogs/checkAgenda.log
        

        脚本:

        cd /home/a/b/checkAgenda
        
        java -jar checkAgenda.jar
        sleep 20
        java -jar checkAgenda.jar 
        sleep 20
        java -jar checkAgenda.jar 
        

        【讨论】:

        • 1-6代表什么?
        • @Phantom007 1-6 代表星期一到星期六,其中“-”是一个范围,“0”是星期日。这是一个很好的链接,它很好地解释了所有字段以及您可以在哪里测试它:“crontab.guru/#*_*_*_*_1-6”
        【解决方案13】:

        在shell循环中运行,例如:

        #!/bin/sh    
        counter=1
        while true ; do
         echo $counter
         counter=$((counter+1))
         if [[ "$counter" -eq 60 ]]; then
          counter=0
         fi
         wget -q http://localhost/tool/heartbeat/ -O - > /dev/null 2>&1 &
         sleep 1
        done
        

        【讨论】:

        • 即使假设60 应该是30,您也可能希望将wget 移入 if 语句中,否则它会每秒执行一次。无论如何,我不确定这比单个sleep 30 更好。如果您监控的是实际的 UNIX 时间而不是您的计数器,那将会有所不同。
        • echo 计数器打印出来,如果不在后台运行 wget,可以通过 EXCUTE COMMAND 找出延迟时间。
        【解决方案14】:

        编写一个shell脚本 创建 .sh 文件

        nano every30second.sh

        编写脚本

        #!/bin/bash
        For  (( i=1; i <= 2; i++ ))
        do
            write Command here
            sleep 30
        done
        

        然后为这个脚本设置 cron crontab -e

        (* * * * * /home/username/every30second.sh)

        此 cron 每 1 分钟调用一次 .sh 文件,并且在 .sh 文件中的命令在 1 分钟内运行 2 次​​p>

        如果您想运行脚本 5 秒,则将 30 替换为 5 并更改 for 循环,如下所示:For (( i=1; i &lt;= 12; i++ ))

        当您选择任何一秒时,计算 60/秒并写入 For 循环

        【讨论】:

          【解决方案15】:

          目前我正在使用以下方法。工作没有问题。

          * * * * * /bin/bash -c ' for i in {1..X}; do YOUR_COMMANDS ; sleep Y ; done '
          

          如果您想每 N 秒运行一次,则 X 将是 60/NY 将是N

          【讨论】:

          • 您可能希望将YOUR_COMMANDS 更改为YOUR_COMMANDS &amp;,以便将命令启动到后台,否则,如果命令花费的时间超过几分之一秒 - 它将延迟下一个发射。因此,在 X=2 和 Y=30 的情况下,如果命令需要 10 秒 - 它将在分钟启动,然后在 40 秒后启动,而不是 30 秒。感谢@paxdiablo。
          • 出于某种原因,如果我省略了/bin/bash -c 部分(包括参数引号),则脚本只会每分钟运行一次,而忽略迭代(在我的情况下为X=12Y=5)。
          • 我收到process already running。可能是因为sleep 需要多几毫秒才能死掉。最好不要调用最后一个 sleep,例如,在 1 分钟内仅调用 5 次,暂停 10 秒。
          【解决方案16】:

          如果您正在运行带有 SystemD 的最新 Linux 操作系统,则可以使用 SystemD 计时器单元以您希望的任何粒度级别(理论上低至纳秒)运行脚本,并且 - 如果您愿意 - 比启动规则更灵活Cron 曾经允许。 不需要sleep 杂项

          与 cron 文件中的单行相比,它需要更多的设置,但如果您需要比“每分钟”更好的东西,那么值得付出努力。

          SystemD 计时器模型基本上是这样的:计时器是在计时器结束时启动服务单元的单元

          因此,对于您要安排的每个脚本/命令,您必须有一个服务单元,然后是一个额外的计时器单元。一个计时器单元可以包含多个时间表,因此您通常不需要超过一个计时器和一项服务。

          这是一个简单的例子,它每 10 秒记录一次“Hello World”:

          /etc/systemd/system/helloworld.service:

          [Unit]
          Description=Say Hello
          [Service]
          ExecStart=/usr/bin/logger -i Hello World
          

          /etc/systemd/system/helloworld.timer:

          [Unit]
          Description=Say Hello every 10 seconds
          [Timer]
          OnBootSec=10
          OnUnitActiveSec=10
          AccuracySec=1ms
          [Install]
          WantedBy=timers.target
          

          设置这些单位后(在/etc/systemd/system 中,如上所述,用于系统范围设置,或~/.config/systemd/user 用于用户特定设置),您需要启用计时器(但不是服务)通过运行systemctl enable --now helloworld.timer--now 标志也会立即启动计时器,否则只会在下次启动或用户登录后启动)。

          这里使用的[Timer]部分字段如下:

          • OnBootSec - 在每次启动后的这么多秒内启动服务。
          • OnUnitActiveSec - 在上次启动服务后的这么多秒内启动服务。这就是导致计时器重复自身并表现得像 cron 作业的原因。
          • AccuracySec - 设置计时器的精度。计时器仅与此字段设置的一样准确,默认值为 1 分钟(模拟 cron)。不要求最佳精度的主要原因是为了提高功耗 - 如果 SystemD 可以安排下一次运行以与其他事件同时发生,它需要更少地唤醒 CPU。上面示例中的 1ms 并不理想 - 我通常在我的亚分钟计划作业中将准确度设置为 1(1 秒),但这意味着如果您查看显示“Hello World”消息的日志,你会看到它经常迟到 1 秒。如果您对此表示满意,我建议将准确度设置为 1 秒或更长。

          您可能已经注意到,这个计时器并不能很好地模仿 Cron - 从某种意义上说,该命令不会在每个挂钟周期的开始时开始(即它不会在第 10 秒开始时钟,然后是 20 号,依此类推)。相反,它只是在计时器结束时发生。如果系统在 12:05:37 启动,那么下一次运行命令将在 12:05:47,然后在 12:05:57,等等。如果您对实际挂钟精度感兴趣,那么您可以想要替换 OnBootSecOnUnitActiveSec 字段,而是使用您想要的时间表设置 OnCalendar 规则(据我所知,使用日历格式不能超过 1 秒)。上面的例子也可以写成:

          OnCalendar=*-*-* *:*:00,10,20,30,40,50
          

          最后一点:正如您可能猜到的那样,helloworld.timer 单元以 helloworld.service 单元开头,因为它们具有相同的名称(减去单元类型后缀)。这是默认设置,但您可以通过为 [Timer] 部分设置 Unit 字段来覆盖它。

          更多血腥细节可以在以下位置找到:

          【讨论】:

          • 我经常在 cmets 部分遇到这样更好的答案。恕我直言,虽然 cron 一直是计划作业的主要内容,但这个答案应该是公认的,因为它不是睡眠的“黑客作业”,并且考虑到所需的间隔/频率,冒着并行化执行长时间运行任务的风险
          • 超棒的答案,应该是选择的答案
          • 我想知道在 Alpine 上是否有可能,因为人们说那里是 OpenRC,而不是 systemd。
          • @Nakilon:Alpine 最初的目的是作为容器的简约操作系统,因此它不需要像 SystemD 这样的复杂系统运行时。 IMO OpenRC 对于容器来说也完全是一种过度杀伤——如果你有充分的理由使用多进程容器,请使用 supervisord 或其他简单的东西,我也质疑你在这种情况下是否需要运行 cron。我知道有些人在非容器情况下运行 Alpine,我希望他们停止 - Alpine 甚至没有通过作为主操作系统运行所需的最基本的强化和 QA。
          • @Guss,目标不是多进程,而是比一分钟一次更频繁地运行任务。我需要它在 10 秒内运行一次。我目前正在使用 cron 调用带有六个命令的 .sh 脚本,这些命令像 { ruby main.rb &amp; sleep 10 ;} &amp;&amp; ... &amp;&amp; ruby main.rb 这样链接。你看,在上一次迭代中我没有调用sleep——我认为我需要像这样内联循环,因为我遇到了"process is still running" 错误。错误消息消失了,但它仍然跳过第二次迭代,我不知道为什么。仍在调试。
          【解决方案17】:

          看看frequent-cron - 它很旧但非常稳定,您可以降到微秒级。在这个时间点上,我唯一要反对的是,我仍在努力研究如何在 init.d 之外安装它,但作为本机 systemd 服务,但肯定到 Ubuntu 18 之前它只运行仍然可以使用 init.d (距离可能在后面的版本中有所不同)。它还有一个额外的优势 (?) 可以确保它不会生成另一个 PHP 脚本实例,除非之前的脚本已经完成,这减少了潜在的内存泄漏问题。

          【讨论】:

            【解决方案18】:

            您可以将该脚本作为服务运行,每 30 秒重新启动一次

            注册服务

            sudo vim /etc/systemd/system/YOUR_SERVICE_NAME.service
            

            在下面的命令中粘贴

            Description=GIVE_YOUR_SERVICE_A_DESCRIPTION
            
            Wants=network.target
            After=syslog.target network-online.target
            
            [Service]
            Type=simple
            ExecStart=YOUR_COMMAND_HERE
            Restart=always
            RestartSec=10
            KillMode=process
            
            [Install]
            WantedBy=multi-user.target
            

            重新加载服务

            sudo systemctl daemon-reload
            

            启用服务

            sudo systemctl enable YOUR_SERVICE_NAME
            

            启动服务

            sudo systemctl start YOUR_SERVICE_NAME
            

            检查您的服务状态

            systemctl status YOUR_SERVICE_NAME
            

            【讨论】:

            • 它会起作用,但它会在 daemon.log 或类似文件中创建大量日志输出 在大多数情况下,最好在 bash 脚本中循环
            【解决方案19】:

            经过反复试验,我找到了正确的表达方式:*/30 * * ? * * * 这转换为每 30 秒。 参考:https://www.freeformatter.com/cron-expression-generator-quartz.html 他们提供了每秒运行的表达式: * * * ? * * * */x 用于以每 x 个单位运行。我在分钟的地方和中提琴上试过了。我相信其他人已经发现了这一点,但我想分享我的尤里卡时刻! :D

            【讨论】:

            • 这是 Quartz,不是 cron。
            猜你喜欢
            • 1970-01-01
            • 2021-06-17
            • 2019-06-06
            • 2015-01-18
            • 1970-01-01
            • 2014-08-14
            • 2011-03-20
            • 1970-01-01
            • 2023-04-01
            相关资源
            最近更新 更多