447 Stimmen

Alle 30 Sekunden einen Cron laufen lassen

Ok, ich habe also einen Cron, den ich alle 30 Sekunden ausführen muss.

Hier ist, was ich habe:

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

Es läuft, aber läuft es alle 30 Minuten oder alle 30 Sekunden?

Außerdem habe ich gelesen, dass Cron vielleicht nicht das beste Werkzeug ist, wenn ich es so oft laufen lasse. Gibt es ein anderes besseres Tool, das ich unter Ubuntu 11.04 verwenden oder installieren kann und das eine bessere Option ist? Gibt es eine Möglichkeit, den obigen Cron zu reparieren?

905voto

paxdiablo Punkte 809679

Sie haben */30 im Minuten Das heißt, jede Minute, aber mit einer Schrittweite von 30 (mit anderen Worten, jede halbe Stunde). Da cron nicht auf subminütige Auflösungen heruntergeht, müssen Sie einen anderen Weg finden.

Eine Möglichkeit, auch wenn sie ein wenig umständlich ist (a) ist es, zwei Aufträge zu haben, einer davon um 30 Sekunden versetzt:

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

Sie werden sehen, dass ich Kommentare hinzugefügt und formatiert habe, damit sie leicht synchronisiert werden können.

Beide cron Aufträge laufen eigentlich jede Minute, aber die letztere wartet man eine halbe Minute, bevor man den "Hauptteil" des Auftrags ausführt, /path/to/executable .

Für andere (nicht cron -basierten) Optionen, siehe die anderen Antworten hier, insbesondere die, die sich auf fcron y systemd . Diese sind wahrscheinlich vorzuziehen, vorausgesetzt, Ihr System ist in der Lage, sie zu verwenden (z. B. durch die Installation von fcron oder eine Distro mit systemd in ihr).


Wenn Sie nicht verwenden möchten, können Sie eine schleifenbasierte Lösung mit einer kleinen Änderung verwenden. Sie müssen sich immer noch darum kümmern, dass Ihr Prozess in irgendeiner Form weiterläuft, aber wenn das erledigt ist, sollte das folgende Skript funktionieren:

#!/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

Der Trick besteht in der Verwendung eines sleep 30 aber um es in der Hintergrund bevor Ihre Nutzlast ausgeführt wird. Nachdem die Nutzlast beendet ist, warten Sie einfach auf die Hintergrund sleep zu beenden.

Wenn die Nutzlast n Sekunden (wobei n <= 30 ), die Wartezeit nach der Nutzlast beträgt dann 30 - n Sekunden. Wenn es dauert mehr mehr als 30 Sekunden, so wird der nächste Zyklus so lange verzögert, bis die Nutzlast beendet ist, jedoch nicht länger.

Sie werden sehen, dass ich einen Debug-Code eingefügt habe, der an der Grenze von einer Minute beginnt, damit die Ausgabe anfangs leichter zu verfolgen ist. Außerdem erhöhe ich allmählich die maximale Nutzlastzeit, so dass Sie schließlich sehen, dass die Nutzlast die 30-Sekunden-Zykluszeit überschreitet (eine zusätzliche Leerzeile wird ausgegeben, damit der Effekt offensichtlich ist).

Es folgt ein Probelauf (wobei die Zyklen normalerweise 30 Sekunden nach dem vorherigen Zyklus beginnen):

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).

Wenn Sie die plumpe Lösung vermeiden wollen, ist dies wahrscheinlich besser. Sie brauchen immer noch eine cron Job (oder ein gleichwertiges Programm), um in regelmäßigen Abständen festzustellen, ob dieses Skript läuft, und wenn nicht, es zu starten. Das Skript selbst kümmert sich dann um das Timing.


(a) Einige meiner Arbeitskollegen würden sagen, dass Schlampereien meine Spezialität sind :-)

125voto

Guss Punkte 27060

Wenn Sie ein aktuelles Linux-Betriebssystem mit SystemD verwenden, können Sie die Timer-Einheit von SystemD nutzen, um Ihr Skript mit beliebiger Granularität (theoretisch bis hinunter zu Nanosekunden) auszuführen, und - wenn Sie möchten - viel flexiblere Startregeln anwenden, als dies mit Cron jemals möglich war. Nein sleep Schlampereien erforderlich

Die Einrichtung ist etwas aufwendiger als eine einzelne Zeile in einer Cron-Datei, aber wenn Sie etwas Besseres als "Jede Minute" benötigen, lohnt sich der Aufwand.

