Bioyino – verteilter, skalierbarer Metrik-Aggregator

Sie sammeln also Kennzahlen. Wie wir sind. Wir erfassen auch Kennzahlen. Natürlich geschäftsnotwendig. Heute werden wir über den allerersten Link unseres Überwachungssystems sprechen – einen Statsd-kompatiblen Aggregationsserver Bioyino, warum wir es geschrieben haben und warum wir Brubeck aufgegeben haben.

Bioyino – verteilter, skalierbarer Metrik-Aggregator

Aus unseren vorherigen Artikeln (1, 2) können Sie herausfinden, dass wir bis vor einiger Zeit Noten gesammelt haben Brubeck. Es ist in C geschrieben. Aus Code-Sicht ist es so einfach wie ein Plug (das ist wichtig, wenn Sie einen Beitrag leisten möchten) und, was am wichtigsten ist, es bewältigt unser Volumen von 2 Millionen Metriken pro Sekunde (MPS) in Spitzenzeiten ohne Probleme. In der Dokumentation wird die Unterstützung für 4 Millionen MPS mit einem Sternchen angegeben. Dies bedeutet, dass Sie den angegebenen Wert erhalten, wenn Sie das Netzwerk unter Linux richtig konfigurieren. (Wir wissen nicht, wie viele MPS Sie erhalten können, wenn Sie das Netzwerk unverändert lassen.) Trotz dieser Vorteile hatten wir mehrere schwerwiegende Beschwerden über Brubeck.

Anspruch 1. Github, der Entwickler des Projekts, hat aufgehört, es zu unterstützen: Patches und Fixes zu veröffentlichen, unsere und (nicht nur unsere) PR zu akzeptieren. In den letzten Monaten (irgendwann von Februar bis März 2018) hat die Aktivität wieder zugenommen, davor herrschte jedoch fast zwei Jahre völlige Ruhe. Darüber hinaus befindet sich das Projekt in der Entwicklung für interne Gihub-Anforderungen, was zu einem ernsthaften Hindernis für die Einführung neuer Funktionen werden kann.

Anspruch 2. Genauigkeit der Berechnungen. Insgesamt sammelt Brubeck 65536 Werte zur Aggregation. In unserem Fall können für einige Metriken während des Aggregationszeitraums (30 Sekunden) viel mehr Werte eintreffen (1 in der Spitze). Als Ergebnis dieser Stichprobe erscheinen die Maximal- und Minimalwerte unbrauchbar. Zum Beispiel so:

Bioyino – verteilter, skalierbarer Metrik-Aggregator
Wie war

Bioyino – verteilter, skalierbarer Metrik-Aggregator
Wie es hätte sein sollen

Aus dem gleichen Grund werden Beträge in der Regel falsch berechnet. Fügen Sie hier einen Fehler mit einem 32-Bit-Float-Überlauf hinzu, der den Server im Allgemeinen in einen Segfault schickt, wenn er eine scheinbar harmlose Metrik empfängt, und alles wird großartig. Der Fehler wurde übrigens nicht behoben.

Und endlich, Anspruch X. Zum Zeitpunkt des Schreibens sind wir bereit, es allen 14 mehr oder weniger funktionierenden Statsd-Implementierungen vorzustellen, die wir finden konnten. Stellen wir uns vor, dass eine einzelne Infrastruktur so stark gewachsen ist, dass die Annahme von 4 Millionen MPS nicht mehr ausreicht. Oder auch wenn es noch nicht gewachsen ist, aber die Kennzahlen für Sie bereits so wichtig sind, dass selbst kurze Einbrüche in den Diagrammen von 2 bis 3 Minuten bereits kritisch werden und bei Managern unüberwindbare Depressionen auslösen können. Da die Behandlung von Depressionen eine undankbare Aufgabe ist, sind technische Lösungen erforderlich.

Erstens Fehlertoleranz, damit ein plötzliches Problem auf dem Server nicht zu einer psychiatrischen Zombie-Apokalypse im Büro führt. Zweitens die Skalierung, um mehr als 4 Millionen MPS akzeptieren zu können, ohne tief in den Linux-Netzwerkstapel einzudringen und ruhig „in der Breite“ auf die erforderliche Größe zu wachsen.

