Mikhail Salosin. Golang Meetup. Verwenden von Go im Backend der Watch + -Anwendung

Mikhail Salosin (im Folgenden - MS): - Hallo allerseits! Ich heiße Michael. Ich arbeite als Backend-Entwickler bei MC2 Software und werde über die Verwendung von Go im Backend der mobilen Watch + -Anwendung sprechen.



Präsentiert jemand gerne Hockey?



Dann ist diese App genau das Richtige für Sie. Es ist für Android und iOS gedacht und wird verwendet, um Sendungen verschiedener Sportereignisse online und in Aufzeichnungen anzusehen. Außerdem enthält die Anwendung verschiedene Statistiken, Textsendungen, Tabellen für Konferenzen, Turniere und andere Informationen, die für Fans nützlich sind.



Auch in der Anwendung gibt es so etwas wie Videomomente, dh Sie können die akuten Momente von Spielen (Tore, Kämpfe, Schießereien usw.) sehen. Wenn Sie nicht die gesamte Sendung sehen möchten, können Sie nur die interessantesten sehen.

Was wurde in der Entwicklung verwendet?


Der Hauptteil wurde in Go geschrieben. Die API, mit der die mobilen Clients kommunizierten, wurde in Go geschrieben. Außerdem wurde auf Go ein Dienst zum Senden von Push-Benachrichtigungen an Mobilgeräte geschrieben. Wir mussten auch unser eigenes ORM schreiben, über das wir vielleicht eines Tages sprechen werden. Nun, einige kleine Dienste sind auf Go geschrieben: Größenänderung und Hochladen von Bildern für die Redakteure ...

Wir haben Postgres (PostgreSQL) als Datenbank verwendet. Die Schnittstelle für Editoren wurde in Ruby on Rails mit dem ActiveAdmin-Juwel geschrieben. Der Import von Statistiken vom Statistikanbieter ist ebenfalls auf Ruby geschrieben.

Für System-API-Tests haben wir Python unittest (Python) verwendet. Memcached wird verwendet, um API-Zahlungsanforderungen zu drosseln, Chef, um die Konfiguration zu steuern, Zabbix, um interne Systemstatistiken zu sammeln und zu überwachen. Graylog2 - Zum Sammeln von Protokollen ist Slate eine API-Dokumentation für Clients.



Protokollauswahl


Das erste Problem, auf das wir gestoßen sind: Wir mussten ein Protokoll für die Interaktion des Backends mit mobilen Clients auswählen, basierend auf den folgenden Punkten ...

  • Die wichtigste Anforderung: Daten zu Kunden müssen in Echtzeit aktualisiert werden. Das heißt, jeder, der gerade die Sendung sieht, sollte fast sofort Updates erhalten.
  • Der Einfachheit halber haben wir akzeptiert, dass Daten, die mit Clients synchronisiert sind, nicht gelöscht, sondern mithilfe spezieller Flags ausgeblendet werden.
  • Alle Arten von seltenen Anfragen (wie Statistiken, Aufstellungen, Teamstatistiken) werden von normalen GET-Anfragen empfangen.
  • Außerdem sollte das System gleichzeitig 100.000 Benutzern ruhig standhalten.

Basierend darauf hatten wir zwei Protokolloptionen:
  1. Websockets. Wir brauchten jedoch keine Kanäle vom Client zum Server. Wir mussten nur Updates vom Server an den Client senden, daher ist ein Web-Socket eine redundante Option.
  2. Server-Sent Events (SSE) waren genau richtig! Es ist ganz einfach und erfüllt im Grunde alles, was wir brauchen.

Vom Server gesendete Ereignisse


Ein paar Worte darüber, wie dieses Ding

funktioniert ... Es funktioniert über der http-Verbindung. Der Client sendet eine Anfrage, der Server antwortet mit Content-Type: text / event-stream und schließt die Verbindung zum Client nicht, sondern schreibt weiterhin Daten in die Verbindung:



Daten können in einem mit den Clients vereinbarten Format gesendet werden. In unserem Fall haben wir in dieser Form gesendet: Im Ereignisfeld wurde der Name der geänderten Struktur (Person, Spieler) gesendet und im Datenfeld - JSON mit neuen, geänderten Feldern für den Spieler.

