Wie haben wir das Wachstum von CityMobile sichergestellt?

Bild

Mein Name ist Ivan, ich bin der Leiter der Serverentwicklung bei Citimobil. Heute werde ich darüber sprechen, was genau diese Serverentwicklung ist, auf welche Probleme wir gestoßen sind und wie wir uns entwickeln wollen.

Beginn des Wachstums


Nur wenige Menschen wissen, dass CityMobile seit 13 Jahren existiert. Es war einmal eine kleine Firma, die nur in Moskau arbeitete. Es gab nur sehr wenige Entwickler, und sie wussten genau, wie das System funktioniert - weil sie es selbst erstellt haben. Der Taximarkt begann sich gerade zu entwickeln, die Ladungen waren Penny. Wir hatten nicht einmal die Aufgabe, Fehlertoleranz und Skalierung bereitzustellen.

Im Jahr 2018 investierte die Mail.Ru Group in CityMobile und wir begannen schnell zu wachsen. Es gab keine Zeit, die Plattform neu zu schreiben oder zumindest bedeutende Umgestaltungen vorzunehmen. Es war notwendig, ihre funktionalen Fähigkeiten zu entwickeln, um unseren Hauptkonkurrenten einzuholen und schnell Mitarbeiter einzustellen. Als ich in das Unternehmen eintrat, waren nur 20 Entwickler am Backend beteiligt, und fast alle standen vor Gericht. Aus diesem Grund haben wir uns entschlossen, „tief hängende Früchte zu pflücken“: einfache Änderungen vorzunehmen, die zu einem enormen Ergebnis führen.

Zu dieser Zeit hatten wir einen Monolithen in PHP und drei Dienste auf Go sowie eine Master-Basis in MySQL, auf die der Monolith zugegriffen hatte (die Dienste wurden als Repositorys von Redis und EasticSearch verwendet). Und allmählich, mit zunehmender Last, verlangsamten schwere Systemanforderungen die Basis.

Was könnte man damit machen?

Zuerst haben wir den offensichtlichen Schritt getan: einen Sklaven in Produktion zu bringen. Aber wenn viele schwere Anfragen zu ihm kommen, wird er es aushalten? Es war auch offensichtlich, dass der Sklave mit einer Fülle von Anfragen nach analytischen Berichten allmählich zurückbleiben würde. Ein starker Rückstand an Sklaven könnte die Leistung des gesamten CityMobile beeinträchtigen. Infolgedessen haben wir einen weiteren Sklaven für Analysten eingesetzt. Seine Verzögerung führt nie zu Problemen auf dem Produkt. Aber auch das schien uns nicht genug. Wir haben einen Tabellenreplikator von MySQL in Clickhouse geschrieben. Und heute lebt die Analytik von alleine und verwendet einen Stack, der mehr für OLAP entwickelt wurde.

In dieser Form arbeitete das System einige Zeit, dann tauchten Funktionen auf, die höhere Anforderungen an die Hardware stellten. Mit jeder Woche gab es immer mehr Anfragen: Nicht einmal eine Woche verging ohne einen neuen Rekord. Zusätzlich haben wir eine Zeitbombe unter unser System gelegt. Früher hatten wir nur einen Fehlerpunkt - die MySQL-Master-Basis, aber mit der Hinzufügung eines Slaves gab es zwei solche Punkte: den Master und den Slave. Ein Ausfall einer dieser Maschinen würde zu einem vollständigen Ausfall des Systems führen.

Um sich davor zu schützen, haben wir begonnen, einen lokalen Proxy zu verwenden, um Health-Check-Slaves durchzuführen. Dadurch konnten wir viele Slaves verwenden, ohne den Code zu ändern. Wir haben regelmäßige automatische Überprüfungen des Status jedes Slaves und seiner allgemeinen Metriken eingeführt:

  • la;
  • Sklavenverzögerung;
  • Hafenverfügbarkeit;
  • Anzahl der Schlösser usw.

Wenn ein bestimmter Schwellenwert überschritten wird, entfernt das System den Slave von der Last. Gleichzeitig kann jedoch nicht mehr als die Hälfte der Slaves zurückgezogen werden, so dass sie aufgrund der zunehmenden Belastung der verbleibenden Slaves keine Ausfallzeiten für sich selbst vereinbaren können. Als Proxy haben wir HAProxy verwendet und sofort einen Plan für den Wechsel zu ProxySQL in das Backlog aufgenommen. Die Wahl war etwas seltsam, aber unsere Administratoren hatten bereits gute Erfahrungen mit HAProxy, und das Problem war akut und erforderte eine frühzeitige Lösung. Daher haben wir ein ausfallsicheres System für Slaves erstellt, das sich leicht genug skalieren lässt. Bei aller Einfachheit hat sie uns nie im Stich gelassen.

