Wie führe ich einen Befehl durchschnittlich fünfmal pro Sekunde aus?

21

Ich habe ein Befehlszeilenskript, das einen API-Aufruf ausführt und eine Datenbank mit den Ergebnissen aktualisiert.

Ich habe ein Limit von 5 API-Aufrufen pro Sekunde beim API-Anbieter. Die Ausführung des Skripts dauert mehr als 0,2 Sekunden.

  • Wenn ich den Befehl nacheinander ausführe, läuft er nicht schnell genug und ich rufe nur 1 oder 2 APIs pro Sekunde auf.
  • Wenn ich den Befehl nacheinander, aber gleichzeitig von mehreren Terminals aus ausführe, überschreite ich möglicherweise die Grenze von 5 Anrufen / Sekunde.

Gibt es eine Möglichkeit, Threads so zu orchestrieren, dass mein Befehlszeilenskript fast genau fünfmal pro Sekunde ausgeführt wird?

Zum Beispiel würde etwas mit 5 oder 10 Threads ausgeführt, und kein Thread würde das Skript ausführen, wenn ein vorheriger Thread es vor weniger als 200 ms ausgeführt hat.

Benjamin
quelle
Alle Antworten hängen von der Annahme ab, dass Ihr Skript in der Reihenfolge beendet wird, in der es aufgerufen wird. Ist es für Ihren Anwendungsfall akzeptabel, wenn sie nicht in Ordnung sind?
Cody Gustafson
@CodyGustafson Es ist absolut akzeptabel, wenn sie nicht in Ordnung sind. Ich glaube nicht, dass es eine solche Annahme in der akzeptierten Antwort gibt, zumindest?
Benjamin
Was passiert, wenn Sie die Anzahl der Anrufe pro Sekunde überschreiten? Wenn der API-Anbieter drosselt, brauchen Sie keinen Mechanismus an Ihrem Ende ... oder?
Floris
@Floris Sie geben eine Fehlermeldung zurück, die in einer Ausnahme im SDK übersetzt wird. Erstens bezweifle ich, dass der API-Anbieter froh sein wird, wenn ich 50 Throttle-Nachrichten pro Sekunde generiere (Sie sollten entsprechend auf solche Nachrichten reagieren), und zweitens verwende ich die API gleichzeitig für andere Zwecke, so dass ich Ich möchte nicht das Limit erreichen, das tatsächlich etwas höher ist.
Benjamin

Antworten:

25

Auf einem GNU-System und wenn Sie haben pv, können Sie Folgendes tun:

cmd='
   that command | to execute &&
     as shell code'

yes | pv -qL10 | xargs -n1 -P20 sh -c "$cmd" sh

Der -P20soll höchstens 20 $cmdgleichzeitig ausführen .

-L10 begrenzt die Rate auf 10 Bytes pro Sekunde, also 5 Zeilen pro Sekunde.

Wenn Ihr $cmds zwei langsam wird und das Limit von 20 erreicht, xargshört das Lesen auf, bis $cmdmindestens eine Instanz zurückkehrt. pvschreibt weiterhin mit der gleichen Rate an die Pipe, bis die Pipe voll ist (was unter Linux mit einer Standard-Pipe-Größe von 64 KB fast 2 Stunden dauert).

An diesem Punkt pvwird aufhören zu schreiben. Aber selbst dann, wenn xargsdas Lesen fortgesetzt wird, pvwird versucht, alle Zeilen, die es früher hätte senden sollen, so schnell wie möglich einzuholen und zu senden, um insgesamt einen Durchschnitt von 5 Zeilen pro Sekunde aufrechtzuerhalten.

Das heißt, solange es mit 20 Prozessen möglich ist, diese 5 Durchläufe pro Sekunde im Durchschnitt zu erreichen, wird es dies tun. Wenn das Limit erreicht ist, wird die Rate, mit der neue Prozesse gestartet werden, nicht vom Timer von pv gesteuert, sondern von der Rate, mit der frühere cmd-Instanzen zurückkehren. Wenn zum Beispiel derzeit 20 ausgeführt werden und 10 Sekunden vergangen sind und 10 von ihnen sich dafür entscheiden, alle gleichzeitig zu beenden, werden sofort 10 neue gestartet.

Beispiel:

$ cmd='date +%T.%N; exec sleep 2'
$ yes | pv -qL10 | xargs -n1 -P20 sh -c "$cmd" sh
09:49:23.347013486
09:49:23.527446830
09:49:23.707591664
09:49:23.888182485
09:49:24.068257018
09:49:24.338570865
09:49:24.518963491
09:49:24.699206647
09:49:24.879722328
09:49:25.149988152
09:49:25.330095169

Im Durchschnitt sind es 5-mal pro Sekunde, auch wenn die Verzögerung zwischen zwei Läufen nicht immer genau 0,2 Sekunden beträgt.

Mit ksh93(oder mit, zshwenn Ihr sleepBefehl Sekundenbruchteile unterstützt):

typeset -F SECONDS=0
n=0; while true; do
  your-command &
  sleep "$((++n * 0.2 - SECONDS))"
done

Damit ist die Anzahl der gleichzeitigen your-commands jedoch nicht begrenzt.

Stéphane Chazelas
quelle
Nach ein paar Tests pvscheint der Befehl genau das zu sein, wonach ich gesucht habe, ich könnte nicht besser hoffen! Nur in dieser Zeile: yes | pv -qL10 | xargs -n1 -P20 sh -c "$cmd" shIst die letzte nicht shüberflüssig?
Benjamin
1
@Benjamin Diese Sekunde shist für das $0in deinem $cmdDrehbuch. Es wird auch in Fehlermeldungen von der Shell verwendet. Ohne es $0wäre yvon yes, so würden Sie Fehlermeldungen wie erhalten y: cannot execute cmd... Sie könnten auch tunyes sh | pv -qL15 | xargs -n1 -P20 sh -c "$cmd"
Stéphane Chazelas
Ich kämpfe darum, das Ganze in verständliche Teile zu zerlegen, TBH! In Ihrem Beispiel haben Sie dies zuletzt entfernt sh. und in meinen Tests kann ich, wenn ich es entferne, keinen Unterschied feststellen!
Benjamin
@Benjamin. Es ist nicht kritisch. Es wird nur anders machen, wenn Sie $cmdes verwenden $0(warum sollte es?) Und für Fehlermeldungen. Versuchen Sie es zum Beispiel mit cmd=/; ohne die zweite sh, dann würden Sie so etwas wie sehen y: 1: y: /: Permission deniedstattsh: 1: sh: /: Permission denied
Stéphane Chazelas
Ich habe ein Problem mit Ihrer Lösung: Sie funktioniert ein paar Stunden lang und wird dann irgendwann ohne Fehler beendet. Könnte dies damit zusammenhängen, dass die Pfeife voll wird und unerwartete Nebenwirkungen hat?
Benjamin
4

Wenn Ihr Befehl weniger als 1 Sekunde dauert, können Sie einfach 5 Befehle pro Sekunde starten. Offensichtlich ist das sehr platzen.

while sleep 1
do    for i in {1..5}
      do mycmd &
      done
done

Wenn Ihr Befehl länger als 1 Sekunde dauert und Sie die Befehle verteilen möchten, können Sie es versuchen

while :
do    for i in {0..4}
      do  sleep .$((i*2))
          mycmd &
      done
      sleep 1 &
      wait
done

Alternativ können Sie 5 separate, unabhängig voneinander ablaufende Schleifen mit einem Minimum von 1 Sekunde erstellen.

for i in {1..5}
do    while :
      do   sleep 1 &
           mycmd &
           wait
      done &
      sleep .2
done
meuh
quelle
Ganz nette Lösung auch. Ich mag die Tatsache, dass es einfach ist und genau fünfmal pro Sekunde ausgeführt wird, aber es hat den Nachteil, dass 5 Befehle gleichzeitig gestartet werden (anstatt alle 200 ms), und es fehlt möglicherweise die Sicherheit, dass höchstens n Threads gleichzeitig ausgeführt werden !
Benjamin
@Benjamin Ich habe einen 200ms Schlaf in der Schleife der zweiten Version hinzugefügt. Diese zweite Version kann nicht mehr als 5 cmds gleichzeitig ausführen, da wir nur jeweils 5 starten und dann auf alle warten.
Meuh
Das Problem ist, dass nicht mehr als 5 pro Sekunde gestartet werden dürfen. Wenn die Ausführung aller Skripte plötzlich länger als 1 Sekunde dauert, ist das API-Limit noch nicht erreicht. Und wenn Sie auf alle warten, blockiert ein einziges Blockierungsskript alle anderen?
Benjamin
@Benjamin Sie können also 5 unabhängige Schleifen mit einer Mindestruhezeit von 1 Sekunde ausführen (siehe 3. Version).
Meuh
2

