Bioyino – ein verteilter, skalierbarer Metriken-Aggregator

Also, Sie sammeln Metriken. So wie wir. Auch wir sammeln Metriken. Natürlich die, die für das Geschäft wichtig sind. Heute erzählen wir Ihnen vom ersten Glied in unserem Überwachungssystem — einem statsd-kompatiblen Aggregationsserver. bioyino, warum wir ihn entwickelt haben und warum wir uns von brubeck abgewandt haben.

Bioyino – ein verteilter, skalierbarer Metriken-Aggregator

Aus unseren vorherigen Artikeln (1, 2) können Sie erfahren, dass wir bis zu einem bestimmten Zeitpunkt Metriken mithilfe von brubecksammlen. Es ist in C geschrieben. Aus Sicht des Codes ist es einfach wie ein Korken (das ist wichtig, wenn Sie beitragen möchten) und, am wichtigsten, es bewältigt ohne größere Probleme unsere Volumen von 2 Millionen Metriken pro Sekunde (MPS) im Peak. Die Dokumentation gibt an, dass eine Unterstützung von 4 Millionen MPS unter Vorbehalt möglich ist. Das bedeutet, dass Sie die angegebene Zahl erreichen, wenn Sie das Netzwerk unter Linux richtig konfigurieren. (Wie viele MPS man erhält, wenn man das Netzwerk so lässt, wie es ist, wissen wir nicht). Trotz dieser Vorteile hatten wir einige ernsthafte Beschwerden über brubeck.

Beschwerde 1. Github — der Entwickler des Projekts — hat die Unterstützung eingestellt: keine Patches, Fixes, oder unsere (nicht nur unsere) PRs angenommen. In den letzten Monaten (ungefähr seit Februar-März 2018) hat sich die Aktivität zwar wiederbelebt, jedoch gab es davor fast 2 Jahre völlige Stille. Darüber hinaus wird das Projekt für interne Bedürfnisse von Github, was ein ernsthaftes Hindernis für die Einführung neuer Funktionen darstellen kann.

Beschwerde 2. Die Genauigkeit der Berechnungen. Brubeck aggregiert nur 65536 Werte. In unserem Fall können in einigen Metriken während der Aggregationsperiode (30 Sekunden) viel mehr Werte ankommen (1.527.392 im Peak). Durch diese Sampling-Methode erscheinen die Höchst- und Tiefstwerte unbrauchbar. Zum Beispiel so:

Bioyino – ein verteilter, skalierbarer Metriken-Aggregator
Wie bereits erwähnt

Bioyino – ein verteilter, skalierbarer Metriken-Aggregator
So hätte es sein sollen

Aus demselben Grund werden Summen insgesamt falsch berechnet. Fügen Sie dazu einen Fehler mit einem Überlauf des 32-Bit-Floats hinzu, der den Server bei der Verarbeitung einer scheinbar harmlosen Metrik in einen Segfault versetzt, und es wird wirklich großartig. Dieser Fehler wurde übrigens bis heute nicht behoben.

Und schließlich, Beschwerde X. Zum Zeitpunkt der Abfassung dieses Artikels sind wir bereit, sie allen 14 mehr oder weniger funktionierenden Implementierungen von statsd vorzulegen, die wir finden konnten. Stellen wir uns vor, dass eine bestimmte Infrastruktur so gewachsen ist, dass sie 4 Millionen MPS nicht mehr bewältigen kann. Oder sie ist noch nicht gewachsen, aber Metriken sind für Sie bereits so wichtig, dass selbst kurze, 2-3-minütige Ausfälle in den Grafiken kritisch werden und eine unüberwindbare Depression bei den Managern hervorrufen können. Da die Behandlung von Depressionen ein dankloses Unterfangen ist, sind technische Lösungen erforderlich.

Erstens, Ausfallsicherheit, damit plötzliche Probleme auf dem Server im Büro keinen psychiatrischen Zombieapokalypse auslösen. Zweitens, Skalierbarkeit, um in der Lage zu sein, mehr als 4 Millionen MPS zu verarbeiten, ohne in den Linux-Netzwerkstack tiefer graben zu müssen und beruhigt „breit“ auf die benötigten Größen zu wachsen.

