Einführung in die Entwicklung von Webanwendungen auf PSGI / Plack. Teil 3. Starman

  • Tutorial
Fortsetzung der Artikelserie zu PSGI / Plack. Ein Preforking-Starman-PSGI-Server wird ausführlicher erläutert.

Mit Genehmigung des Autors des Artikels und des Chefredakteurs der Zeitschrift PragmaticPerl.com . Der Originalartikel befindet sich hier.

Starman?


Der Autor dieses Servers (Tatsuhiko Miyagawa) sagt Folgendes über ihn:

„Der Name Starman stammt aus dem Song Star HA Otoko der japanischen Rockband Unicorn (Yes, Unicorn). David Bowie hat auch ein Lied mit dem gleichen Namen, Starman - der Name des Charakters des japanischen Kultspiels Earthbound, der Name eines musikalischen Themas von Super Mario Brothers.

Ich habe es satt, Perl-Module wie HTTP :: Server :: PSGI :: How :: Its :: Written :: With :: What :: Module zu benennen, und deshalb nennen die Leute es im IRC HSPHIWWWM. Dies ist schlecht ausgeprägt und schafft Probleme für Anfänger. Ja, vielleicht bin ich ein Versehen. Die Zeit wird zeigen."

Mit dem Namen aussortiert. Jetzt werden wir uns mit dem Server selbst befassen.


Vorgabeln?


Das Preforking-Modell von Starman ähnelt den leistungsstärksten Unix-Servern. Es verwendet ein Modell vorab laufender Prozesse. Er startet auch automatisch den Pool der Arbeiter neu und entfernt seine Zombie-Prozesse.

Plack-Anwendung


Diesmal ist die Plack-Anwendung völlig elementar:

use strict;
use warnings;
use Plack;
use Plack::Builder;
use Plack::Request;
sub body {
    return 'body';
}
sub body2 {
    return shift;
}
my $app = sub {
    my $env = shift;
    my $req = Plack::Request->new($env);
    my $res = $req->new_response(200);
    $res->body(body());
    return $res->finalize();
};
my $main_app = builder {
    mount "/" => builder { $app };
};

Wenn Sie unter Starman entwickeln, müssen Sie einen sehr wichtigen Punkt seiner Arbeit verstehen. Stellen Sie sich zum Beispiel eine Datenbankverbindung vor. Um Zeit und Codezeilen zu sparen, wird die Verbindungsinitialisierung häufig an den Anfang des Skripts gestellt. Dies gilt für CGI und manchmal für FastCGI. Dies ist bei PSGI nicht der Fall. Und hier ist warum. Wenn der Server gestartet wird, wird dieser Code für jeden Worker genau einmal ausgeführt. Und die Gefahr der Situation besteht darin, dass die Anwendung zunächst wie gewohnt funktioniert, bis die Verbindung entweder durch Zeitüberschreitung oder aus einem anderen Grund abstürzt. Bei asynchronen Servern können Sie am Anfang des Anwendungscodes den Verbindungspool initialisieren (Verbindung! = Verbindungspool).

Um dies zu bestätigen oder zu widerlegen, werden wir Änderungen am Anwendungscode vornehmen. Fügen Sie nach dem Import die folgende Zeile am Anfang des Codes hinzu:

warn 'AFTER IMPORT';


Jetzt sollte die Anwendung folgendermaßen aussehen:

use strict;
use warnings;
use Plack;
use Plack::Builder;
use Plack::Request;
warn 'AFTER IMPORT';
sub body {
    return 'body';
}
sub body2 {
    return shift;
}
my $app = sub {
    my $env = shift;
    my $req = Plack::Request->new($env);
    my $res = $req->new_response(200);
    $res->body(body());
    return $res->finalize();
};
my $main_app = builder {
    mount "/" => builder { $app };
};

Für die Reinheit des Experiments werden wir Starman mit einem Arbeiter mit dem folgenden Befehl ausführen:

starman --port 8080 --workers 1 app.psgi


Wobei app.psgi die Anwendung ist.

Unmittelbar nach Abschluss des Starts sehen wir in STDERR das folgende Bild:

noxx@noxx-inferno ~/perl/psgi $ starman --port 8080 app.psgi --workers 1
2013/06/02-15:05:31 Starman::Server (type Net::Server::PreFork) starting! pid(4204)
Resolved [*]:8080 to [::]:8080, IPv6
Not including resolved host [0.0.0.0] IPv4 because it will be handled by [::] IPv6
Binding to TCP port 8080 on host :: with IPv6
Setting gid to "1000 1000 4 24 27 30 46 107 125 1000 1001"
AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7.