Da wir Spielraum für Skalierung hatten, entschieden wir uns, mit der Fehlertoleranz zu beginnen. "UM! Fehlertoleranz! Es ist ganz einfach, wir schaffen das“, dachten wir und starteten zwei Server, auf denen wir jeweils eine Kopie von Brubeck erstellten. Dazu mussten wir den Traffic mit Metriken auf beide Server kopieren und dafür sogar schreiben kleines Dienstprogramm. Damit haben wir das Fehlertoleranzproblem gelöst, aber... nicht sehr gut. Zuerst schien alles großartig: Jeder Brubeck sammelt seine eigene Aggregationsversion, schreibt alle 30 Sekunden Daten in Graphite und überschreibt dabei das alte Intervall (dies geschieht auf der Graphite-Seite). Sollte ein Server plötzlich ausfallen, haben wir immer einen zweiten mit einer eigenen Kopie der aggregierten Daten. Aber hier liegt das Problem: Fällt der Server aus, erscheint in den Diagrammen ein „Sägeblatt“. Dies liegt daran, dass die 30-Sekunden-Intervalle von Brubeck nicht synchronisiert sind und im Moment eines Absturzes einer davon nicht überschrieben wird. Wenn der zweite Server startet, passiert das Gleiche. Ganz erträglich, aber ich will besser! Auch das Problem der Skalierbarkeit ist nicht verschwunden. Alle Metriken „fliegen“ immer noch auf einen einzelnen Server, und daher sind wir je nach Netzwerkebene auf die gleichen 2-4 Millionen MPS beschränkt.

Wenn man ein wenig über das Problem nachdenkt und gleichzeitig mit einer Schaufel Schnee ausgräbt, dann kommt einem vielleicht folgende offensichtliche Idee in den Sinn: Man braucht einen Statsd, der im verteilten Modus arbeiten kann. Das heißt, eines, das die zeitliche und metrische Synchronisierung zwischen Knoten implementiert. „Natürlich gibt es eine solche Lösung wahrscheinlich schon“, sagten wir und gingen zu Google…. Und sie fanden nichts. Nachdem Sie die Dokumentation für verschiedene Statistiken durchgesehen haben (https://github.com/etsy/statsd/wiki#server-implementations (Stand: 11.12.2017. Dezember XNUMX) haben wir absolut nichts gefunden. Anscheinend sind weder die Entwickler noch die Nutzer dieser Lösungen bisher auf SO viele Metriken gestoßen, sonst würden sie sich bestimmt etwas einfallen lassen.

Und dann erinnerten wir uns an den „Spielzeug“-Statsd – bioyino, der beim Just for Fun-Hackathon geschrieben wurde (der Name des Projekts wurde vom Skript vor Beginn des Hackathons generiert) und erkannten, dass wir dringend einen eigenen Statsd brauchten. Wofür?

  • weil es zu wenige Statsd-Klone auf der Welt gibt,
  • weil es möglich ist, die gewünschte oder nahezu gewünschte Fehlertoleranz und Skalierbarkeit bereitzustellen (einschließlich der Synchronisierung aggregierter Metriken zwischen Servern und der Lösung des Problems von Sendekonflikten),
  • weil es möglich ist, Kennzahlen genauer zu berechnen als Brubeck,
  • weil Sie selbst detailliertere Statistiken sammeln können, die Brubeck uns praktisch nicht zur Verfügung gestellt hat,
  • weil ich die Chance hatte, meine eigene Hyperperformance-Laboranwendung mit verteiltem Maßstab zu programmieren, die die Architektur eines anderen ähnlichen Hyperfor nicht vollständig wiederholen wird ... nun, das war's.

Worüber soll ich schreiben? Natürlich in Rust. Warum?

  • weil es bereits eine prototypische Lösung gab,
  • weil der Autor des Artikels Rust zu diesem Zeitpunkt bereits kannte und unbedingt etwas für die Produktion darin schreiben wollte, mit der Möglichkeit, es in Open Source zu veröffentlichen,
  • weil Sprachen mit GC aufgrund der Art des empfangenen Datenverkehrs (fast in Echtzeit) für uns nicht geeignet sind und GC-Pausen praktisch inakzeptabel sind,
  • weil Sie eine maximale Leistung benötigen, die mit C vergleichbar ist
  • weil Rust uns eine furchtlose Parallelität bietet, und wenn wir angefangen hätten, es in C/C++ zu schreiben, hätten wir noch mehr Schwachstellen, Pufferüberläufe, Rennbedingungen und andere beängstigende Wörter als Brubeck eingebracht.

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

Zeit verging...

Nach mehreren gescheiterten Versuchen war endlich die erste funktionsfähige Version fertig. Was ist passiert? Das ist, was passiert ist.

Bioyino – verteilter, skalierbarer Metrik-Aggregator

Jeder Knoten empfängt seinen eigenen Satz von Metriken und akkumuliert diese. Er aggregiert keine Metriken für die Typen, bei denen der vollständige Satz für die endgültige Aggregation erforderlich ist. Die Knoten sind durch eine Art verteiltes Sperrprotokoll miteinander verbunden, das es Ihnen ermöglicht, unter ihnen den einzigen auszuwählen (hier haben wir geweint), der es wert ist, Metriken an den Großen zu senden. Dieses Problem wird derzeit von gelöst Konsul, aber in der Zukunft erstrecken sich die Ambitionen des Autors auf eigene Implementierung Floß, wobei der würdigste Knoten natürlich der Konsensführerknoten sein wird. Zusätzlich zum Konsens senden Knoten häufig (standardmäßig einmal pro Sekunde) die Teile voraggregierter Metriken an ihre Nachbarn, die sie in dieser Sekunde gesammelt haben. Es stellt sich heraus, dass Skalierung und Fehlertoleranz erhalten bleiben – jeder Knoten verfügt immer noch über einen vollständigen Satz an Metriken, die Metriken werden jedoch bereits aggregiert über TCP gesendet und in ein Binärprotokoll codiert, sodass die Duplizierungskosten im Vergleich zu UDP erheblich reduziert werden. Trotz der relativ großen Anzahl eingehender Metriken erfordert die Akkumulation sehr wenig Speicher und noch weniger CPU. Für unsere hochkomprimierbaren Mertics sind das nur ein paar Dutzend Megabyte an Daten. Als zusätzlichen Bonus erhalten wir in Graphite keine unnötigen Datenumschreibungen, wie es bei Burbeck der Fall war.

UDP-Pakete mit Metriken werden durch ein einfaches Round Robin zwischen Knoten auf Netzwerkgeräten unausgeglichen. Natürlich analysiert die Netzwerkhardware den Inhalt von Paketen nicht und kann daher weit mehr als 4 Millionen Pakete pro Sekunde abrufen, ganz zu schweigen von Metriken, über die sie überhaupt nichts weiß. Wenn wir berücksichtigen, dass die Metriken nicht einzeln in jedem Paket enthalten sind, können wir an dieser Stelle keine Leistungsprobleme erwarten. Wenn ein Server abstürzt, erkennt das Netzwerkgerät dies schnell (innerhalb von 1–2 Sekunden) und entfernt den abgestürzten Server aus der Rotation. Dadurch können passive (d. h. nicht führende) Knoten ein- und ausgeschaltet werden, praktisch ohne dass Drawdowns auf den Diagrammen zu bemerken sind. Das Maximum, das wir verlieren, ist Teil der Messwerte, die in letzter Sekunde eingegangen sind. Ein plötzlicher Verlust/Abschaltung/Wechsel eines Leiters führt immer noch zu einer geringfügigen Anomalie (das 30-Sekunden-Intervall ist immer noch nicht synchron), wenn jedoch Kommunikation zwischen Knoten besteht, können diese Probleme minimiert werden, indem beispielsweise Synchronisierungspakete gesendet werden .

Ein wenig über die interne Struktur. Die Anwendung ist natürlich multithreaded, aber die Threading-Architektur unterscheidet sich von der in Brubeck verwendeten. Die Threads in Brubeck sind die gleichen – jeder von ihnen ist sowohl für die Informationssammlung als auch für die Aggregation verantwortlich. Im Bioyino werden die Arbeiter in zwei Gruppen eingeteilt: diejenigen, die für das Netzwerk verantwortlich sind, und diejenigen, die für die Aggregation verantwortlich sind. Diese Unterteilung ermöglicht Ihnen eine flexiblere Verwaltung der Anwendung je nach Art der Metriken: Wo eine intensive Aggregation erforderlich ist, können Sie Aggregatoren hinzufügen, wo viel Netzwerkverkehr herrscht, können Sie die Anzahl der Netzwerkflüsse hinzufügen. Derzeit arbeiten wir auf unseren Servern in 8 Netzwerk- und 4 Aggregationsflüssen.

Der Zählteil (der für die Aggregation verantwortlich ist) ist ziemlich langweilig. Durch Netzwerkflüsse gefüllte Puffer werden auf die Zählflüsse verteilt, wo sie anschließend analysiert und aggregiert werden. Auf Anfrage werden Metriken zum Senden an andere Knoten bereitgestellt. All dies, einschließlich des Sendens von Daten zwischen Knoten und der Arbeit mit Consul, wird asynchron ausgeführt und auf dem Framework ausgeführt Tokyo.

Viel mehr Probleme während der Entwicklung verursachte der Netzwerkteil, der für den Empfang von Metriken verantwortlich ist. Das Hauptziel der Aufteilung von Netzwerkflüssen in separate Einheiten war der Wunsch, die Zeit zu reduzieren, die ein Fluss verbringt nicht um Daten aus dem Socket zu lesen. Optionen, die asynchrones UDP und reguläres recvmsg verwenden, verschwanden schnell: Ersteres verbraucht zu viel Benutzerraum-CPU für die Ereignisverarbeitung, zweites erfordert zu viele Kontextwechsel. Deshalb wird es jetzt verwendet recvmmsg mit großen Puffern (und Puffer, meine Herren Offiziere, sind für Sie nichts!). Die Unterstützung für reguläres UDP ist leichten Fällen vorbehalten, in denen recvmmsg nicht benötigt wird. Im Multimessage-Modus ist es möglich, das Wichtigste zu erreichen: In den allermeisten Fällen harkt der Netzwerk-Thread die Betriebssystemwarteschlange aus, liest Daten aus dem Socket und überträgt sie in den Userspace-Puffer, wobei er nur gelegentlich auf die Bereitstellung des gefüllten Puffers umschaltet Aggregatoren. Die Warteschlange im Socket sammelt sich praktisch nicht an, die Anzahl der verworfenen Pakete wächst praktisch nicht.

Beachten

In den Standardeinstellungen ist die Puffergröße recht groß eingestellt. Wenn Sie sich plötzlich dazu entschließen, den Server selbst auszuprobieren, kann es sein, dass nach dem Senden einer kleinen Anzahl von Metriken diese nicht in Graphite ankommen und im Netzwerk-Stream-Puffer verbleiben. Um mit einer kleinen Anzahl von Metriken zu arbeiten, müssen Sie bufsize und task-queue-size in der Konfiguration auf kleinere Werte setzen.

Zum Schluss noch ein paar Charts für Chartliebhaber.

Statistiken zur Anzahl eingehender Metriken für jeden Server: mehr als 2 Millionen MPS.

Bioyino – verteilter, skalierbarer Metrik-Aggregator

Einen der Knoten deaktivieren und eingehende Metriken neu verteilen.

Bioyino – verteilter, skalierbarer Metrik-Aggregator

Statistiken zu ausgehenden Metriken: Es sendet immer nur ein Knoten – der Raid-Boss.

Bioyino – verteilter, skalierbarer Metrik-Aggregator

Statistiken über den Betrieb jedes Knotens unter Berücksichtigung von Fehlern in verschiedenen Systemmodulen.

Bioyino – verteilter, skalierbarer Metrik-Aggregator

Detaillierung der eingehenden Metriken (Metriknamen werden ausgeblendet).

Bioyino – verteilter, skalierbarer Metrik-Aggregator

Was haben wir als nächstes mit all dem vor? Natürlich Code schreiben, verdammt...! Das Projekt war ursprünglich als Open-Source-Projekt geplant und wird dies auch während seiner gesamten Laufzeit bleiben. Zu unseren unmittelbaren Plänen gehören die Umstellung auf unsere eigene Version von Raft, die Änderung des Peer-Protokolls auf ein tragbareres Protokoll, die Einführung zusätzlicher interner Statistiken, neuer Arten von Metriken, Fehlerbehebungen und anderer Verbesserungen.

Natürlich ist jeder willkommen, bei der Entwicklung des Projekts mitzuhelfen: PR erstellen, Issues erstellen, wenn möglich reagieren wir, verbessern usw.

Nachdem dies gesagt ist, das ist alles, Leute, kauft unsere Elefanten!



Source: habr.com

Kommentar hinzufügen