Da wir bei der Skalierbarkeit genug Spielraum hatten, beschlossen wir, mit der Ausfallsicherheit zu beginnen. „Oh! Ausfallsicherheit! Das ist einfach, das können wir“, dachten wir und starteten 2 Server, auf denen jeweils eine Kopie von brubeck lief. Dazu mussten wir den Traffik mit Metriken auf beide Server kopieren und sogar ein kleines Dienstprogrammschreiben. Dieses Problem mit der Ausfallsicherheit haben wir gelöst, aber … nicht besonders gut. Zunächst lief alles ganz gut: Jeder brubeck sammelt seine eigene Aggregationsvariante, schreibt alle 30 Sekunden Daten in Graphite und überschreibt den alten Zeitraum (das geschieht auf der Graphite-Seite). Wenn einer der Server ausfällt, haben wir immer noch den anderen mit einer eigenen Kopie der aggregierten Daten. Aber hier ist das Problem: Wenn der Server ausfällt, tritt in den Grafiken eine „Säge“ auf. Das liegt daran, dass die 30-Sekunden-Intervalle bei brubeck nicht synchronisiert sind und im Moment des Ausfalls eines von ihnen nicht überschrieben wird. Wenn der zweite Server gestartet wird, passiert das Gleiche. Das ist zwar erträglich, aber wir möchten es besser haben! Auch das Skalierbarkeitsproblem ist nach wie vor präsent. Alle Metriken laufen immer noch auf einen einzelnen Server, weshalb wir auf denselben 2-4 Millionen MPS in Abhängigkeit von der Netzwerkverfügbarkeit beschränkt sind.

