Welche Arten von Designentscheidungen helfen im Allgemeinen bei der Skalierung einer Anwendung?
(Hinweis: Nachdem ich gerade von Big O Notation erfahren habe, möchte ich hier mehr Prinzipien der Programmierung sammeln. Ich habe versucht, Big O Notation zu erklären, indem ich meine eigene Frage beantworte, aber ich möchte, dass die Community sowohl diese Frage als auch die Antworten verbessert.)
Antworten bisher
1) Definieren Sie die Skalierung. Müssen Sie für viele Benutzer, Datenverkehr und Objekte in einer virtuellen Umgebung skalieren?
2) Schau dir deine Algorithmen an. Wird die Menge der Arbeit, die sie tun, linear mit der tatsächlichen Arbeitsmenge skaliert - d. H. Anzahl der zu durchlaufenden Elemente, Anzahl der Benutzer usw.?
3) Schau dir deine Hardware an. Ist Ihre Anwendung so konzipiert, dass Sie sie auf mehreren Computern ausführen können, wenn Sie nicht mithalten können?
Sekundäre Gedanken
1) Nicht zu früh optimieren - zuerst testen. Vielleicht passieren Engpässe in unvorhergesehenen Orten.
2) Vielleicht übersteigt die Notwendigkeit zu skalieren Moores Gesetz nicht, und vielleicht ist die Aktualisierung von Hardware billiger als Refactoring.
Das einzige, was ich sagen würde, ist, Ihre Anwendung so zu schreiben, dass sie von Anfang an auf einem Cluster bereitgestellt werden kann. Alles darüber ist eine vorzeitige Optimierung. Ihr erster Job sollte genug Benutzer haben, um ein Skalierungsproblem zu haben.
Erstellen Sie den Code so einfach wie möglich, dann profilieren Sie das System als zweites und optimieren Sie es nur, wenn ein offensichtliches Leistungsproblem vorliegt.
Oft sind die Zahlen aus der Profilerstellung Ihres Codes nicht intuitiv; Die Flaschenhälse neigen dazu, sich in Modulen zu befinden, die Sie nicht für langsam hielten. Daten sind wichtig, wenn es um die Optimierung geht. Wenn Sie die Teile optimieren, von denen Sie denken, dass sie langsam sind, optimieren Sie oft die falschen Dinge.
Ok, Sie haben also einen wichtigen Punkt bei der Verwendung der "großen O-Notation" getroffen. Das ist eine Dimension, die Sie in den Fond beißt, wenn Sie nicht aufpassen. Es gibt auch andere Dimensionen im Spiel, die manche Leute nicht durch die "große O" Brille sehen (aber wenn du genauer hinsiehst, sind sie wirklich).
Ein einfaches Beispiel für diese Dimension ist ein Datenbank-Join. Es gibt "best practices" (Best Practices) beim Konstruieren, zum Beispiel, ein linker innerer Join, der helfen wird, die sql effizienter auszuführen. Wenn Sie den relationalen Kalkül zerlegen oder sogar einen EXPLAIN-Plan (Oracle) betrachten, können Sie leicht erkennen, welche Indizes in welcher Reihenfolge verwendet werden und ob Tabellen-Scans oder verschachtelte Operationen auftreten.
Das Konzept des Profiling ist auch der Schlüssel. Sie müssen gründlich und in der richtigen Granularität über alle sich bewegenden Teile der Architektur instrumentiert werden, um Ineffizienzen zu erkennen und zu beheben. Nehmen wir beispielsweise an, Sie erstellen eine webbasierte MVC2-Anwendung mit drei Ebenen und mehreren Threads, bei der AJAX und clientseitige Verarbeitung zusammen mit einem OR-Mapper zwischen Ihrer App und der DB genutzt werden. Ein vereinfachter linearer einzelner Anfrage- / Antwortfluss sieht folgendermaßen aus:
%Vor%Sie sollten eine Methode haben, um die Leistung (Antwortzeiten, Durchsatz gemessen in "Stuff pro Zeiteinheit" usw.) in jedem dieser verschiedenen Bereiche zu messen, nicht nur auf der Box- und OS-Ebene (CPU, Speicher, Festplatte i / o, usw.), aber spezifisch für den Dienst jeder Ebene. Auf dem Webserver müssen Sie also alle Zähler für den Webserver kennen, den Sie verwenden. In der App-Ebene benötigen Sie diese Transparenz, unabhängig davon, welche virtuelle Maschine Sie verwenden (jvm, clr, was auch immer). Die meisten OR-Mapper manifestieren sich in der virtuellen Maschine. Achten Sie also darauf, dass Sie alle Details beachten, wenn sie für Sie auf dieser Ebene sichtbar sind. Innerhalb der Datenbank müssen Sie alles wissen, das ausgeführt wird, und alle spezifischen Optimierungsparameter für Ihre DB-Version. Wenn Sie viel Geld haben, ist BMC Patrol eine ziemlich gute Wette für die meisten davon (mit entsprechenden Wissensmodulen (KM)). Am billigen Ende können Sie sicherlich Ihre eigenen rollen, aber Ihre Meilenzahl hängt von Ihrer Tiefe der Expertise ab.
Vorausgesetzt, dass alles synchron ist (keine warteschlangenbasierten Vorgänge, auf die Sie warten müssen), gibt es eine Menge Möglichkeiten für Probleme mit der Leistung und / oder Skalierbarkeit. Da es in Ihrem Beitrag jedoch um Skalierbarkeit geht, sollten wir den Browser ignorieren, mit Ausnahme von Remote-XHR-Aufrufen, die eine andere Anfrage / Antwort vom Webserver aufrufen.
Was können Sie angesichts dieser Problemdomäne bei der Skalierbarkeit tun?
Verbindungsbehandlung. Dies ist auch an die Sitzungsverwaltung und Authentifizierung gebunden. Das muss so sauber und leicht wie möglich sein, ohne die Sicherheit zu gefährden. Die Metrik ist maximale Verbindungen pro Zeiteinheit.
Sitzungs-Failover auf jeder Ebene Notwendig oder nicht? Wir gehen davon aus, dass jede Ebene horizontal ein Haufen von Boxen unter einem Lastausgleichsmechanismus sein wird. Lastenausgleich ist in der Regel sehr leicht, aber einige Implementierungen von Sitzungsfailover können schwerer als gewünscht sein. Auch wenn Sie mit Sticky-Sitzungen arbeiten, können Sie Ihre Optionen tiefer in der Architektur beeinflussen. Sie müssen auch entscheiden, ob Sie einen Webserver an einen bestimmten App-Server binden möchten oder nicht. In der .NET-Remoting-Welt ist es wahrscheinlich einfacher, sie miteinander zu verbinden. Wenn Sie den Microsoft-Stack verwenden, ist er möglicherweise skalierbarer, um 2-Tier zu betreiben (überspringen Sie das Remoting), aber Sie müssen einen erheblichen Sicherheitsabwägungsprozess durchführen. Auf der Java-Seite habe ich es immer mindestens 3-stufig gesehen. Kein Grund, es anders zu machen.
Objekthierarchie. Innerhalb der App benötigen Sie die sauberste mögliche, leichteste Objektstruktur. Bringen Sie nur die Daten, die Sie brauchen, wenn Sie sie brauchen. Entfernen Sie unnötige oder unnötige Daten unnötig.
ODER Ineffizienzen von Mappern. Es gibt eine Impedanzabweichung zwischen Objektdesign und relationalem Design. Das Viele-zu-Viele-Konstrukt in einem RDBMS steht in direktem Konflikt mit Objekthierarchien (person.address vs. location.resident). Je komplexer Ihre Datenstrukturen sind, desto weniger effizient ist Ihr OR-Mapper. An einem bestimmten Punkt müssen Sie Köder in einer einmaligen Situation schneiden und einen mehr ... äh ... primitiven Datenzugriffsansatz (Stored Procedure + Datenzugriffsschicht) machen, um mehr Leistung oder Skalierbarkeit aus einem besonders herauszupressen hässliches Modul. Verstehen Sie die damit verbundenen Kosten und treffen Sie eine bewusste Entscheidung.
XSL-Transformationen. XML ist ein wunderbarer, normalisierter Mechanismus für den Datentransport, aber der Mensch kann ein riesiger Leistungshund sein! Je nachdem, wie viele Daten Sie mit sich herumtragen und welchen Parser Sie wählen und wie komplex Ihre Struktur ist, können Sie sich mit XSLT leicht in eine sehr dunkle Ecke malen.Ja, akademisch ist es eine brillant saubere Art, eine Präsentationsebene zu erstellen, aber in der realen Welt kann es zu katastrophalen Leistungsproblemen kommen, wenn man nicht besonders darauf achtet. Ich habe gesehen, dass ein System mehr als 30% der Transaktionszeit nur in XSLT verbraucht. Nicht schön, wenn du versuchst, die Nutzerbasis um das Vierfache zu erhöhen, ohne zusätzliche Boxen zu kaufen.
Können Sie sich aus einem Skalierbarkeits-Jam herauskaufen? Absolut. Ich habe es häufiger beobachtet, als ich zugeben möchte. Moores Gesetz (wie Sie bereits erwähnt haben) ist heute noch gültig. Halten Sie für jeden Fall etwas Bargeld bereit.
Caching ist ein großartiges Werkzeug, um die Belastung des Motors zu reduzieren (die Erhöhung der Geschwindigkeit und des Durchsatzes ist ein praktischer Nebeneffekt). Es kommt jedoch zu Lasten des Speicherbedarfs und der Komplexität, wenn der Cache ungültig wird, wenn er veraltet ist. Meine Entscheidung wäre, komplett sauber zu beginnen und Caching nur dann langsam hinzuzufügen, wenn Sie entscheiden, dass es nützlich für Sie ist. Zu oft werden die Komplexitäten unterschätzt und was als ein Weg zur Behebung von Leistungsproblemen begann, führt zu funktionalen Problemen. Außerdem zurück zum Kommentar zur Datennutzung. Wenn Sie jede Minute Objekte im Gigabyte-Bereich erstellen, spielt es keine Rolle, ob Sie zwischenspeichern oder nicht. Sie werden schnell Ihren Speicherbedarf maximieren und die Speicherbereinigung wird Ihren Tag ruinieren. Ich nehme an, dass der Takeaway dafür sorgt, dass Sie genau verstehen, was in Ihrer virtuellen Maschine passiert (Erstellen von Objekten, Zerstörung, GCs usw.), damit Sie die bestmöglichen Entscheidungen treffen können.
Sorry für die Ausführlichkeit. Gerade gerollt und vergessen zu sehen. Ich hoffe, ein Teil davon berührt den Geist Ihrer Untersuchung und ist nicht zu rudimentär eine Unterhaltung.
Nun, es gibt dieses Blog namens Hohe Skalierbarkeit , das viele Informationen zu diesem Thema enthält. Ein paar nützliche Sachen.
Oft ist der effektivste Weg, dies zu tun, ein durchdachtes Design, bei dem Skalierung ein Teil davon ist.
Entscheiden Sie, was Skalierung für Ihr Projekt bedeutet. Gibt es unendlich viele Benutzer, ist es in der Lage, mit einem Slashdotting auf einer Website umzugehen, ist es Entwicklungszyklen?
Verwenden Sie dies, um Ihre Entwicklungsbemühungen zu fokussieren
Jeff und Joel besprechen die Skalierung im Stack Overflow Podcast # 19 .
FWIW, die meisten Systeme werden am effektivsten skalieren, indem sie dies ignorieren, bis es ein Problem ist - Moores Gesetz hält immer noch, und wenn Ihr Verkehr nicht schneller wächst als Moores Gesetz, ist es normalerweise billiger, einfach eine größere Kiste zu kaufen (bei $ 2 oder $ 3K ein Pop) als Entwickler bezahlen.
Der wichtigste Punkt, auf den Sie sich konzentrieren sollten, ist Ihre Datenebene. Das ist der schwierigste Teil Ihrer Anwendung, den Sie skalieren müssen, da sie normalerweise autoritativ sein muss, und geclusterte kommerzielle Datenbanken sind sehr teuer - die Open-Source-Varianten sind normalerweise sehr schwierig, um richtig zu funktionieren.
Wenn Sie der Meinung sind, dass Ihre Anwendung sehr skalierbar sein wird, kann es sinnvoll sein, Systeme wie memcached oder map reduce relativ früh in Ihrer Entwicklung zu betrachten.
Eine gute Idee ist es, zu bestimmen, wie viel Arbeit jede zusätzliche Aufgabe erzeugt. Dies kann davon abhängen, wie der Algorithmus strukturiert ist.
Stellen Sie sich zum Beispiel vor, Sie hätten virtuelle Autos in einer Stadt. Sie möchten, dass jedes Auto eine Karte hat, die zeigt, wo sich alle Autos befinden.
Eine Möglichkeit, dies zu erreichen, wäre:
%Vor%Das scheint einfach zu sein: Schauen Sie sich die Position des ersten Autos an, fügen Sie sie der Karte jedes anderen Autos hinzu. Dann schau dir die Position des zweiten Autos an und füge es der Karte jedes anderen Autos hinzu. Etc.
Aber es gibt ein Skalierbarkeitsproblem. Wenn es 2 Autos gibt, nimmt diese Strategie 4 "add my position" Schritte; Wenn es 3 Autos gibt, dauert es 9 Schritte. Für jede "Positionsaktualisierung" müssen Sie die gesamte Liste der Autos durchblättern - und jedes Auto benötigt seine Position aktualisiert.
Ignorieren, wie viele andere Dinge für jedes Auto getan werden müssen (z. B. kann es eine festgelegte Anzahl von Schritten zur Berechnung der Position eines einzelnen Autos geben), für N Autos braucht es N 2 "Besuche in Autos", um diesen Algorithmus auszuführen . Das ist kein Problem, wenn Sie 5 Autos und 25 Schritte haben. Aber wenn Sie Autos hinzufügen, werden Sie sehen, dass das System versinkt. 100 Autos werden 10,000 Schritte machen, und 101 Autos werden 10,201 Schritte machen!
Besser wäre es, die Verschachtelung der for-Schleifen rückgängig zu machen.
%Vor%Bei dieser Strategie ist die Anzahl der Schritte ein Vielfaches von N, nicht von N 2 . Also 100 Autos werden 100 mal die Arbeit von 1 Auto nehmen - NICHT 10.000 mal die Arbeit .
Dieses Konzept wird manchmal in "großer O-Notation" ausgedrückt - die Anzahl der benötigten Schritte ist "großer O von N" oder "großer O von N 2 ."
Beachten Sie, dass dieses Konzept nur die Skalierbarkeit betrifft und nicht die Anzahl der Schritte für jedes Auto optimiert. Hier ist es uns egal, ob es 5 Schritte oder 50 Schritte pro Auto braucht - Hauptsache N Autos nehmen (X * N) Schritte, nicht (X * N 2 ).
>Tags und Links algorithm language-agnostic scalability