Nun dazu, wie die Interaktion selbst funktioniert.
  • Zunächst ermittelt der Client, wann die letzte Synchronisierung mit dem Dienst durchgeführt wurde: Er überprüft seine lokale Datenbank und ermittelt das Datum der letzten von ihm aufgezeichneten Änderung.
  • Er sendet eine Anfrage mit diesem Datum.
  • Als Antwort senden wir ihm alle Aktualisierungen, die an diesem Datum aufgetreten sind.
  • Danach stellt er eine Verbindung zum Live-Kanal her und schließt erst, wenn er diese Updates benötigt:



Wir senden ihm eine Liste mit Änderungen: Wenn jemand ein Tor erzielt hat - wir ändern die Punktzahl des Spiels, wurden verletzt -, wird dies ebenfalls in Echtzeit gesendet. Somit erhalten Kunden im Ereignisstrom des Spiels sofort relevante Daten. Damit der Client versteht, dass der Server nicht gestorben ist, dass ihm nichts passiert ist, senden wir regelmäßig alle 15 Sekunden einen Zeitstempel, damit er weiß, dass alles in Ordnung ist und keine erneute Verbindung erforderlich ist.

Wie wird die Live-Verbindung bedient?


  • Zunächst erstellen wir einen Kanal, in den Updates mit einem Puffer gelangen.
  • Danach abonnieren wir diesen Kanal, um Updates zu erhalten.
  • Stellen Sie den richtigen Header ein, damit der Client weiß, dass alles in Ordnung ist.
  • ping. timestamp .
  • , . timestamp, , .



Das erste Problem, auf das wir gestoßen sind, war das folgende: Für jede Verbindung, die mit dem Client geöffnet wurde, haben wir einen Timer erstellt, der alle 15 Sekunden aktiviert wurde. Es stellte sich heraus, dass wir 6.000 Verbindungen mit einem Computer (mit einem API-Server) hatten. 6 Tausend Timer wurden erstellt. Dies führte dazu, dass die Maschine nicht die notwendige Last hielt. Das Problem war für uns nicht so offensichtlich, aber sie haben uns ein wenig geholfen, und wir haben es beseitigt.

Infolgedessen kommt jetzt ein Ping von demselben Kanal, von dem das Update stammt.

Dementsprechend gibt es nur einen Timer, der alle 15 Sekunden einmal tickt.

Hier sind einige Hilfsfunktionen: Senden des Headers, des Pings und der Struktur selbst. Das heißt, der Name der Tabelle (Person, Spiel, Saison) und die Informationen zu diesem Datensatz werden hier übertragen:



Mechanismus zum Senden von Updates


Nun ein wenig darüber, woher die Änderungen kommen. Wir haben mehrere Leute, Redakteure, die die Sendung in Echtzeit sehen. Sie erstellen alle Ereignisse: Jemand wurde entfernt, jemand wurde verletzt, eine Art Ersatz ...

Mit Hilfe von CMS gelangen die Daten in die Datenbank. Danach benachrichtigt die Datenbank, die den Listen / Notify-Mechanismus verwendet, die API-Server darüber. API-Server senden diese Informationen bereits an Clients. Daher sind nur wenige Server mit der Datenbank verbunden, und die Datenbank wird nicht besonders belastet, da der Client in keiner Weise direkt mit der Datenbank interagiert:



PostgreSQL: Hören / Benachrichtigen


Mit dem Listen / Notify-Mechanismus in Postgres können Sie Abonnenten über Ereignisse informieren, bei denen sich ein Ereignis geändert hat. In der Datenbank wurde eine Art Datensatz erstellt. Dazu haben wir einen einfachen Auslöser und eine einfache Funktion geschrieben:



Beim Einfügen oder Ändern eines Datensatzes rufen wir die Benachrichtigungsfunktion auf dem Kanal data_updates auf, übertragen den Tabellennamen und die Kennung des Datensatzes, der dort geändert oder eingefügt wurde.

Für alle Tabellen, die mit dem Client synchronisiert werden sollen, definieren wir einen Trigger, der nach dem Ändern / Aktualisieren des Datensatzes die auf der Folie unten angegebene Funktion aufruft.
Wie abonniert die API diese Änderungen?

Der Fanout-Mechanismus wird erstellt - er sendet Nachrichten an den Client. Er sammelt alle Kundenkanäle und sendet Updates, die er über diese Kanäle erhalten hat:



Hier überprüft die Standard-pq-Bibliothek, die eine Verbindung zur Datenbank herstellt und angibt, dass sie den Kanal abhören möchte (data_updates), dass die Verbindung offen ist und alles in Ordnung ist. Ich lasse die Fehlerprüfung aus, um Platz zu sparen (überprüfen Sie nicht die Belastung).

Als nächstes setzen wir den Ticker asynchron, der alle 15 Sekunden einen Ping sendet, und beginnen, den von uns abonnierten Kanal anzuhören. Wenn wir einen Ping haben, veröffentlichen wir diesen Ping. Wenn wir einen Datensatz erhalten haben, veröffentlichen wir diesen Datensatz für alle Abonnenten dieses Fanouts.

Wie funktioniert Fan-Out?


Auf Russisch bedeutet dies „Splitter“. Wir haben ein Objekt, das Abonnenten registriert, die Updates erhalten möchten. Sobald ein Update für dieses Objekt eintrifft, wird dieses Update an alle Abonnenten weitergegeben. Einfach genug:



Wie es auf Go implementiert wird:



Es gibt eine Struktur, die mit Mutexen synchronisiert wird. Es verfügt über ein Feld, in dem der Status der Fanout-Verbindung zur Datenbank gespeichert wird, dh im Moment, in dem Aktualisierungen abgehört und empfangen werden, sowie eine Liste aller verfügbaren Kanäle - Map, deren Schlüssel der Kanal und die Struktur in Form von Werten ist (tatsächlich) in keiner Weise verwendet).

Mit zwei Methoden - Verbunden und Verbunden - können Sie Fanout mitteilen, dass eine Verbindung zur Basis besteht und die Verbindung zur Basis getrennt ist. Im zweiten Fall müssen Sie alle Clients trennen und ihnen mitteilen, dass sie nichts mehr hören können und dass sie sich wieder verbinden, da die Verbindung zu ihnen geschlossen wurde.

Es gibt auch eine Subscribe-Methode, mit der Listenern ein Kanal



hinzugefügt wird : Es gibt eine Unsubscribe-Methode, mit der ein Kanal von Listenern entfernt wird, wenn der Client die Verbindung trennt, sowie eine Publish-Methode, mit der Sie eine Nachricht an alle Abonnenten senden können.

Frage: - Was wird über diesen Kanal übertragen?

MS: - Es wird ein Modell übertragen, das sich geändert hat oder pingt (im Wesentlichen nur eine Zahl, eine Ganzzahl).

MS:- Sie können alles senden, jede Struktur veröffentlichen, es wird einfach zu JSON und das wars.

MS: - Wir erhalten eine Benachrichtigung von Postgres - sie enthält den Tabellennamen und die Kennung. Durch den Namen der Tabelle, die wir erhalten, und den Bezeichner erhalten wir den Datensatz, den wir benötigen, und diese Struktur wird bereits zur Veröffentlichung gesendet.

Infrastruktur


Wie sieht es in Bezug auf die Infrastruktur aus? Wir haben 7 Eisenserver: Einer davon ist vollständig der Basis gewidmet, die restlichen sechs drehen sich auf virtuellen Computern. Es gibt 6 Kopien der API: Jede virtuelle Maschine mit der API wird auf einem separaten Eisenserver ausgeführt - dies dient der Zuverlässigkeit.



Wir haben zwei Frontends, auf denen Keepalived installiert ist, um die Zugänglichkeit zu verbessern, sodass in diesem Fall ein Frontend das andere ersetzen kann. Weitere zwei Kopien des CMS.

Es gibt auch einen Importeur von Statistiken. Es gibt einen DB-Slave, von dem regelmäßig Sicherungen durchgeführt werden. Es gibt Pigeon Pusher - eine Anwendung, die Pushies an Kunden sowie Infrastruktur-Dinge sendet: Zabbix, Graylog2 und Chef.

Tatsächlich ist diese Infrastruktur redundant, da 100.000 mit weniger Servern bedient werden können. Aber es gab Eisen - wir haben es benutzt (uns wurde gesagt, dass es möglich ist - warum nicht).

Vorteile von Go