Das SystemD-Zeitgebermodell ist im Grunde genommen wie folgt: Timer sind Einheiten, die nach Ablauf eines Timers Serviceeinheiten starten .

Für jedes Skript bzw. jeden Befehl, das bzw. den Sie planen wollen, müssen Sie also eine Service-Unit und eine zusätzliche Timer-Unit haben. Eine einzelne Timer-Einheit kann mehrere Zeitpläne enthalten, so dass Sie normalerweise nicht mehr als einen Timer und einen Dienst benötigen.

Hier ist ein einfaches Beispiel, das alle 10 Sekunden "Hello World" protokolliert:

/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

Nach dem Einrichten dieser Einheiten (in /etc/systemd/system wie oben beschrieben, für eine systemweite Einstellung, oder bei ~/.config/systemd/user für eine benutzerspezifische Einrichtung), müssen Sie den Timer (nicht den Dienst) aktivieren, indem Sie systemctl enable --now helloworld.timer (die --now Flagge wird der Timer auch sofort gestartet, andernfalls wird er erst nach dem nächsten Bootvorgang oder der Benutzeranmeldung gestartet).

En [Timer] Die hier verwendeten Abschnittsfelder sind wie folgt:

  • OnBootSec - starten Sie den Dienst so viele Sekunden nach jedem Start.
  • OnUnitActiveSec - den Dienst so viele Sekunden nach dem letzten Start des Dienstes starten. Dies führt dazu, dass der Timer sich selbst wiederholt und sich wie ein Cron-Job verhält.
  • AccuracySec - stellt die Genauigkeit des Timers ein. Die Zeitgeber sind nur so genau, wie in diesem Feld eingestellt, und die Standardeinstellung ist 1 Minute (emuliert Cron). Der Hauptgrund, nicht die beste Genauigkeit zu verlangen, ist die Verbesserung des Stromverbrauchs - wenn SystemD den nächsten Lauf so planen kann, dass er mit anderen Ereignissen zusammenfällt, muss es die CPU weniger oft aufwecken. Die 1ms im obigen Beispiel ist nicht ideal - ich setze die Genauigkeit normalerweise auf 1 (1 Sekunde) in meinen geplanten Aufträgen im Minutentakt, aber das würde bedeuten, dass die "Hello World"-Meldungen im Protokoll oft um 1 Sekunde zu spät kommen. Wenn Sie damit einverstanden sind, schlage ich vor, die Genauigkeit auf 1 Sekunde oder mehr einzustellen.

Wie Sie vielleicht bemerkt haben, ahmt dieser Timer Cron nicht so gut nach - in dem Sinne, dass der Befehl nicht zu Beginn jeder Wanduhrperiode startet (d.h. er startet nicht bei der 10. Sekunde auf der Uhr, dann bei der 20. usw.). Stattdessen erfolgt er nur, wenn der Timer abläuft. Wenn das System um 12:05:37 Uhr hochgefahren ist, wird der Befehl das nächste Mal um 12:05:47 Uhr ausgeführt, dann um 12:05:57 Uhr, usw. Wenn Sie an der tatsächlichen Genauigkeit der Wanduhr interessiert sind, sollten Sie die Option OnBootSec y OnUnitActiveSec Felder und setzen stattdessen ein OnCalendar Regel mit dem gewünschten Zeitplan (der, soweit ich weiß, nicht schneller als 1 Sekunde sein kann, wenn man das Kalenderformat verwendet). Das obige Beispiel kann auch geschrieben werden als:

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

Letzte Anmerkung: Wie Sie wahrscheinlich erraten haben, ist die helloworld.timer Gerät startet die helloworld.service Einheit, da sie denselben Namen haben (ohne den Zusatz "unit type"). Dies ist die Standardeinstellung, aber Sie können sie außer Kraft setzen, indem Sie die Option Unit Feld für die [Timer] Abschnitt.

Weitere blutige Details finden Sie unter:

82voto

wildplasser Punkte 41104

Das können Sie nicht. Cron hat eine Granularität von 60 Sekunden.

* * * * * 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'\''

41voto

Marvin Pinto Punkte 29072

Die Granularität von Cron ist in Minuten und war nicht entwickelt, um jeden Tag zu wecken x Sekunden, um etwas auszuführen. Führen Sie Ihre wiederkehrende Aufgabe innerhalb einer Schleife aus, und sie sollte tun, was Sie brauchen:

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

32voto

Andrew Punkte 736

Sie brauchen nicht zwei Cron-Einträge, sondern können sie in einem Eintrag unterbringen:

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

also in Ihrem Fall:

* * * * * /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'\''"

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X