Sergio ist richtig. Ihre App ist zu diesem Zeitpunkt wahrscheinlich besser als das traditionelle Apache/Passenger-Modell. Wenn Sie den geradlinigen Weg gehen, insbesondere auf Single-Thread-Plattformen wie Ruby, können Sie NIEMALS etwas blockieren, sei es die DB, Cache-Server, andere HTTP-Anfragen, die Sie vielleicht machen - nichts.
Dies macht asynchrone (evented) Programmierung schwieriger - es ist einfach zu blockieren, in der Regel in Form von synchronen Festplatten-I/O oder DNS-Auflösungen. Nicht-blockierende (evented) Frameworks wie Nodejs sind insofern vorsichtig, als sie Ihnen (fast) nie einen Framework-Funktionsaufruf liefern, der blockiert, sondern alles wird mit Callbacks (einschließlich DB-Abfragen) behandelt.
Dies könnte einfacher sein, sichtbar zu machen, wenn man sich im Herzen eines Single-Threaded-non-blocking-Server aussehen:
while(wait_on_sockets(/* list<socket> */ &$sockets, /* event */ &$what, $timeout)) {
foreach($socketsThatHaveActivity as $fd in $sockets) {
if($what == READ) { // There is data availabe to read from this socket
$data = readFromSocket($fd);
processDataQuicklyWithoutBlocking($data);
}
elseif ($what == WRITE && $data = dataToWrite($fd)) { // This socket is ready to be written to (if we have any data)
writeToSocket($fd, $data);
}
}
}
Was Sie sehen, über die Ereignisschleife aufgerufen wird. wait_on_sockets
wird normalerweise vom Betriebssystem in Form eines Systemaufrufs bereitgestellt, z. B. "select", "poll", "epoll" oder "kqueue". Wenn processDataQuicklyWithoutBlocking zu lange dauert, füllt sich der vom Betriebssystem unterhaltene Netzwerkpuffer der Anwendung (neue Anforderungen, eingehende Daten usw.) und führt dazu, dass er neue Verbindungen zurückweist und bestehende Verbindungen löscht, da $ socketThatHaveActivity nicht schnell genug behandelt wird . Dies unterscheidet sich von einem Threaded-Server (zB einer typischen Apache-Installation) darin, dass jede Verbindung mit einem separaten Thread/Prozess bedient wird. Eingehende Daten werden sofort in die App eingelesen und ausgehende Daten werden ohne Verzögerung gesendet . Wenn Sie beispielsweise eine DB-Abfrage ausführen, fügen Sie die Socket-Verbindung des Datenbankservers zur Liste der überwachten Sockets ($ sockets) hinzu, also auch dann, wenn Ihre Abfrage ausgeführt wird eine Weile, Ihr (einziger) Thread ist nicht an dieser einen Buchse blockiert. Vielmehr bieten sie einen Rückruf:
$db.query("...sql...", function($result) { ..handle result ..});
Wie Sie oben sehen können, db.query kehrt sofort mit absolut keine Blockierung auf dem db whatsoever Server.Dies bedeutet auch Sie häufig Code wie diesen zu schreiben, es sei denn, die Programmiersprache selbst Asynchron-Funktionen (wie die neue C#) unterstützt:
$db.query("...sql...", function($result) { $httpResponse.write($result); $connection.close(); });
Die nie immer Block Regel kann etwas entspannt sein, wenn Sie viele Prozesse die jeweils eine Ereignisschleife ausführen (normalerweise die Art, einen Knotencluster auszuführen) oder einen Threadpool verwenden, um die Ereignisschleife aufrechtzuerhalten (javas Anlegesteg, netty usw., Sie können Ihre eigenen in C/C++ schreiben). Während ein Thread auf etwas blockiert ist, können andere Threads weiterhin die Ereignisschleife ausführen. Aber unter einer Last, die hoch genug ist, würden selbst diese nicht funktionieren. Also niemals blockieren in einem ausgeglichenen Server.
Also, wie Sie sehen können, versuchen in der Regel evented Server ein anderes Problem zu lösen - sie können eine große Anzahl von offenen Verbindungen haben. Wo sie sich auszeichnen, ist es, nur Bytes mit Lichtberechnungen herumzuschieben (z. B. Comet-Server, Caches wie Memcached, Lack, Proxies wie Nginx, Tintenfisch usw.). Es ist nichts wert, dass, obwohl sie besser skalieren, die Antwortzeiten im Allgemeinen zunehmen (nichts ist besser, als einen ganzen Thread für eine Verbindung zu reservieren). Natürlich ist es möglicherweise ökonomisch/rechnerisch nicht möglich, die gleiche Anzahl von Threads als # von gleichzeitigen Verbindungen auszuführen.
Jetzt zurück zu Ihrem Problem - ich würde Ihnen empfehlen, immer noch Nginx herum zu halten, da es hervorragend bei Verbindungsmanagement (das ist ereignisbasiert) ist - bedeutet in der Regel Umgang mit HTTP keep-alives, SSL usw. Sie sollten dann verbinden mit FastCGI zu Ihrer Rails-App, wo Sie noch Mitarbeiter ausführen müssen, aber Ihre App nicht neu schreiben müssen, um vollständig ausgeglichen zu werden. Sie sollten Nginx auch statischen Inhalt liefern lassen - keinen Grund, Ihre Rails-Mitarbeiter mit etwas zu beschäftigen, das Nginx normalerweise besser machen kann. Dieser Ansatz ist im Allgemeinen besser skalierbar als Apache/Passenger, insbesondere wenn Sie eine stark frequentierte Website betreiben.
Wenn Sie Ihre gesamte App schreiben können, um ausgeglichen zu werden, dann großartig, aber ich habe keine Ahnung, wie einfach oder schwierig das in Ruby ist.
Wow .. danke für die ausführliche Antwort Tejas. Also die Benchmarks, die ich aus dem Netz gelesen habe ... sind das für ein ganz anderes Anwendungsgenre? Thins eigene Seite gibt eine Rails-App als Beispiel-App für Thin. http://code.macournoyer.com/thin/. Ich hatte den Eindruck, dass ich den Passagier einfach durch dünn ersetzen konnte und alles wird gut aussehen. –
Solange Sie nicht irgendwo blockieren, sollten Sie in der Lage sein, diese Benchmarks neu zu erstellen. – tejas