Wenn Sie eine Anfrage an localhost: 8080 / senden, können Sie sicherstellen, dass in STDERR nichts Neues angezeigt wurde und der Server normal reagiert.
Führen Sie den folgenden Befehl aus, um sicherzustellen, dass der Worker wirklich einer ist:

ps uax | grep starman


Ergebnis:

noxx      4204  0.6  0.1  57836 11264 pts/3    S+   15:05   0:00 starman master --port 8080 app.psgi --workers 1
noxx      4205  0.2  0.1  64708 13164 pts/3    S+   15:05   0:00 starman worker --port 8080 app.psgi --workers 1
noxx      4213  0.0  0.0  13580   940 pts/4    S+   15:05   0:00 grep --colour=auto starman

Es gibt zwei Prozesse. Tatsächlich ist der Arbeiter jedoch nur einer von ihnen. Lassen Sie uns ein weiteres Experiment durchführen. Führen Sie Starman mit drei Arbeitern.

starman --port 8080 --workers 3 app.psgi


Ergebnis:

2013/06/02-15:11:08 Starman::Server (type Net::Server::PreFork) starting! pid(4219)
Resolved [*]:8080 to [::]:8080, IPv6
Not including resolved host [0.0.0.0] IPv4 because it will be handled by [::] IPv6
Binding to TCP port 8080 on host :: with IPv6
Setting gid to "1000 1000 4 24 27 30 46 107 125 1000 1001"
AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7.
AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7.
AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7.


Alles ist richtig. Schauen wir uns nun die Liste der Prozesse an. Für mich sieht es so aus:

noxx      4219  0.1  0.1  57836 11264 pts/3    S+   15:11   0:00 starman master --port 8080 app.psgi --workers 3
noxx      4220  0.0  0.1  64460 12756 pts/3    S+   15:11   0:00 starman worker --port 8080 app.psgi --workers 3
noxx      4221  0.0  0.1  64460 12920 pts/3    S+   15:11   0:00 starman worker --port 8080 app.psgi --workers 3
noxx      4222  0.0  0.1  64460 12756 pts/3    S+   15:11   0:00 starman worker --port 8080 app.psgi --workers 3
noxx      4224  0.0  0.0  13580   936 pts/4    S+   15:12   0:00 grep --colour=auto starman


Ein Meister, drei Arbeiter.

Wir haben die Ausführungsreihenfolge herausgefunden. Fügen Sie nun eine weitere Warnung hinzu.

warn 'IN BUILDER'


Die Anwendung ist wie folgt:

use strict;
use warnings;
use Plack;
use Plack::Builder;
use Plack::Request;
warn 'AFTER IMPORT';
sub body {
    return 'body';
}
sub body2 {
    return shift;
}
my $app = sub {
    my $env = shift;
    my $req = Plack::Request->new($env);
    my $res = $req->new_response(200);
    $res->body(body());
    return $res->finalize();
};
my $main_app = builder {
    warn 'IN BUILDER';
    mount "/" => builder { $app };
};


Für einen Worker-Prozess sieht die Ausgabe folgendermaßen aus (Startbefehl: starman --port 8080 --workers 1 app.psgi):

2013/06/02-17:33:27 Starman::Server (type Net::Server::PreFork) starting! pid(4430)
Resolved [*]:8080 to [::]:8080, IPv6
Not including resolved host [0.0.0.0] IPv4 because it will be handled by [::] IPv6
Binding to TCP port 8080 on host :: with IPv6
Setting gid to "1000 1000 4 24 27 30 46 107 125 1000 1001"
AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7.
IN BUILDER at /home/noxx/perl/psgi/app.psgi line 23.


Wenn wir die Anwendung mit drei Mitarbeitern ausführen, wird in STDERR das folgende Bild angezeigt:

AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7.
IN BUILDER at /home/noxx/perl/psgi/app.psgi line 23.
AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7.
IN BUILDER at /home/noxx/perl/psgi/app.psgi line 23.
AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7.
IN BUILDER at /home/noxx/perl/psgi/app.psgi line 23.


Wenn Sie eine Anfrage an localhost: 8080 / stellen, können Sie leicht sicherstellen, dass in STDERR nichts Neues angezeigt wurde.

