Debugging busy processes

Manchmal können wir mit dem Befehl php start.php status Prozesse im busy-Status sehen, was bedeutet, dass der entsprechende Prozess geschäftliche Aufgaben bearbeitet. Unter normalen Umständen sollte der Prozess nach Abschluss der Aufgaben wieder in den idle-Status zurückkehren. Wenn der Prozess jedoch dauerhaft im Busy-Status bleibt und nicht in den Idle-Status zurückkehrt, deutet dies darauf hin, dass es innerhalb des Prozesses Blockaden oder Schleifen gibt. Wir können die folgenden Methoden verwenden, um das Problem zu lokalisieren.

Lokalisierung mit strace + lsof

1. Finde die PID des busy Prozesses im Status
Führe php start.php status aus, um die folgende Anzeige zu erhalten.

Die pid des busy Prozesses in der Abbildung ist 11725 und 11748.

2. Strace zur Verfolgung des Prozesses nutzen
Wähle eine der PIDs (hier wählen wir 11725), und führe strace -ttp 11725 aus. Die Anzeige sieht wie folgt aus:

Es ist zu sehen, dass der Prozess ständig die Systemaufrufe poll([{fd=16, events=.... durchführt, was darauf hinweist, dass auf ein lesbares Ereignis des Deskriptors mit der Nummer 16 gewartet wird. Dies bedeutet, dass auf Daten von diesem Deskriptor gewartet wird.

Wenn keine Systemaufrufe angezeigt werden, behalte das aktuelle Terminal und öffne ein neues Terminal, um kill -SIGALRM 11725 auszuführen (sende ein Alarmsignal an den Prozess) und überprüfe, ob das strace-Terminal eine Reaktion zeigt und ob es bei einem bestimmten Systemaufruf blockiert ist. Wenn weiterhin keine Systemaufrufe angezeigt werden, könnte das Programm sich in einer endlosen Schleife befinden. Siehe den anderen Gründen am Ende der Seite, die dazu führen können, dass ein Prozess lange Zeit busy bleibt, Punkt 2 zur Lösung.

Wenn das System bei den Systemaufrufen epoll_wait oder select blockiert, ist das eine normale Situation, was darauf hinweist, dass der Prozess bereits im Idle-Zustand ist.

3. Verwende lsof, um die Prozessdeskriptoren anzuzeigen
Führe lsof -nPp 11725 aus und die Anzeige sieht wie folgt aus:

Der Deskriptor 16 entspricht dem Datensatz mit 16u (letzte Zeile). Der Deskriptor fd=16 ist eine TCP-Verbindung, die Remote-Adresse ist 101.37.136.135:80, was darauf hinweist, dass der Prozess auf eine HTTP-Ressource zugreift. Die Schleife poll([{fd=16, events=.... wartet ständig darauf, dass der HTTP-Server Daten zurücksendet, was erklärt, warum der Prozess im busy-Status ist.

Lösung:
Nachdem wir wissen, wo der Prozess blockiert ist, ist es einfacher, das Problem zu beheben. Zum Beispiel deutet die Lokalisierung oben darauf hin, dass das Geschäft auf eine curl-Funktion zugreift und die zugehörige URL lange Zeit keine Daten zurücksendet, was dazu führt, dass der Prozess ständig wartet. In diesem Fall kann der Anbieter der URL kontaktiert werden, um den Grund für die langsame Antwort herauszufinden. Gleichzeitig sollten beim curl-Aufruf Timeout-Parameter hinzugefügt werden, sodass nach 2 Sekunden ohne Antwort ein Timeout auftritt, um längere Blockaden zu vermeiden (auf diese Weise könnte der Prozess etwa 2 Sekunden lang im busy-Status sein).

Andere Gründe, die dazu führen, dass ein Prozess lange busy bleibt

Neben der Prozessblockade, die den Prozess busy macht, können auch die folgenden Gründe dazu führen, dass ein Prozess im busy-Status ist.

1. Geschäftsfehler führen zur ständigen Beendigung des Prozesses
Erscheinung: In diesem Fall ist die Systemlast recht hoch; der load average im status beträgt 1 oder mehr. Die Zahl exit_count des Prozesses ist sehr hoch und steigt ständig.
Lösung: Führe den Worker im Debug-Modus aus (php start.php start ohne -d), um die Fehler im Geschäft zu sehen und die Fehler zu beheben.

2. Unendliche Schleifen im Code
Erscheinung: Im top-Bildschirm ist zu sehen, dass der busy Prozess eine hohe CPU-Auslastung hat, und der Befehl strace -ttp pid zeigt keine Systemaufrufinformationen an.
Lösung: Siehe den Artikel von BirdBrother, um mit gdb und PHP Quellcode zu lokalisieren. Die Schritte sind ungefähr wie folgt zusammengefasst:

  1. php -v die Version anzeigen
  2. Lade den entsprechenden PHP-Version Quellcode herunter
  3. gdb --pid=Pir des busy Prozesses
  4. source php Quellcode Pfad/.gdbinit
  5. zbacktrace zeigt den Call-Stack an.
    Im letzten Schritt kannst du den aktuellen Ausführungs-Call-Stack der PHP-Codes sehen, wo sich die Endlosschleife befindet.
    Achtung: Wenn zbacktrace keinen Call-Stack anzeigt, könnte dein PHP beim Kompilieren nicht mit der Option -g kompiliert worden sein. Du musst PHP neu kompilieren und Workers neu starten, um die Lokalisierung durchzuführen.

3. Endloses Hinzufügen von Timern
Der Geschäftscode fügt ständig Timer hinzu, ohne sie zu löschen, was dazu führt, dass die Anzahl der Timer im Prozess ständig steigt und letztlich zu einem endlosen Betrieb von Timern führt. Zum Beispiel der folgende Code:

$worker = new Worker;
$worker->onConnect = function($con){
    Timer::add(10, function(){});
};
Worker::runAll();

Der obige Code wird einen Timer hinzufügen, wenn ein Client sich verbindet. Es gibt jedoch keine Logik im ganzen Geschäftsablauf, um den Timer zu löschen. So wird die Anzahl der Timer im Prozess mit der Zeit immer höher, was schließlich dazu führt, dass der Prozess unendlich Timer abarbeitet und im Busy-Status bleibt.
Der korrekte Code:

$worker = new Worker;
$worker->onConnect = function($con){
    $con->timer_id = Timer::add(10, function(){});
};
$worker->onClose = function($con){
    Timer::del($con->timer_id);
};
Worker::runAll();