Nachdem wir an dieser Anwendung gearbeitet hatten, wurden solche offensichtlichen Vorteile von Go aufgedeckt.
  • Coole http Bibliothek. Mit ihm können Sie bereits eine Menge erstellen.
  • Außerdem die Kanäle, mit denen wir den Mechanismus zum Senden von Benachrichtigungen an Kunden sehr einfach implementieren konnten.
  • Mit dem wunderbaren Race-Detektor konnten wir mehrere kritische Fehler (Staging-Infrastruktur) beseitigen. Alles, was beim Staging funktioniert, wird ausgeführt und mit dem Race-Schlüssel kompiliert. und dementsprechend können wir sehen, welche potenziellen Probleme wir mit der Staging-Infrastruktur haben.
  • Minimalismus und Einfachheit der Sprache.




Wir suchen Entwickler! Wenn jemand will - bitte.

Fragen


Frage des Publikums (im Folgenden - B): - Mir scheint, Sie haben einen wichtigen Punkt in Bezug auf das Fan-out übersehen. Ich verstehe richtig, dass Sie blockiert werden, wenn Sie eine Antwort an einen Client senden, wenn der Client nicht lesen möchte.

MS: - Nein, wir blockieren nicht. Erstens haben wir alles hinter nginx, das heißt, es gibt keine Probleme mit langsamen Clients. Zweitens hat der Client einen Kanal mit einem Puffer - tatsächlich können wir dort bis zu hundert Aktualisierungen vornehmen ... Wenn wir nicht in den Kanal schreiben können, wird er gelöscht. Wenn wir sehen, dass der Kanal blockiert ist, schließen wir ihn einfach und fertig - der Client stellt die Verbindung wieder her, wenn ein Problem auftritt. Daher tritt hier grundsätzlich keine Blockierung auf.

F: - Könnten Sie sofort einen Datensatz an Listen / Notify senden, keine Kennungstabelle?

MS:- Listen / Notify hat ein Limit von 8.000 Bytes pro Preload, das gesendet wird. Im Prinzip wäre es möglich zu senden, wenn wir mit einer kleinen Datenmenge zu tun hätten, aber es scheint mir, dass der Weg [wie wir] einfach zuverlässiger ist. Einschränkungen sind in Postgres selbst.

F: - Erhalten Kunden Updates zu Spielen, an denen sie nicht interessiert sind?

MS:- Im Allgemeinen ja. In der Regel gibt es 2-3 Übereinstimmungen parallel und dann ziemlich selten. Wenn der Kunde etwas sieht, beobachtet er normalerweise das laufende Spiel. Dann gibt es auf dem Client eine lokale Datenbank, in der sich alle diese Updates summieren, und selbst ohne Internetverbindung kann der Client alle früheren Übereinstimmungen anzeigen, für die er Updates hat. Tatsächlich synchronisieren wir unsere Datenbank auf dem Server mit der lokalen Datenbank des Clients, damit sie offline arbeiten kann.

F: - Warum hast du dein ORM gemacht?

Alexey (einer der Entwickler von "Watch +"):- Zu dieser Zeit (es war vor einem Jahr) war ORM weniger als jetzt, wenn es ziemlich viele von ihnen gibt. Von den meisten vorhandenen ORMs mag ich am wenigsten, dass die meisten von ihnen an leeren Schnittstellen arbeiten. Das heißt, die Methoden, die in diesen ORMs bereit sind, alles zu übernehmen: Struktur, Strukturzeiger, Zahl, etwas, das überhaupt nicht relevant ist ...

Unser ORM generiert Strukturen basierend auf dem Datenmodell. Selbst. Und deshalb sind alle Methoden konkret, verwenden keine Reflexion usw. Sie akzeptieren Strukturen und erwarten, dass sie die kommenden Strukturen verwenden.

F: - Wie viele Personen haben teilgenommen?

MS: - In der Anfangsphase nahmen zwei Personen teil. Irgendwann im Juni haben wir angefangen, im August war der Hauptteil fertig (erste Version). Im September gab es eine Veröffentlichung.

BEIM:- Wenn Sie SSE beschreiben, verwenden Sie kein Timeout. Warum so?

MS: - Um ehrlich zu sein, ist SSE immer noch ein HTML5-Protokoll: Der SSE-Standard ist so konzipiert, dass er mit Browsern kommuniziert, so wie ich es verstehe. Es verfügt über zusätzliche Funktionen, mit denen Browser die Verbindung wiederherstellen können (usw.), die wir jedoch nicht benötigen, da wir Clients hatten, die eine Logik zum Verbinden und Empfangen von Informationen implementieren konnten. Wir haben eher nicht SSE gemacht, sondern etwas Ähnliches wie SSE. Dies ist nicht das Protokoll selbst.
Es gab keine Notwendigkeit. Soweit ich weiß, haben Clients den Verbindungsmechanismus von Grund auf neu implementiert. Es war ihnen im Prinzip egal.