Die folgenden Schlussfolgerungen können gezogen werden:

Diese Aktion wird ausgeführt, wenn die Anwendung gestartet wird. Dies gilt sowohl für den Start des Skripts als auch für den Builder-Abschnitt, falls vorhanden.
Diese Aktion wird bei Serveranforderungen nicht ausgeführt.
Starman-Workflows werden nacheinander gestartet.
Dies ermöglicht das Erstellen schwerer Objekte sowohl zu Beginn des Skripts als auch im Builder-Teil.

Und jetzt fügen wir dem Code eine weitere Warnung des folgenden Formulars hinzu:

warn 'REQUEST';


Und lassen Sie uns den Antrag auf das folgende Formular bringen:

use strict;
use warnings;
use Plack;
use Plack::Builder;
use Plack::Request;
warn 'AFTER IMPORT';
sub body {
    return 'body';
}
sub body2 {
    return shift;
}
my $app = sub {
    warn 'REQUEST';
    my $env = shift;
    my $req = Plack::Request->new($env);
    my $res = $req->new_response(200);
    $res->body(body());
    return $res->finalize();
};
my $main_app = builder {
    warn 'IN BUILDER';
    mount "/" => builder { $app };
};


Führen Sie nun die Anwendung mit einem Workflow aus (starman --port 8080 --workers 1 app.psgi). Bisher hat sich nichts geändert:

AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7.
IN BUILDER at /home/noxx/perl/psgi/app.psgi line 24.


Es lohnt sich jedoch, einen neuen Eintrag in STDERR anzufordern.

REQUEST at /home/noxx/perl/psgi/app.psgi line 16.


Zusammenfassend. Bei jeder Anforderung an starman wird nur der Code der Anwendung selbst ausgeführt (es lohnt sich, sich an return sub ... zu erinnern), aber beim Start wird dieser Code nicht ausgeführt.

Nehmen wir an, ein Prozess ist gescheitert. Fügen Sie die folgende Zeile hinzu, um sub ... zurückzugeben:

die("DIED");


Als Ergebnis sollten Sie eine Bewerbung mit folgendem Formular erhalten:

use strict;
use warnings;
use Plack;
use Plack::Builder;
use Plack::Request;
warn 'AFTER IMPORT';
sub body {
    return 'body';
}
sub body2 {
    return shift;
}
my $app = sub {
    warn 'REQUEST';
    my $env = shift;
    my $req = Plack::Request->new($env);
    my $res = $req->new_response(200);
    $res->body(body());
    die("DIED");
    return $res->finalize();
};
my $main_app = builder {
    warn 'IN BUILDER';
    mount "/" => builder { $app };
};

Wir starten die Anwendung mit einem Workflow, stellen eine Anfrage. Die Anwendung stürzt natürlich ab. Aber das Ergebnis ist merkwürdig, wenn auch logisch. Die Anwendung stürzte nicht ab, nur zwei Benachrichtigungen wurden in STDERR angezeigt:

REQUEST at /home/noxx/perl/psgi/app.psgi line 16.
DIED at /home/noxx/perl/psgi/app.psgi line 21.


Ersetzen Sie nun die Matrize ('DIED'); an der Ausfahrt 1; Führen Sie Starman aus und senden Sie eine Anfrage an localhost: 8080 /. Jetzt ist der Workflow gesunken. Dies ist aus STDERR ersichtlich, das nun folgendermaßen aussehen wird:

REQUEST at /home/noxx/perl/psgi/app.psgi line 16, <$read> line 7.
AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7, <$read> line 8.
IN BUILDER at /home/noxx/perl/psgi/app.psgi line 26, <$read> line 8.


Nach jeder Anforderung wird der Workflow abgebrochen, aber der Master-Prozess wird ihn auslösen.

Verlasse Starman für eine Weile. Versuchen wir, diese Anwendung beispielsweise unter Twiggy auszuführen. Wenn dieser Server nicht installiert ist, ist es Zeit, ihn zu installieren. Das Paket heißt Twiggy.

Führen Sie nach der Installation von Twiggy unsere Anwendung mit dem folgenden Befehl aus:
twiggy --port 8080 app.psgi


Und wir werden eine Anfrage stellen. Alle mögen Starman, bis auf eine Funktion. Der Server ist gefallen.