Weiteres Wachstum


Als sich das Geschäft entwickelte, fanden wir einen weiteren Engpass in unserem System. Mit Änderungen der äußeren Bedingungen - zum Beispiel begann der Niederschlag in einer großen Region - stieg die Anzahl der Taxibestellungen schnell an. In solchen Situationen hatten die Fahrer keine Zeit, schnell genug zu reagieren, und es gab einen Mangel an Autos. Während der Verteilung von Aufträgen wurde eine Last auf MySQL-Slaves in einer Schleife erstellt.

Wir haben eine erfolgreiche Lösung gefunden - Tarantool. Es war schwierig, das System dafür neu zu schreiben, daher haben wir das Problem anders gelöst: mit dem Replikationstool mysql-tarantool-replicationReplikation einiger Tabellen von MySQL nach Tarantool. Alle Leseanfragen, die während des Mangels an Autos entstanden sind, haben wir in Tarantool ausgestrahlt und sind seitdem nicht mehr besorgt über Gewitter und Hurrikane! Und wir haben das Problem mit dem Fehlerpunkt noch einfacher gelöst: Wir haben sofort mehrere Replikate installiert, auf die wir über HAProxy auf Healthcheck zugreifen. Jede Tarantool-Instanz wird von einem separaten Replikator repliziert. Als angenehmen Bonus haben wir auch das Problem der Verzögerung von Slaves in diesem Codeabschnitt gelöst: Die Replikation von MySQL nach Tarantool funktioniert viel schneller als von MySQL nach MySQL.

Unsere Master-Basis war jedoch immer noch ein Fehlerpunkt und skalierte nicht auf Aufzeichnungsvorgängen. Wir haben begonnen, dieses Problem auf diese Weise zu lösen.

Erstens hatten wir zu diesem Zeitpunkt bereits begonnen, aktiv neue Dienste zu schaffen (zum Beispiel Betrugsbekämpfung, über die meine Kollegen bereits geschrieben haben ). Darüber hinaus erforderten die Dienste sofort eine Skalierbarkeit des Speichers. Für Redis haben wir begonnen, nur Redis-Cluster und für Tarantool - Vshard zu verwenden. Wo wir MySQL verwenden, haben wir begonnen, Vitess für neue Logik zu verwenden . Solche Datenbanken können sofort geteilt werden, sodass bei der Aufzeichnung fast keine Probleme auftreten. Wenn sie plötzlich auftreten, können sie leicht durch Hinzufügen von Servern gelöst werden. Jetzt verwenden wir Vitess nur für unkritische Dienste und untersuchen die Fallstricke, aber in Zukunft wird es in allen MySQL-Datenbanken verfügbar sein.

Zweitens, da es schwierig und langwierig war, Vitess für die bereits vorhandene Logik zu implementieren, gingen wir einfacher, wenn auch weniger universell vor: Wir begannen, die Master-Basis Tabelle für Tabelle auf verschiedenen Servern zu verteilen. Wir hatten großes Glück: Es stellte sich heraus, dass die Hauptlast des Datensatzes durch Tabellen erstellt wird, die für die Hauptfunktionalität nicht kritisch sind. Und wenn wir solche Tabellen erstellen, schaffen wir keine zusätzlichen Punkte für Geschäftsfehler. Der Hauptfeind für uns war die starke Verbindung der Tabellen im Code mit JOINs (es gab jeweils JOINs und 50-60 Tabellen). Wir schneiden sie gnadenlos.

Jetzt ist es an der Zeit, zwei sehr wichtige Muster für den Entwurf von Hochlastsystemen in Erinnerung zu rufen:

  • Graceful degradation. , - . , , , , .. , .
  • Circuit breaker. , . , , , . ? ( - graceful degradation). , FPM- , . - ( ) , . , - , ( ).

Wir haben also zumindest angefangen zu skalieren, aber es gab immer noch Fehlerquellen.

Dann haben wir uns für die halbsynchrone Replikation entschieden (und diese erfolgreich implementiert). Was ist seine Funktion? Wenn der Cleaner im Rechenzentrum während der normalen asynchronen Replikation einen Eimer Wasser auf den Server schüttet, haben die letzten Transaktionen keine Zeit, sich auf die Slaves zu replizieren, und gehen verloren. Und wir müssen sicher sein, dass wir in diesem Fall keine ernsthaften Probleme haben werden, nachdem einer der Sklaven ein neuer Meister geworden ist. Aus diesem Grund haben wir beschlossen, Transaktionen überhaupt nicht zu verlieren, und dafür haben wir die halbsynchrone Replikation verwendet. Jetzt können die Slaves verzögern, aber selbst wenn der Master-Datenbankserver zerstört wird, werden Informationen zu allen Transaktionen auf mindestens einem Slave gespeichert.

