Monday 10 July 2017

Akka Handelssystem


Lässt sich ein hypothetisches HFT-System in Java vorstellen, das (sehr) niedrige Latenzzeiten erfordert, mit vielen kurzlebigen kleinen Objekten, die aufgrund der Unveränderlichkeit (Scala), Tausende von Verbindungen pro Sekunde und einer obszönen Anzahl von Nachrichten, die sich in einem Ereignis bewegen, auftreten Architektur (akka und amqp). Für die Experten da draußen, was wäre (hypothetisch) die beste Tuning für JVM 7 Welche Art von Code würde es glücklich machen würde Scala und Akka für diese Art von Systemen bereit sein Hinweis: Es gab einige ähnliche Fragen, wie diese. Aber Ive noch zu finden eine Abdeckung Scala (die ihre eigene idiosynkratische Fußabdruck in der JVM hat). Es ist möglich, sehr gute Leistung in Java zu erreichen. Allerdings muss die Frage spezifischer sein, um eine glaubwürdige Antwort zu liefern. Ihre wichtigsten Quellen der Latenz kommen aus folgen nicht erschöpfende Liste: Wie viel Müll Sie erstellen und die Arbeit der GC zu sammeln und zu fördern. Unveränderliche Designs in meiner Erfahrung passen nicht gut mit niedriger Latenz. GC Abstimmung muss ein großer Fokus sein. Warm up the JVM, so dass Klassen geladen werden und die JIT hat Zeit, um seine Arbeit zu tun hat. Entwerfen Sie Ihre Algorithmen zu O (1) oder mindestens O (log2 n), und haben Performance-Tests, die dies behaupten. Ihr Design muss lock-free sein und dem Single Writer Principle folgen. Eine bedeutende Anstrengung muss in das Verstehen des vollständigen Stapels und das Zeigen der mechanischen Sympathie in seinem Gebrauch verstanden werden. Gestalten Sie Ihre Algorithmen und Datenstrukturen Cache-freundlich. Cache misses in diesen Tagen sind die größten Kosten. Dies ist eng mit der Prozessaffinität verknüpft, die, wenn sie nicht korrekt eingerichtet ist, zu erheblichen Cache-Verschmutzungen führen kann. Dies wird mit Sympathie für das Betriebssystem und sogar einige JNI-Code in einigen Fällen. Stellen Sie sicher, dass Sie über ausreichende Kerne verfügen, damit jeder Thread, der ausgeführt werden muss, einen Kern zur Verfügung hat, ohne warten zu müssen. Ich habe vor kurzem über eine Fallstudie einer solchen Übung. Sie können feststellen, dass die Verwendung eines Ringpuffers für Nachrichtenübergabe wird übertreffen, was mit Akka getan werden kann. Die Hauptringpufferimplementierung, die Leute auf der JVM für Finanzanwendungen verwenden, ist ein genannt Disruptor, der sorgfältig für die Leistungsfähigkeit (Energie von zwei Größe), für die JVM (kein GC, keine Schlösser) und für moderne CPUs (kein falsches Teilen von) abgestimmt wird Cache-Zeilen). Hier ist eine Intro-Präsentation aus Scala Sicht scala-phase. orgenalksjamie-allen-sdisruptorindex. html1 und es gibt Links auf der letzten Folie zu den ursprünglichen LMAX Zeug. Beantwortet Jul 3 12 at 3:52 Sehr interessant Vielen Dank für den Austausch. Ndash Hugo Sereno Ferreira Ihre Antwort 2017 Stack Exchange, IncMy Kollegen entwickeln ein Handelssystem, das ziemlich starken Strom von eingehenden Transaktionen verarbeitet. Jede Transaktion umfasst ein Instrument (Think Bond oder Aktie) und hat einige (jetzt) ​​unwichtige Eigenschaften. Sie sind mit Java (lt 8) stecken, so lass es bleiben: Instrument wird später als Schlüssel in HashMap verwendet werden. So für die Zukunft wir proactiv implementieren ComparableltInstrumentgt. Dies ist unsere Domäne, jetzt die Anforderungen: Transaktionen kommen ins System und müssen bearbeitet werden (was auch immer das bedeutet), so schnell wie möglich Wir sind frei, sie in beliebiger Reihenfolge zu verarbeiten. Aber Transaktionen für das gleiche Instrument müssen sequentiell in der exakt gleichen Reihenfolge verarbeitet werden, wie sie kam. Die erste Implementierung war einfach - alle eingehenden Transaktionen in eine Warteschlange (z. B. ArrayBlockingQueue) mit einem einzelnen Verbraucher. Dies erfüllt die letzte Anforderung, da die Warteschlange eine strikte FIFO-Bestellung über alle Transaktionen hinweg bewahrt. Eine solche Architektur verhindert jedoch die gleichzeitige Verarbeitung von unabhängigen Transaktionen für verschiedene Instrumente und verschwendet somit eine überzeugende Durchsatzverbesserung. Es überrascht nicht, dass diese Implementierung, ohne Zweifel einfach, zu einem Engpass wurde. Die erste Idee bestand darin, eingehende Transaktionen durch Instrumenten - und Prozessinstrumente einzeln aufzuteilen. Wir kamen mit der folgenden Datenstruktur: Yuck Aber das Schlimmste ist noch zu kommen. Wie stellen Sie sicher, dass maximal ein Thread jede Warteschlange zu einem Zeitpunkt verarbeitet. Andernfalls könnten zwei Threads Elemente aus einer Warteschlange (ein Instrument) abholen und diese in umgekehrter Reihenfolge verarbeiten, was nicht zulässig ist. Der einfachste Fall ist, dass ein Thread pro Warteschlange - dies nicht Skala, wie wir erwarten Zehntausende von verschiedenen Instrumenten haben. So können wir N Threads sagen und jede von ihnen eine Teilmenge von Warteschlangen behandeln lassen, z. B. Instrument. hashCode () N sagt uns, welcher Thread die angegebene Warteschlange übernimmt. Aber seine noch nicht perfekt aus drei Gründen: Ein Thread muss viele Warteschlangen zu beobachten, die meisten wahrscheinlich beschäftigt warten, Iteration über sie die ganze Zeit. Alternativ kann die Warteschlange ihren Eltern-Thread irgendwie aufwachen. Im schlimmsten Fall werden alle Instrumente widersprüchliche Hash-Codes haben, die nur einen Thread ansprechen, der effektiv derselbe wie unsere ursprüngliche Lösung ist. Sein einfach verdammt komplexer Schöner Code ist nicht komplex. Die Umsetzung dieser Monstrosität ist möglich, Aber hart und fehleranfällig. Darüber hinaus gibt es eine andere nicht-funktionale Anforderung: Instrumente kommen und gehen und es gibt Hunderttausende von ihnen im Laufe der Zeit. Nach einer Weile sollten wir Einträge in unserer Karte entfernen, die Instrumente darstellen, die in letzter Zeit nicht gesehen wurden. Ansonsten gut erhalten ein Gedächtnis leak. If können Sie kommen mit einigen einfacheren Lösung, lassen Sie mich wissen. In der Zwischenzeit möchte ich Ihnen sagen, was ich meinen Kollegen vorschlug. Wie Sie erraten können, war es Akka - und es erwies sich als peinlich einfach. Wir brauchen zwei Arten von Schauspielern: Dispatcher und Prozessor. Dispatcher hat eine Instanz und empfängt alle eingehenden Transaktionen. Seine Verantwortung ist es, Arbeitnehmer Prozessor Schauspieler für jedes Instrument zu finden oder spawn Push-Transaktion für sie: Das ist tot einfach. Da unser Dispatcher-Akteur effektiv single-threaded ist, ist keine Synchronisation erforderlich. Wir erhalten kaum Transaktion. Lookup oder erstellen Prozessor und Pass Transaktion weiter. Dies ist, wie Prozessor Umsetzung könnte aussehen: Thats it Interessanterweise unsere Akka Umsetzung ist fast identisch mit unserer ersten Idee mit Karte von Warteschlangen. Schließlich ist ein Schauspieler nur eine Warteschlange und ein (logischer) Thread, der Elemente in dieser Warteschlange verarbeitet. Der Unterschied ist: Akka verwaltet begrenzte Faden-Pool und teilt es zwischen vielleicht Hunderttausende von Schauspielern. Und weil jedes Instrument seinen eigenen dedizierten (und single-threaded) Akteur hat, ist eine sequentielle Verarbeitung von Transaktionen pro Instrument garantiert. Eine Sache noch. Wie bereits erwähnt, gibt es eine enorme Menge an Instrumenten und wir wollen nicht Schauspieler für Instrumente, die werent gesehen für eine ganze Weile zu halten. Lets sagen, dass, wenn ein Prozessor keine Transaktion innerhalb einer Stunde erhalten, sollte es gestoppt und Müll gesammelt werden. Wenn wir später ein neues Geschäft für ein solches Instrument erhalten, können wir es immer wieder neu erstellen. Dieses ist ziemlich tricky - wir müssen sicherstellen, dass, wenn Transaktion kommt, wenn der Prozessor beschlossen, sich selbst zu löschen, können wir nicht verlieren, dass die Transaktion. Anstatt sich selbst zu stoppen, signalisiert der Prozessor seinem Elternteil, dass er zu lange untätig war. Der Disponent wird dann PoisonPill schicken. Da ProcessorIdle - und Transaktionsnachrichten sequentiell verarbeitet werden, besteht kein Risiko, dass Transaktionen an nicht mehr vorhandenen Akteuren gesendet werden. Jeder Akteur verwaltet seinen Lebenszyklus unabhängig, indem er timeout mit setReceiveTimeout einplanen: Wenn Processor keine Nachricht für einen Zeitraum von einer Stunde empfing, signalisiert er sanft seinem Elternteil (Dispatcher). Aber der Schauspieler ist noch am Leben und kann Transaktionen behandeln, wenn sie genau nach einer Stunde passieren. Was Dispatcher tut, tötet er Prozessor und entfernt ihn von einer Karte: Es gab eine leichte Unannehmlichkeit. Instrumentprozessoren verwendet werden, um ein MapltInstrument zu sein, ActorRefgt. Dies erwies sich als unzureichend, da wir plötzlich einen Eintrag in dieser Karte nach Wert entfernen müssen. Mit anderen Worten, wir müssen einen Schlüssel (Instrument) finden, der einem bestimmten ActorRef (Processor) zuordnet. Es gibt verschiedene Möglichkeiten, es zu behandeln (z. B. im Leerlauf Processor könnte senden ein Instrumnt es Griffe), sondern stattdessen habe ich BiMapltK, Vgt aus Guava. Es funktioniert, weil sowohl Instrumente als auch ActorRefs eindeutig sind (Akteur-pro-Instrument). Ich denke, es ist nicht so, wie ich es mir vorgestellt habe, aber ich glaube nicht, dass es so ist, Schlösser und Thread Pools, seine perfekte. Mein Teamkollegen waren so aufgeregt, dass am Ende des Tages beschlossen, ihre gesamte Bewerbung auf Akka. Yet ein anderes Akka Benchmark Ich habe meine Scala und Akka Fähigkeiten durch die Schaffung einer Beispielanwendung in der Ich habe Erfahrung aus der Entwicklung von Handelssystemen so, obwohl die Probe ist vereinfacht, es ist verwurzelt in der Architektur der realen Welt. Ich habe es interessant, die Performance der verschiedenen technischen Lösungen mit dieser Beispielanwendung zu vergleichen. Sohe mehrere konkrete Implementierungen des Handels Scala Schauspieler Akka Schauspieler Ich kann Ihnen sofort sagen, dass die Leistung der Akka Schauspieler ist hervorragend im Vergleich zu Scala Schauspieler. Bevor ich die Benchmark betrachte, beschreibe ich kurz die Beispielanwendung. Ein Handelssystem ist im Wesentlichen über passende Kauf - und Verkaufsaufträge. Eine Grenze Bestellung ist eine Bestellung, um eine Sicherheit zu kaufen nicht mehr, oder zu einem weniger als einem bestimmten Preis zu verkaufen. Zum Beispiel, wenn ein Investor will eine Aktie kaufen, aber doesn8217t wollen mehr als 20 für sie bezahlen, kann der Investor eine Limit-Order, um die Aktie bei 20 8220or better8221 kaufen. Es gibt viele andere Arten von Aufträgen und besondere Zwänge. Das Muster behandelt nur glatte Limitaufträge. Aufträge, die weg vom aktuellen besten Marktpreis sind, werden in einem Orderbuch für die Sicherheit gesammelt, für die spätere Ausführung. Eine passende Maschine verwaltet ein oder mehrere Auftragsbücher, d. h. der Marktplatz ist durch das Auftragsbuch verschachtelt. Die passenden Motoren halten den aktuellen Zustand der Auftragsbücher. Clients verbinden sich mit einem Auftragsempfängerservice, der für die Weiterleitung des Auftrags an den richtigen passenden Motor zuständig ist. Der Auftragsempfänger ist staatenlos und die Kunden können jeden Auftragsempfänger unabhängig vom Auftragsbuch verwenden. Zur Redundanz arbeiten die entsprechenden Motoren paarweise. Jeder Auftrag wird von beiden passenden Motoren verarbeitet. Der Auftrag wird auch in einem persistenten Transaktionsprotokoll von beiden übereinstimmenden Engines gespeichert. In einem realen Setup werden die primären und standby-übereinstimmenden Engines typischerweise in getrennten Rechenzentren eingesetzt. Nun, über die Benchmark. Das Testszenario stellte Kauf - und Verkaufsaufträge in 15 Auftragsbüchern, aufgeteilt in 3 passende Motoren. Die Aufträge sind auf unterschiedlichen Kursniveaus, so dass eine Orderbuch-Tiefe aufgebaut wird, aber am Ende werden alle Aufträge gehandelt und das wird durch den JUnit-Test mit dem Benchmark überprüft. Das Szenario wurde bei einer unterschiedlichen Belastung durch Variieren der Anzahl der simulierten Clients von 1 bis 40 ausgeführt. Die hier veranschaulichten Benchmark-Ergebnisse wurden auf einer realen 8-Core-Box (Dual-CPU Xeon 5500, 2,26 GHz pro Kern) durchgeführt. Hier sind das Ergebnis der Verarbeitung von 750000 Bestellungen auf jeder Last Ebene. Die Basic-Lösung verwendet gewöhnliche synchrone Methodenaufrufe. Es ist extrem schnell, aber nicht eine Option für eine echte skalierbare Lösung. Eine asynchrone Nachrichtenübergabe ist eine bessere Alternative zum Skalieren auf Multicore - oder Mehrfachknoten. In den Scala - und Akka-Actors-Lösungen senden die Kunden jede Auftragsnachricht an einen Auftragseingang und wartet auf die Antwort Future (Operator in Scala und in Akka). Der Auftragsempfänger leitet die Anfrage an die für das Auftragsbuch zuständige Matching Engine weiter, d. H. Der Order Receiver-Threaddispatcher kann sofort für die nächste Anfrage verwendet werden. Die übereinstimmende Engine sendet die Auftragsnachricht an den Standby und beide passenden Maschinen verarbeiten die übereinstimmende Logik und die Transaktionsprotokollierung parallel. Eine Bestätigung wird auf den Client geantwortet, wenn beide fertig sind. Die Benchmark-Ergebnisse zeigen, dass Akka Akteure im Vergleich zu Scala-Akteuren bei gleicher Belastung dreimal so viele Aufträge verarbeiten können. Ähnliche Ergebnisse mit latency. Die Latenzzeit der Akka-Schauspieler ist ein Drittel der Scala-Schauspieler. Dies gilt auch für niedrige Belastung. Durchschnittliche Latenz ist nicht immer das beste Maß, so lassen Sie uns auf einige Perzentile schauen. Operationen, die auf eine Zukunft warten, wurde beim Senden von Nachrichten verwendet. Dies hat eine Skalierbarkeit Preisschild, da der Thread blockiert wird, während er darauf wartet, dass die Zukunft abgeschlossen ist. Bessere Skalierbarkeit kann mit One-Way-Message-Passing erreicht werden, was durch die ScalaAkka Actor One-Way-Lösungen illustriert wird. Es verwendet Bang-Operation () für das Senden aller Nachrichten. Die passenden Engines schreibt jede Bestellung in eine Transaktionsprotokolldatei. Dies ist ein blockierender IO-Engpass. Um den Test der Nachricht weiterzugeben, die einen Schritt weitergeht, wurde der Benchmark auch ohne das Transaktionsprotokoll ausgeführt. Die Akka-Lösung leuchtet noch mehr. Mehr als drei Mal höhere Transaktionsrate im Vergleich zu Scala Akteure bei der gleichen Last, wenn die Lösung basierend auf dem Senden von Nachrichten und warten auf Antwort. Für die One-Way-Message-Passing-Lösungen sind die Akka-Akteure zwei Mal schneller als Scala-Akteure. Akka verfügt über eine große Flexibilität bei der Festlegung der verschiedenen Versandmechanismen. Der Akka Schauspieler hawt ist in der Benchmark als Vergleich mit der Akka Actor One-Way-Lösung enthalten. Es verwendet die HawtDispatch-Threading-Bibliothek, die ein Java-Klon von libdispatch ist. Der letzte Test ohne Transaktionsprotokoll zeigt, dass HawtDispatcher eine etwas bessere Leistung als der Ereignis-basierte Dispatcher hat, der für Akka Actor einseitig verwendet wurde. Der vollständige Quellcode für die Beispielanwendung finden Sie unter: githubpatriknwakka-sample-trading Um die Benchmark selbst auszuführen, können Sie die Distribution herunterladen und entpacken, die alle benötigten JAR-Dateien enthält. Enthalten README beschreibt, wie die Tests ausgeführt werden. Update-Hinweis 15. August: Added Scala Actor One-Way-Lösung und neue Beschreibung, wie die Benchmark laufen. Update-Hinweis 22. August: Neuer Benchmark-Lauf auf echter 8-Kern-Box.

No comments:

Post a Comment