noxx@noxx-inferno ~/perl/psgi $ twiggy --port 8080 app.psgi
AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7.
IN BUILDER at /home/noxx/perl/psgi/app.psgi line 26.
REQUEST at /home/noxx/perl/psgi/app.psgi line 16, <> line 5.
noxx@noxx-inferno ~/perl/psgi $


Das liegt natürlich daran, dass Twiggy keinen Master-Prozess hat und es niemanden gibt, der einen gefallenen Arbeiter großzieht. Und jetzt folgt von hier aus ein sehr wichtiger Punkt, der berücksichtigt werden muss. Vor dem Neustart des Servers müssen Sie sicherstellen, dass der Code korrekt ist und keine Syntaxfehler enthält. Wenn Sie versuchen, eine Anwendung, die einen Fehler enthält, mit Starman zu starten, treten mehrere Ereignisse in der folgenden Reihenfolge auf:

  • Starman startet den Master-Prozess und prüft, ob Workflows gestartet werden können.
  • Starman startet Workflows und übergibt den Anwendungscode an die Ausführung.
  • Die Arbeitsabläufe beginnen zu fallen, und der Master beginnt, sie aufzunehmen.
  • Die Last steigt unglaublich und in sehr kurzer Zeit.


Laufzeitfehler sind nicht so kritisch. Entfernen wir die Tropfen aus der Anwendung und bringen sie fast in ihre ursprüngliche Form:

use strict;
use warnings;
use Plack;
use Plack::Builder;
use Plack::Request;
warn 'AFTER IMPORT';
sub body {
    return 'body';
}
sub body2 {
    return shift;
}
my $app = sub {
    warn 'REQUEST';
    my $env = shift;
    my $req = Plack::Request->new($env);
    my $res = $req->new_response(200);
    $res->body(body());
    return $res->finalize();
};
my $main_app = builder {
    warn 'IN BUILDER';
    mount "/" => builder { $app };
};

Versuchen Sie Folgendes in dieser Reihenfolge:
  • Wir bringen den Antrag in seine ursprüngliche Form.
  • Führen Sie es mit Starman aus.
  • Wir werden eine Anfrage stellen.
  • Ändern Sie den Anwendungscode und speichern Sie ihn.
  • Ohne die Anwendung neu zu starten, werden wir sie erneut anfordern.

Ergebnis:

curl localhost:8080/
body

Wir speichern die Anwendung, ändern die Körperfunktion. Lassen Sie jetzt zum Beispiel niemanden zurückgeben. Wir stellen eine Anfrage - das Ergebnis ist, wenn wir den Server nicht neu gestartet haben, wie folgt:
curl localhost:8080/
body

Ein Neustart lohnt sich jedoch, da sich alles ändert:

curl localhost:8080/
nobody


Eine weitere wichtige Schlussfolgerung. Um die Anwendung zu aktualisieren, reicht es nicht aus, die Dateien zu ändern. Sie müssen den Server neu starten. Oder senden Sie ein spezielles Signal an den Master-Prozess.

Starman und Signale


Stellen Sie sich vor, wir haben eine große PSGI-Anwendung, die nicht gestoppt werden kann, weil Wir haben ziemlich schwere Bibliotheken, die beispielsweise zehn Sekunden in den Speicher laden.

Wir wiederholen die vorherige Aktionskette, jedoch mit einer Änderung. Fügen Sie das Senden von Signalen hinzu.

Das Signal, das Starman zum erneuten Lesen auffordert, ist SIGHUP.

Der Befehl zum Senden dieses Signals sieht folgendermaßen aus:

kill -s SIGHUP [pid]

Sie können den PID-Wert mit dem folgenden Befehl abrufen:

ps uax | grep starman | grep master


Beispiel für die Befehlsausgabe:

noxx      6214  0.8  0.1  54852 10288 pts/3    S+   19:17   0:00 starman master --port 8080 --workers 1 app.psgi

pid = 6214. Wir

überprüfen die Antwortanforderung. Ersetzen Sie niemanden durch body und führen Sie die Anwendung aus.

Ergebnis:
curl localhost:8080
body
kill -s SIGHUP 6214
curl localhost:8080
nobody

Währenddessen können wir in STDERR Starman Folgendes sehen:

AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7.
IN BUILDER at /home/noxx/perl/psgi/app.psgi line 24.
REQUEST at /home/noxx/perl/psgi/app.psgi line 16.
Sending children hup signal
AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7, <$read> line 2.
IN BUILDER at /home/noxx/perl/psgi/app.psgi line 24, <$read> line 2.
REQUEST at /home/noxx/perl/psgi/app.psgi line 16, <$read> line 2.

Daher gibt es zwei Möglichkeiten, eine PSGI-Anwendung zu aktualisieren. Welche Sie wählen müssen, hängt von der Aufgabe ab.

Angenommen, Sie benötigen einen anderen Workflow. Es kann auf zwei Arten hinzugefügt werden. Starten Sie den Server mit dem erforderlichen Parameter (--workers) neu oder senden Sie ein Signal. Das Signal zum Hinzufügen eines Workflows lautet TTIN und zum Löschen TTOU. Wenn wir den Server sicher vollständig stoppen möchten, können wir das QUIT-Signal verwenden.

Also. Starten Sie unsere Anwendung mit einem Workflow:
starman --port 8080 --workers 1


Dann fügen wir zwei Prozesse hinzu, indem wir den folgenden Befehl zweimal ausführen:
kill -s TTIN 6214


Starman-Prozessliste:

noxx      6214  0.0  0.1  54852 10304 pts/3    S+   19:17   0:00 starman master --port 8080 --workers 1 app.psgi
noxx      6221  0.0  0.1  64724 13188 pts/3    S+   19:19   0:00 starman worker --port 8080 --workers 1 app.psgi
noxx      6233  0.0  0.1  64476 12872 pts/3    S+   19:26   0:00 starman worker --port 8080 --workers 1 app.psgi
noxx      6239  2.0  0.1  64480 12872 pts/3    S+   19:29   0:00 starman worker --port 8080 --workers 1 app.psgi

STDERR ist bereits bekannt:

AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7, <$read> line 4.
IN BUILDER at /home/noxx/perl/psgi/app.psgi line 24, <$read> line 4.
AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7, <$read> line 4.
IN BUILDER at /home/noxx/perl/psgi/app.psgi line 24, <$read> line 4.


Dann entfernen wir einen Prozess:

kill -s TTOU 6214


Wir können sehen, dass das Team einen Effekt hatte, indem wir uns die Liste der Prozesse ansehen:
noxx      6214  0.0  0.1  54852 10304 pts/3    S+   19:17   0:00 starman master --port 8080 --workers 1 app.psgi
noxx      6221  0.0  0.1  64724 13188 pts/3    S+   19:19   0:00 starman worker --port 8080 --workers 1 app.psgi
noxx      6233  0.0  0.1  64476 12872 pts/3    S+   19:26   0:00 starman worker --port 8080 --workers 1 app.psgi
noxx      6238  0.0  0.0  13584   936 pts/4    S+   19:29   0:00 grep --colour=auto starman

In STDERR wird dies jedoch nicht angezeigt.

Und jetzt werden wir die Arbeit unserer Bewerbung abschließen, indem wir ihm ein QUIT-Signal senden.

kill -s QUIT 6214

Der Server schreibt in STDERR:

2013/06/02-19:32:15 Received QUIT. Running a graceful shutdown
Sending children hup signal
2013/06/02-19:32:15 Worker processes cleaned up
2013/06/02-19:32:15 Server closing!


Und fährt herunter.

Das ist alles, was Sie über Starman wissen müssen, um loszulegen.

Ein weiteres wichtiges Detail bleibt bestehen. Wenn Sie Starman starten, können Sie das erforderliche Modul über den Schalter -M angeben, um den Master-Prozess zu starten. Aber dann beginnt die folgende Einschränkung zu funktionieren. Über -M (-MDBI -MDBIx :: Class) geladene Module werden während SIGHUP nicht erneut gelesen.

Eine weitere nützliche Serveroption ist -I. Hier können Sie den Pfad zu Perl-Modulen angeben, bevor Sie den Master-Prozess starten. Starman kann auch mit Unix-Sockets arbeiten. Diese Funktion wird jedoch in den folgenden Artikeln ausführlicher erläutert, beginnend mit dem Artikel über die Bereitstellung und Verwaltung von Plack.

Und schließlich das Flag -E, das die Umgebungsvariable (PLACK_ENV) in den übergebenen Zustand versetzt.

Der nächste Artikel befasst sich mit dem asynchronen PSGI-Server - Twiggy.

Dmitry Shamatrin

Jetzt auch beliebt: