2012-05-09 7 views
9

Ich hatte eine Rails 3 App auf Nginx/Passenger, die ich gerade nach Nginx/Thin (1.3.1) verschoben habe. Allerdings ist meine App jetzt deutlich langsamer als bei Passenger. Viele Anfragen haben auch eine Auszeit.Thin Server underperforming/Wie funktionieren gereinigte Webserver?

Thin ist ein geregelter Webserver. Von dem, was ich über "evented" -Webserver gelesen habe, gibt es kein Konzept von Arbeitern. Ein "Arbeiter" kümmert sich um alles. Wenn also eine Anfrage auf IO wartet, geht thin einfach zur nächsten Anfrage und so weiter. Eine Erklärung, die ich über "evented" -Server gelesen habe, besagt, dass "evented" -Server genauso gut oder besser funktionieren sollten als "workers" -basierte Server, da sie nur an Systemressourcen gebunden sind.

Allerdings ist meine CPU-Auslastung sehr gering. Meine Speicherbelegung ist auch sehr gering, und es passiert auch nicht viel IO. Meine App macht nur ein paar MySQL-Abfragen.

Was ist der Flaschenhals hier? Sollte mein Thin Server Anfragen nicht bearbeiten, bis die CPU 100% erreicht? Muss ich etwas anderes in meiner App tun, damit es mit einem ausgeglichenen Server besser funktioniert?

Antwort

13

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.

+0

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

+0

Solange Sie nicht irgendwo blockieren, sollten Sie in der Lage sein, diese Benchmarks neu zu erstellen. – tejas

3

Ja, Thin macht evented I/O, aber nur für HTTP-Teil. Dies bedeutet, dass es eingehende HTTP-Daten während der Verarbeitung einer Anfrage empfangen kann. Alle blockierenden E/A, die Sie während der Verarbeitung ausführen, blockieren jedoch weiterhin. Wenn Ihr MySQL langsam antwortet, füllt sich die Thin-Anforderungswarteschlange.

Für "mehr" ausgeglichenen Webserver sollten Sie Rainbows überprüfen.

+0

Hallo Sergio. Entschuldige mein Verständnis für diese Konzepte. Ich habe gelesen, dass der mysql2-Edelstein für Schienen auch IO ausgeglichen hat. Auch wenn das der Fall ist, wäre ein Worker-basierter Server nicht immer besser? Ich laufe nur 2 Instanzen von dünn im Gegensatz zu 25 Instanzen von Passagier, die ich früher lief. –

+0

@CoffeeBite: Es *** kann *** async Anrufe tun, ja, aber es ist nicht automatisch, und Sie müssen Code schreiben, um es geschehen zu lassen. Standardmäßig ist es synchron. –

+0

@CoffeeBite: "Wären Arbeiter nicht immer besser" - nicht sicher. Ich selbst benutze [Unicorn] (http://unicorn.bogomips.org/) hinter einem Nginx. Nginx verarbeitet HTTP-E/A, und Unicorns können Anfragen schnell ausführen. –