Dies war der erste Schritt zum Erfolg. Der zweite Schritt war die Verwendung des Orchestrator-Dienstprogramms. Wir überwachen auch ständig alle MySQL im System. Wenn die Master-Basis ausfällt, macht die Automatisierung den Master zum neuesten Slave (und unter Berücksichtigung der halbsynchronen Replikation enthält er alle Transaktionen) und schaltet die gesamte Schreiblast darauf um. So können wir jetzt die Geschichte einer Putzfrau und eines Eimers Wasser nacherleben.

Was weiter?

Als ich zu CityMobile kam, hatten wir drei Dienste und einen Monolithen. Heute gibt es mehr als 20 von ihnen. Und die Hauptsache, die unser Wachstum bremst, ist, dass wir immer noch eine einzige Master-Basis haben. Wir bekämpfen dies heldenhaft und teilen es in separate Basen auf.

Wie entwickeln wir uns weiter?


Erweitern Sie den Satz von Microservices. Dies wird viele der Probleme lösen, mit denen wir heute konfrontiert sind. Zum Beispiel hat das Team keine einzige Person mehr, die das Gerät des gesamten Systems kennt. Und da wir aufgrund des rasanten Wachstums nicht immer über eine aktuelle Dokumentation verfügen und es sehr schwierig ist, diese zu pflegen, ist es für Anfänger schwierig, sich mit dem Verlauf der Dinge zu befassen. Und wenn das System aus zahlreichen Diensten besteht, ist das Schreiben von Dokumentationen für jeden von ihnen unvergleichlich einfacher. Und die Menge an Code für eine einmalige Studie wird stark reduziert.

Geh zu gehen.Ich liebe diese Sprache wirklich, aber ich habe immer geglaubt, dass es nicht praktisch ist, Arbeitscode von einer Sprache in eine andere umzuschreiben. Die Erfahrung hat jedoch kürzlich gezeigt, dass PHP-Bibliotheken, selbst die Standardbibliotheken und die beliebtesten, nicht von höchster Qualität sind. Das heißt, wir patchen viele Bibliotheken. Nehmen wir an, das SRE-Team hat die Standardbibliothek für die Interaktion mit RabbitMQ gepatcht: Es stellte sich heraus, dass eine so grundlegende Funktion wie Timeout nicht funktionierte. Und je tiefer das SRE-Team und ich diese Probleme verstehen, desto klarer wird, dass nur wenige Menschen über Zeitüberschreitungen in PHP nachdenken, nur wenige über das Testen von Bibliotheken nachdenken und nur wenige über Sperren nachdenken. Warum wird dies für uns zu einem Problem? Weil Go-Lösungen viel einfacher zu warten sind.

Was beeindruckt mich sonst noch mit Go? Seltsamerweise ist das Schreiben sehr einfach. Darüber hinaus macht es Go einfach, eine Vielzahl von Plattformlösungen zu erstellen. Diese Sprache verfügt über sehr leistungsfähige Standardwerkzeuge. Wenn sich unser Backend aus irgendeinem Grund plötzlich verlangsamt, gehen Sie einfach zu einer bestimmten URL und Sie können alle Statistiken anzeigen - das Speicherzuordnungsdiagramm, um zu verstehen, wo der Prozess im Leerlauf ist. In PHP ist es schwieriger, Leistungsprobleme zu identifizieren.

Darüber hinaus verfügt Go über sehr gute Linters - Programme, die automatisch die häufigsten Fehler für Sie finden. Die meisten von ihnen sind im Artikel „50 Shades of Go“ beschrieben, und Linters erkennen sie perfekt.

Scherben Sie die Basen weiter. Wir werden bei allen Diensten zu Vitess wechseln.

Übersetzung eines PHP-Monolithen in einen Redis-Cluster.In unseren Diensten hat sich redis-cluster als ausgezeichnet erwiesen. Leider ist die Implementierung in PHP schwieriger. Der Monolith verwendet Befehle, die von Redis-Cluster nicht unterstützt werden (was gut ist, solche Befehle bringen mehr Probleme als Vorteile).

Wir werden die Probleme von RabbitMQ untersuchen. Es wird angenommen, dass RabbitMQ nicht die zuverlässigste Software ist. Wir werden dieses Problem untersuchen, Probleme finden und lösen. Vielleicht denken wir darüber nach, zu Kafka oder Tarantool zu wechseln.

All Articles