Wenn man ein wenig über das Problem nachdenkt und gleichzeitig mit einer Schaufel Schnee schaufelt, könnte einem die offensichtliche Idee kommen: wir brauchen ein statsd, das im verteilten Modus funktioniert. Das heißt, eines, in dem die Synchronisation zwischen den Knoten nach Zeit und Metriken umgesetzt wird. „Natürlich gibt es so eine Lösung wahrscheinlich schon“, sagten wir und fingen an zu googeln ... und fanden nichts. Wir durchstöberten die Dokumentation verschiedener statsd (https://github.com/etsy/statsd/wiki#server-implementations Zum Zeitpunkt des 11.12.2017 haben wir tatsächlich nichts gefunden. Offenbar hatten weder Entwickler noch Nutzer solcher Lösungen bisher mit SO VIELEN Metriken zu tun, sonst hätten sie sicherlich etwas erfunden.

Und dann erinnerten wir uns an den "Spielzeug" statsd — bioyino, den wir während eines Hackathons just for fun geschrieben haben (der Projektname wurde zu Beginn des Hackathons von einem Skript generiert) und erkannten, dass wir dringend unser eigenes statsd benötigten. Warum?

  • weil es in der Welt zu wenige Klone von statsd gibt,
  • weil man die gewünschte oder nah an der gewünschten Fehlertoleranz und Skalierbarkeit gewährleisten kann (unter anderem das Synchronisieren aggregierter Metriken zwischen Servern und das Lösen von Konflikten beim Senden),
  • weil man Metriken genauer zählen kann, als es brubeck tut,
  • weil wir detailliertere Statistiken selbst sammeln können, die brubeck uns praktisch nicht zur Verfügung stellte,
  • weil sich die Gelegenheit bot, unsere eigene hochperformante verteilte Scalablapplikation zu programmieren, die nicht die Architektur eines ähnlichen hyperperformanten Systems vollständig wiederholen würde.

Worauf sollten wir programmieren? Natürlich auf Rust. Warum?

  • weil es bereits einen Prototyp der Lösung gab,
  • weil der Autor des Artikels zu diesem Zeitpunkt bereits Rust kannte und darauf brannte, etwas für die Produktion zu schreiben, das er als Open-Source veröffentlichen konnte,
  • weil Sprachen mit GC uns aufgrund der Art des empfangenen Traffics (praktisch in Echtzeit) nicht passen, und GC-Pausen praktisch inakzeptabel sind,
  • weil maximale Leistung benötigt wird, vergleichbar mit C
  • weil Rust uns furchtlose Parallelität bietet, und würden wir beginnen, das in C/C++ zu schreiben, hätten wir noch mehr Sicherheitsanfälligkeiten, Bufferüberläufe, Race Conditions und andere gruselige Wörter als bei brubeck.

Es gab auch ein Argument gegen Rust. Das Unternehmen hatte keine Erfahrung in der Erstellung von Projekten mit Rust, und derzeit planen wir auch nicht, es in unserem Hauptprojekt zu verwenden. Daher gab es ernsthafte Bedenken, dass nichts klappen würde, aber wir beschlossen, das Risiko einzugehen und es zu versuchen.

Die Zeit verging...

Schließlich, nach mehreren gescheiterten Versuchen, war die erste funktionierende Version fertig. Was ist dabei herausgekommen? Es kam das hier heraus.

Bioyino – ein verteilter, skalierbarer Metriken-Aggregator

Jede Node erhält ihren eigenen Satz von Metriken und speichert sie lokal, ohne die Metriken für jene Typen zu aggregieren, bei denen für die finale Aggregation der vollständige Satz erforderlich ist. Die Nodes sind durch ein gewisses Protokoll der verteilten Sperre (distributed lock) miteinander verbunden, das es ermöglicht, unter ihnen die eine (hier haben wir geweint) auszuwählen, die würdig ist, die Metriken an den Großen zu senden. Momentan wird dieses Problem durch Consul, aber in Zukunft streben die Ambitionen des Autors an eigenem Implementierung Raft, wo die besagte Würdige natürlich die konsensführende Node sein wird. Neben dem Konsens senden die Nodes recht häufig (standardmäßig einmal pro Sekunde) ihren Nachbarn die Teile der voraggregierten Metriken, die sie in dieser Sekunde gesammelt haben. Das bedeutet, dass Skalierung und Fehlertoleranz erhalten bleiben — jede der Nodes behält weiterhin ihren vollständigen Satz von Metriken, aber die Metriken werden nun bereits aggregiert über TCP und in einem binären Protokoll kodiert gesendet, sodass die Kosten für die Duplizierung im Vergleich zu UDP erheblich sinken. Trotz einer recht großen Anzahl an eintreffenden Metriken benötigt die Ansammlung sehr wenig Speicher und noch weniger CPU. Für unsere gut komprimierbaren Metriken sind das nur einige Dutzend Megabyte Daten. Als zusätzlichen Bonus erhalten wir die Abwesenheit unnötiger Datenüberschreibungen in Graphite, wie es bei burbeck der Fall war.

UDP-Pakete mit Metriken werden auf den Netzwerkgeräten zwischen den Nodes mithilfe eines einfachen Round Robin verteilt. Selbstverständlich analysiert die Netzwerktechnologie den Inhalt der Pakete nicht und kann daher weit mehr bewältigen als 4 Millionen Pakete pro Sekunde, ganz zu schweigen von den Metriken, über die sie überhaupt nichts weiß. Wenn man bedenkt, dass die Metriken nicht einzeln in jedem Paket ankommen, sehen wir hier keine Leistungsprobleme. Im Falle eines Serverausfalls erkennt das Netzwerkgerät dieses Ereignis schnell (innerhalb von 1-2 Sekunden) und entfernt den ausgefallenen Server aus der Rotation. Dadurch können passive (also nicht der Führer sein) Nodes praktisch ohne spürbare Einbrüche in den Grafiken ein- und ausgeschaltet werden. Das Maximum, was wir verlieren, ist ein Teil der Metriken, die in der letzten Sekunde eingegangen sind. Ein plötzlicher Verlust/Ausschalten/Wechsel des Führers wird immer noch eine geringfügige Anomalie anzeigen (ein 30-Sekunden-Intervall ist nach wie vor desynchronisiert), aber bei vorhandenem Kontakt zwischen den Nodes kann man auch diese Probleme minimieren, beispielsweise durch den Versand von synchronisierenden Paketen.

Ein bisschen über die interne Struktur. Die Anwendung ist natürlich mehrsprachig, jedoch unterscheidet sich die Thread-Architektur von der in brubeck. In brubeck sind die Threads gleichförmig – jeder ist sowohl für die Informationssammlung als auch für die Aggregation zuständig. In bioyino sind die Arbeits-Threads (workers) in zwei Gruppen unterteilt: verantwortlich für das Netzwerk und verantwortlich für die Aggregation. Diese Trennung ermöglicht eine flexiblere Verwaltung der Anwendung, abhängig von der Art der Metriken: dort, wo intensive Aggregation erforderlich ist, können mehr Aggregatoren hinzugefügt werden, und dort, wo viel Netzwerkverkehr herrscht, kann die Anzahl der Netzwerk-Threads erhöht werden. Momentan arbeiten wir auf unseren Servern mit 8 Netzwerk-Threads und 4 Aggregations-Threads.

Der berechnende Teil, der für die Aggregation verantwortlich ist, ist ziemlich langweilig. Die mit Netzwerk-Threads gefüllten Puffer werden zwischen den berechnenden Threads verteilt, wo sie weiter geparst und aggregiert werden. Auf Anfrage werden die Metriken zur Versendung an andere Knoten bereitgestellt. All dies, einschließlich der Übertragung von Daten zwischen Knoten und der Arbeit mit Consul, geschieht asynchron, basierend auf dem Framework tokio.

Viel mehr Probleme bei der Entwicklung bereitete der Netzwerkteil, der für den Empfang von Metriken verantwortlich ist. Das Hauptziel der Trennung der Netzwerk-Threads in separate Entitäten war der Wunsch, die Zeit zu reduzieren, die ein Thread benötigt nicht zum Lesen von Daten aus dem Socket. Die Optionen mit asynchronem UDP und gewöhnlichem recvmsg wurden schnell verworfen: das erste verbraucht zu viel CPU im Benutzermodus zur Ereignisverarbeitung, das zweite – zu viele Kontextwechsel. Daher verwenden wir jetzt recvmmsg mit großen Puffern (und Puffer, meine Herren, sind nicht einfach irgendetwas!). Die Unterstützung für gewöhnliches UDP bleibt für unauffällige Fälle, in denen recvmmsg nicht benötigt wird. Im Multimessage-Modus kann das Hauptziel erreicht werden: Der Großteil der Zeit räumt der Netzwerk-Thread die Warteschlange des Betriebssystems auf – Daten werden aus dem Socket gelesen und in den Userspace-Puffer verschoben, nur gelegentlich wird gewechselt, um den gefüllten Puffer an die Aggregatoren weiterzugeben. Die Warteschlange im Socket baut sich kaum auf, die Anzahl der verworfenen Pakete wächst praktisch nicht.

Hinweis

In den Standardeinstellungen ist die Puffergröße relativ groß eingestellt. Wenn Sie sich entscheiden, den Server selbst auszuprobieren, können Sie darauf stoßen, dass nach dem Senden einer kleinen Anzahl von Metriken diese nicht in Graphite ankommen, weil sie im Puffer des Netzwerk-Threads bleiben. Um mit einer kleinen Anzahl von Metriken zu arbeiten, müssen die Werte für bufsize und task-queue-size im Konfigurationsdatei kleiner gesetzt werden.

Zum Schluss – ein paar Grafiken für die Grafikliebhaber.

Statistik der eingehenden Metriken pro Server: über 2 Millionen MPS.

Bioyino – ein verteilter, skalierbarer Metriken-Aggregator

Trennung eines der Knoten und Neuzuordnung der eingehenden Metriken.

Bioyino – ein verteilter, skalierbarer Metriken-Aggregator

Statistik der ausgehenden Metriken: immer nur ein Knoten sendet – der Oberboss.

Bioyino – ein verteilter, skalierbarer Metriken-Aggregator

Statistik der Arbeit jedes Knotens mit Berücksichtigung von Fehlern in verschiedenen Modulen des Systems.

Bioyino – ein verteilter, skalierbarer Metriken-Aggregator

Detailansicht der eingehenden Metriken (die Namen der Metriken sind verborgen).

Bioyino – ein verteilter, skalierbarer Metriken-Aggregator

Was planen wir als Nächstes mit all dem? Natürlich Code zu schreiben, bl…! Das Projekt war ursprünglich als Open-Source geplant und wird es sein, solange es existiert. In naher Zukunft planen wir den Wechsel zu unserer eigenen Version von Raft, die Änderung des Peer-Protokolls auf ein tragbareres, die Einführung zusätzlicher interner Statistiken, neuer Metriktypen, Bugfixes und weitere Verbesserungen.

Natürlich sind alle willkommen, die zur Entwicklung des Projekts beitragen möchten: erstellen Sie PRs, Issues, wir werden versuchen, zu antworten, zu verbessern usw.

Das ist alles, meine Damen und Herren, kaufen Sie unsere Elefanten!

Video abspielen


Quelle: habr.com
Erwerben Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server 🔥 Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster