Ich mache algorithmischen Handel bei der Raiffeisenbank. Dies ist ein eher spezifischer Bereich des Bankensektors. Wir machen eine Handelsplattform, die mit geringen und vorhersehbaren Verzögerungen arbeitet. Der Erfolg der Anwendung hängt unter anderem von der Geschwindigkeit der Anwendung ab. Daher müssen wir uns mit dem gesamten am Handel beteiligten Stack befassen: privaten Netzwerkkanälen, spezieller Hardware, Betriebssystemeinstellungen und einer speziellen JVM und natürlich der Anwendung selbst. Wir können nicht aufhören, ausschließlich die Anwendung selbst zu optimieren - das Betriebssystem oder die Netzwerkeinstellungen sind nicht weniger wichtig. Dies erfordert technisches Fachwissen und Gelehrsamkeit, um zu verstehen, wie Daten durch den gesamten Stapel fließen und wo es zu Verzögerungen kommen kann.

Nicht jede Organisation / Bank kann sich die Entwicklung dieser Softwareklasse leisten. Ich hatte jedoch das Glück, dass ein solches Projekt innerhalb der Mauern der Raiffeisenbank gestartet wurde, und ich hatte eine geeignete Spezialisierung - ich habe mich auf die Codeleistung im Intel Moscow Compiler Laboratory spezialisiert. Wir haben Compiler für C, C ++ und Fortran erstellt. Bei der Raiffeisenbank wechselte ich zu Java. Wenn ich früher eine Art Werkzeug gemacht habe, das damals viele Leute benutzten, bin ich jetzt auf die andere Seite der Barrikaden gezogen und beschäftige mich mit der angewandten Analyse der Leistung nicht nur des Codes, sondern des gesamten Anwendungsstapels. Regelmäßig liegt der Weg zur Untersuchung eines Leistungsproblems weit über den Code hinaus, beispielsweise in den Kernel- oder Netzwerkeinstellungen.
Java ist nicht für Hochlast?
Es wird allgemein angenommen, dass Java für die Entwicklung hoch belasteter Systeme nicht sehr geeignet ist.
Dies kann nur teilweise vereinbart werden. Java ist in vielerlei Hinsicht schön. Wenn wir es mit einer Sprache wie C ++ vergleichen, ist der potenzielle Overhead möglicherweise höher, aber manchmal funktionieren funktional ähnliche Lösungen in C ++ langsamer. Es gibt Optimierungen, die automatisch in Java funktionieren, aber nicht in C ++ und umgekehrt. Wenn ich mir die Qualität des Codes anschaue, der nach dem Java JIT-Compiler kommt, möchte ich glauben, dass die Leistung nicht mehr als mehrmals unter dem liegt, was ich in Spitzenzeiten erreichen konnte. Gleichzeitig bekomme ich eine sehr schnelle Entwicklung, hervorragende Werkzeuge und eine große Auswahl an Standardkomponenten.
Seien wir ehrlich: In der C ++ - Welt stehen Entwicklungsumgebungen (IDEs) deutlich hinter IntelliJ und Eclipse. Wenn ein Entwickler eine dieser Umgebungen verwendet, ist die Debugging-Geschwindigkeit, das Auffinden von Fehlern und das Schreiben komplexer Logik um eine Größenordnung höher. Infolgedessen stellt sich heraus, dass es einfacher ist, Java an den richtigen Stellen abzulegen, damit es schnell genug funktioniert, als alles von Grund auf und für eine sehr lange Zeit in C ++ zu erledigen. Das Lustigste ist, dass beim Schreiben von Konkurrenzcode die Synchronisationsansätze in Java und C ++ sehr ähnlich sind: Es handelt sich entweder um Grundelemente auf Betriebssystemebene (z. B.
synchronized / std :: mutex ) oder um Eisengrundelemente (
Atomic * / std :: atomic <*> ). . Und es ist sehr wichtig, diese Ähnlichkeit zu sehen.
Im Allgemeinen entwickeln wir eine Nicht-Hochlast-Anwendung))
Was ist der Unterschied zwischen Anwendungen mit hoher Last und niedriger Latenz?
Der Begriff Hochlast spiegelt die Besonderheiten unserer Arbeit nicht vollständig wider - wir beschäftigen uns mit
latenzempfindlichen Systemen . Was ist der Unterschied? Bei hoch ausgelasteten Systemen ist es wichtig, im Durchschnitt relativ schnell zu arbeiten und die Hardwareressourcen voll auszunutzen. In der Praxis bedeutet dies, dass jede hundertstel / tausendste / ... / millionste Anforderung an das System möglicherweise sehr langsam arbeiten kann, da wir uns auf Durchschnittswerte konzentrieren und nicht immer berücksichtigen, dass unsere Benutzer erheblich unter Bremsen leiden.
Wir beschäftigen uns mit Systemen, für die die Verzögerung kritisch ist. Unsere Aufgabe ist es sicherzustellen, dass das System immer eine vorhersehbare Reaktion hat. Potenziell
latenzempfindliche Systeme sind möglicherweise nicht stark ausgelastet, wenn Ereignisse selten genug auftreten, aber eine garantierte Antwortzeit erforderlich ist. Und das macht ihre Entwicklung nicht einfacher. Ganz im Gegenteil! Überall lauern Gefahren. Die überwiegende Mehrheit der Komponenten moderner Hardware und Software ist "im Durchschnitt" auf gute Arbeit ausgerichtet, d.h. für den
Durchsatz .
Nehmen Sie mindestens Datenstrukturen. Wir verwenden Hash-Tabellen. Wenn ein erneuter Hash der gesamten Datenstruktur auf einem kritischen Pfad auftritt, kann dies bei einer einzelnen Anforderung zu spürbaren Bremsen für einen bestimmten Benutzer führen. Oder JIT-Compiler - optimiert das am häufigsten ausgeführte Codemuster und pessimiert das selten ausgeführte Codemuster. Aber die Geschwindigkeit dieses seltenen Falles kann für uns sehr wichtig sein!
Vielleicht verarbeitet dieser Code eine seltene Art von Bestellungen? Oder eine ungewöhnliche Marktsituation, die eine schnelle Reaktion erfordert? Wir versuchen sicherzustellen, dass die Reaktion unseres Systems auf diese möglicherweise seltenen Ereignisse vorhersehbare und vorzugsweise sehr kurze Zeit in Anspruch nimmt.
Wie erreicht man eine vorhersehbare Reaktionszeit?
Diese Frage kann nicht in zwei Sätzen beantwortet werden. In erster Näherung ist es wichtig zu verstehen, ob es irgendeine Art von Synchronisation gibt -
synchronisiert, Reentrantlock oder etwas aus
java.util.concurrent . Oft muss man bei Busy-Spin'ah die Synchronisation verwenden. Die Verwendung eines beliebigen Synchronisationsprimitivs ist immer ein Kompromiss. Und es ist wichtig zu verstehen, wie diese Synchronisationsprimitive funktionieren und welche Kompromisse sie mit sich bringen. Es ist auch wichtig zu bewerten, wie viel Müll ein bestimmter Code erzeugt. Der beste Weg, um den Garbage Collector zu bekämpfen, besteht darin, ihn nicht auszulösen. Je weniger wir Müll erzeugen, desto seltener führen wir einen Müllsammler aus und desto länger arbeitet das System ohne dessen Eingreifen.
Wir verwenden auch eine Vielzahl verschiedener Tools, mit denen wir nicht nur Durchschnittsindikatoren analysieren können. Wir müssen sehr sorgfältig analysieren, wie langsam das System jedes hundertste, jedes tausendste Mal arbeitet. Offensichtlich sind diese Indikatoren schlechter als der Median oder Durchschnitt. Aber es ist sehr wichtig für uns zu wissen, wie viel. Tools wie
Grafana ,
Prometheus ,
HDR-Histogramme und
JMH zeigen dies.
Kann ich Unsafe entfernen?
Oft muss man das verwenden, was Apologeten als undokumentierte API bezeichnen. Ich spreche von der berühmten Unsicheren. Ich glaube, unsicher ist de facto ein Teil der öffentlichen API von Java-Maschinen geworden. Es macht keinen Sinn, es zu leugnen. Unsafe verwendet viele Projekte, die wir alle aktiv nutzen. Und wenn wir es ablehnen, was wird dann mit diesen Projekten geschehen? Entweder werden sie von der alten Version von Java leben, oder sie müssen wieder viel Energie aufwenden, um all dies neu zu schreiben. Ist die Community dazu bereit? Ist es bereit, möglicherweise zehn Prozent der Produktivität zu verlieren? Und vor allem im Austausch für was?
Indirekt bestätigt meine Meinung eine
sehr ordentliche Entfernung von Methoden aus Unsafe - in Java11 wurden die nutzlosesten Methoden aus Unsafe entfernt. Ich denke, bis mindestens die Hälfte aller Projekte, die Unsafe verwenden, zu etwas anderem wechseln, wird Unsafe in der einen oder anderen Form verfügbar sein.
Es gibt eine Meinung: Bank + Java = blutig verknöchertes Unternehmen?
Unser Team hat keine solchen Schrecken. Im Frühjahr haben wir wahrscheinlich zehn Zeilen geschrieben, und von mir)) versuchen wir, keine großen Technologien zu verwenden. Wir bevorzugen es, klein, ordentlich und schnell zu arbeiten, damit wir es realisieren, steuern und gegebenenfalls modifizieren können. Letzteres ist sehr wichtig für Systeme (wie unsere), die nicht standardmäßige Anforderungen haben, die von den Anforderungen von 90% der Benutzer des Frameworks abweichen können. Und wenn wir ein großes Framework verwenden, können wir unsere Bedürfnisse nicht an die Community weitergeben oder das Verhalten unabhängig korrigieren.
Meiner Meinung nach sollten Entwickler immer in der Lage sein, alle verfügbaren Tools zu verwenden. Ich bin aus C ++ in die Java-Welt gekommen und bin sehr beeindruckt von der Aufteilung der Community in diejenigen, die die
Laufzeit der virtuellen Maschine / des Compilers oder der virtuellen Maschine selbst entwickeln, und in Anwendungsentwickler. Dies ist im Standard-JDK-Klassencode deutlich zu sehen. Oft verwenden JDK-Autoren eine andere API. Potenziell bedeutet dies, dass wir keine Spitzenleistung erzielen können. Im Allgemeinen glaube ich, dass die Verwendung derselben API zum Schreiben sowohl der Standardbibliothek als auch des Anwendungscodes ein hervorragender Indikator für die Reife der Plattform ist.
Noch eine Sache
Ich denke, es ist sehr wichtig, dass alle Entwickler wissen, wie, wenn nicht der gesamte Stack, mindestens die Hälfte davon funktioniert: Java-Code, Byte-Code, Interna der Laufzeit der virtuellen Maschine und Assembler, Hardware, Betriebssystem, Netzwerk. Dies ermöglicht einen breiteren Blick auf die Probleme.
Erwähnenswert ist auch die Leistung. Es ist sehr wichtig, sich nicht auf den Durchschnitt zu konzentrieren und immer den Median und die hohen Perzentile zu betrachten (die schlechteste von 10/100/1000 / ... Messungen).
Ich werde auf einem
Treffen der Java User Group am 30. Mai in St. Petersburg darüber sprechen. Treffen mit Sergei Melnikov, das bin nur ich)) Hier können Sie sich registrieren.
Worüber werden wir reden?- Über die Profilerstellung und Verwendung des Standard-Linux- und Perf-Profilers: Wie man mit ihnen lebt, was sie messen und wie man ihre Ergebnisse interpretiert. Dies ist eine Einführung in die allgemeine Profilerstellung mit Tipps, Life-Hacks und dem Auspressen aller möglichen Elemente aus Profilern, damit diese mit maximaler Genauigkeit und Häufigkeit profiliert werden.
- Informationen zu Ausstattungsmerkmalen, um ein noch detaillierteres Profil zu erhalten und das Profil seltener Ereignisse anzuzeigen. Zum Beispiel, wenn Ihr Code jedes hundertste Mal zehnmal langsamer läuft. Kein Profiler wird darüber berichten. Wir werden unseren kleinen Profiler unter Verwendung des Standard-Linux-Kernel-Mechanismus schreiben und versuchen, das Profil eines seltenen Ereignisses zu sehen.
Kommen Sie zu dem Treffen, es wird ein großartiger Abend, es wird viele interessante Geschichten über unsere Plattform und über unsere Lieblingssprache geben.
Bis dann ;)