Mit einem C-Programm

Sie können beispielsweise einen Thread verwenden, der nach einer Weile 0,2 Sekunden lang ruht

#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>

pthread_t tid;

void* doSomeThing() {
    While(1){
         //execute my command
         sleep(0.2)
     } 
}

int main(void)
{
    int i = 0;
    int err;


    err = pthread_create(&(tid), NULL, &doSomeThing, NULL);
    if (err != 0)
        printf("\ncan't create thread :[%s]", strerror(err));
    else
        printf("\n Thread created successfully\n");



    return 0;
}

benutze es, um zu wissen, wie man einen Thread erstellt: erstelle einen Thread (dies ist der Link, über den ich diesen Code eingefügt habe)

Couim
quelle
Vielen Dank für Ihre Antwort, obwohl ich im Idealfall nach etwas gesucht habe, das keine C-Programmierung beinhaltet, sondern nur vorhandene Unix-Tools verwendet!
Benjamin
Ja, die Stackoverflow Antwort auf diese könnte zum Beispiel sein , einen Token - Bucket zwischen mehreren Worker - Threads geteilt zu verwenden, aber auf Unix.SE fragt , schlägt eher einen „Power - User“ und nicht als „Programmierer“ Ansatz gesucht :-) Trotzdem ccist ein vorhandenes Unix-Tool, und das ist nicht viel Code!
Steve Jessop
1

Mit node.js können Sie einen einzelnen Thread starten , der das Bash-Skript alle 200 Millisekunden ausführt, unabhängig davon, wie lange die Rückmeldung dauert, da die Rückmeldung über eine Rückruffunktion erfolgt .

var util = require('util')
exec = require('child_process').exec

setInterval(function(){
        child  = exec('fullpath to bash script',
                function (error, stdout, stderr) {
                console.log('stdout: ' + stdout);
                console.log('stderr: ' + stderr);
                if (error !== null) {
                        console.log('exec error: ' + error);
                }
        });
},200);

Dieses Javascript wird alle 200 Millisekunden ausgeführt und die Antwort wird über die Rückruffunktion abgerufen function (error, stdout, stderr).

Auf diese Weise können Sie steuern, dass die 5 Aufrufe pro Sekunde niemals überschritten werden, unabhängig davon, wie langsam oder schnell die Ausführung des Befehls ist oder wie lange auf eine Antwort gewartet werden muss.

jcbermu
quelle
Diese Lösung gefällt mir: Sie startet in regelmäßigen Abständen genau 5 Befehle pro Sekunde. Der einzige Nachteil, den ich sehen kann, ist, dass es keine Sicherung dafür gibt, dass höchstens n Prozesse gleichzeitig ausgeführt werden! Wenn dies etwas ist, das Sie leicht einschließen könnten? Ich kenne mich nicht mit node.js aus.
Benjamin
0

Ich habe pveinige Zeit die auf Stéphane Chazelas basierende Lösung verwendet, aber festgestellt, dass sie nach einiger Zeit zufällig (und lautlos) beendet wurde, irgendwo zwischen einigen Minuten und einigen Stunden. - Bearbeiten: Der Grund war, dass mein PHP-Skript gelegentlich aufgrund einer überschrittenen maximalen Ausführungszeit beim Beenden mit dem Status 255 abgestürzt ist.

Also habe ich beschlossen, ein einfaches Befehlszeilentool zu schreiben , das genau das tut, was ich brauche.

Mein ursprüngliches Ziel zu erreichen ist so einfach wie:

./parallel.phar 5 20 ./my-command-line-script

Es werden fast genau 5 Befehle pro Sekunde gestartet, es sei denn, es gibt bereits 20 gleichzeitige Prozesse. In diesem Fall werden die nächsten Ausführungen übersprungen, bis ein Slot verfügbar wird.

Dieses Tool reagiert nicht auf einen Exit mit Status 255.

Benjamin
quelle