F: - Welche zusätzlichen Dienstprogramme haben Sie verwendet?

MS:- Am aktivsten haben wir Govet und Golint verwendet, damit der Stil vereinheitlicht wurde, sowie Gofmt. Sie haben nichts anderes benutzt.

F: - Womit haben Sie debuggt?

MS: - Im Großen und Ganzen wurde das Debuggen mithilfe von Tests durchgeführt. Kein Debugger, GOP haben wir nicht benutzt.

F: - Können Sie die Folie zurückgeben, auf der die Veröffentlichungsfunktion implementiert ist? Einbuchstaben-Variablennamen stören Sie nicht?

MS: - Nein. Sie haben einen ziemlich "engen" Umfang. Sie werden nirgendwo anders verwendet (außer hier) (mit Ausnahme der Innenseiten dieser Klasse), und es ist sehr kompakt - es dauert nur 7 Zeilen.

F: - Irgendwie ist es immer noch nicht intuitiv ...

MS:- Nein, nein, das ist ein echter Code! Es geht nicht um Stil. Es ist nur eine so nützliche, sehr kleine Klasse - es gibt nur 3 Felder innerhalb der Klasse ...



MS: - Im Großen und Ganzen ändern sich alle Daten, die mit Kunden synchronisiert werden (saisonale Spiele, Spieler), nicht. Grob gesagt, wenn wir eine andere Sportart betreiben wollen, bei der das Spiel geändert werden muss, werden wir einfach alles in der neuen Version des Kunden berücksichtigen und die alten Versionen des Kunden werden gesperrt.

F: - Gibt es Pakete von Drittanbietern für das Abhängigkeitsmanagement?

MS: - Wir haben go dep verwendet.

F: - Das Thema des Berichts enthielt etwas zu dem Video, aber nichts zu dem Video im Bericht.

MS: - Nein, ich habe nichts zum Thema des Videos. Es heißt "Look +" - dies ist der Name der Anwendung.

BEIM:- Sie sagten, dass Sie zu Kunden streamen? ..

MS: - Wir haben kein Video gestreamt. Dies wurde komplett von Megaphone gemacht. Ja, ich habe nicht gesagt, dass die Anwendung Megaphon ist.

MS: - Go - um alle Daten zu senden - nach Punktzahl, nach Spielereignissen, Statistiken ... Go - dies ist das gesamte Backend für die Anwendung. Der Kunde muss irgendwo herausfinden, welchen Link er für den Spieler verwenden soll, damit der Benutzer das Spiel sehen kann. Wir haben Links zu Videos und Streams, die vorbereitet werden.


Ein bisschen Werbung :)


Vielen Dank für Ihren Aufenthalt bei uns. Gefällt dir unser Artikel? Möchten Sie weitere interessante Materialien sehen? Unterstützen Sie uns, indem Sie eine Bestellung aufgeben oder Ihren Freunden Cloud-basiertes VPS für Entwickler ab 4,99 US-Dollar empfehlen , ein einzigartiges Analogon von Einstiegsservern, das von uns für Sie erfunden wurde: Die ganze Wahrheit über VPS (KVM) E5-2697 v3 (6 Kerne) 10 GB DDR4 480 GB SSD 1 Gbit / s ab 19 $ oder wie teilt man den Server? (Optionen sind mit RAID1 und RAID10, bis zu 24 Kernen und bis zu 40 GB DDR4 verfügbar).

Dell R730xd 2-mal günstiger im Equinix Tier IV-Rechenzentrum in Amsterdam? Nur wir haben 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2,6 GHz 14C 64 GB DDR4 4 x 960 GB SSD 1 Gbit / s 100 TV von 199 US-Dollar in den Niederlanden!Dell R420 - 2x E5-2430 2,2 GHz 6C 128 GB DDR3 2x960 GB SSD 1 Gbit / s 100 TB - ab 99 US-Dollar! Lesen Sie mehr über den Aufbau eines Infrastrukturgebäudes. Klasse C mit Dell R730xd E5-2650 v4-Servern für 9.000 Euro für einen Cent?

All Articles