Mikhail Salosin. Golang Meetup. Nutzung von Go im Backend der Anwendung „Smotri+“.

Michail Salosin (im Folgenden – MS): – Hallo zusammen! Ich heiße Michail. Ich arbeite als Backend-Entwickler bei der Firma MC2 Software und ich werde ĂŒber die Verwendung von Go im Backend der mobilen Anwendung „Smotri+“ berichten.

Mikhail Salosin. Golang Meetup. Nutzung von Go im Backend der Anwendung „Smotri+“.

Hat jemand von den Anwesenden Lust auf Hockey?

Mikhail Salosin. Golang Meetup. Nutzung von Go im Backend der Anwendung „Smotri+“.

Dann ist diese App genau das Richtige fĂŒr Sie. Sie ist fĂŒr Android und iOS und dient dazu, Live-Übertragungen verschiedener Sportereignisse online und im Replay anzusehen. Außerdem bietet die App verschiedene Statistiken, TextĂŒbertragungen, Tabellen zu Konferenzen, Turnieren und sonstige Informationen, die fĂŒr Fans nĂŒtzlich sind.

Mikhail Salosin. Golang Meetup. Nutzung von Go im Backend der Anwendung „Smotri+“.

In der App gibt es auch die Möglichkeit, Videomomente anzusehen, d. h. man kann die spannenden Momente der Spiele (Tore, KĂ€mpfe, Penaltys usw.) sehen. Wenn Sie nicht die gesamte Übertragung sehen möchten, können Sie sich nur die interessantesten Teile anschauen.

Was haben Sie in der Entwicklung verwendet?

Der Hauptteil wurde in Go geschrieben. Die API, mit der die mobilen Clients kommunizierten, war in Go geschrieben. Auch der Service zum Versenden von Push-Benachrichtigungen an mobile GerĂ€te wurde in Go erstellt. DarĂŒber hinaus mussten wir unser eigenes ORM schreiben, ĂŒber das wir vielleicht eines Tages berichten werden. Außerdem wurden in Go einige kleinere Services wie das Resizing und das Hochladen von Bildern fĂŒr die Redaktion geschrieben...

Als Datenbank haben wir PostgreSQL verwendet. Die OberflĂ€che fĂŒr die Redakteure wurde mit Ruby on Rails unter Verwendung des Gems (gem) ActiveAdmin geschrieben. Der Import von Statistiken vom Statistikanbieter wurde auch in Ruby realisiert.

FĂŒr systematische API-Tests haben wir das Unittest-Framework von Python verwendet. Memcached wird zur Drosselung von API-Zahlungsanfragen eingesetzt, Chef fĂŒr das Konfigurationsmanagement, Zabbix fĂŒr die Erfassung und Überwachung interner statistischer Daten des Systems. Graylog2 wird zum Sammeln von Logs verwendet, Slate ist die API-Dokumentation fĂŒr die Kunden.

Mikhail Salosin. Golang Meetup. Nutzung von Go im Backend der Anwendung „Smotri+“.

Protokollwahl

Das erste Problem, mit dem wir konfrontiert waren: Wir mussten ein Protokoll fĂŒr die Interaktion zwischen Backend und mobilen Clients wĂ€hlen, basierend auf den folgenden Punkten...

  • Das wichtigste Kriterium: Die Daten auf den Clients mĂŒssen in Echtzeit aktualisiert werden. Das heißt, alle, die gerade die Übertragung sehen, sollten praktisch sofortige Updates erhalten.
  • Zur Vereinfachung haben wir angenommen, dass die Daten, die mit den Clients synchronisiert werden, nicht gelöscht, sondern durch spezielle Flags versteckt werden.
  • Verschiedene seltene Anfragen (wie Statistiken, Teamzusammensetzungen, Teamstatistiken) erfolgen ĂŒber normale GET-Anfragen.
  • Außerdem musste das System problemlos 100.000 Benutzer gleichzeitig verkraften.

Daraus ergeben sich zwei Protokolloptionen:

  1. Websockets. Aber wir benötigten keine KanĂ€le vom Client zum Server. Wir mussten nur Aktualisierungen vom Server an den Client senden, daher ist Websocket eine ĂŒberflĂŒssige Wahl.
  2. Server-Sent Events (SSE) passten perfekt! Es ist ziemlich einfach und erfĂŒllt im Prinzip alles, was wir brauchen.

Server-Sent Events

Ein paar Worte dazu, wie dieses Ding funktioniert


Es arbeitet ĂŒber eine HTTP-Verbindung. Der Client sendet eine Anfrage, der Server antwortet mit Content-Type: text/event-stream und schließt die Verbindung mit dem Client nicht, sondern schreibt weiterhin Daten in die Verbindung:

Mikhail Salosin. Golang Meetup. Nutzung von Go im Backend der Anwendung „Smotri+“.

Daten können im Format gesendet werden, das mit den Clients vereinbart wurde. In unserem Fall haben wir es so gesendet: im Feld event wurde der Name der geĂ€nderten Struktur (Person, Spieler) gesendet, und im Feld data – ein JSON mit den neuen, geĂ€nderten Feldern fĂŒr den Spieler.

Jetzt zur Interaktion selbst.

  • Zuerst bestimmt der Client, wann die letzte Synchronisation mit dem Dienst stattfand: Er schaut in seiner lokalen Datenbank nach und bestimmt das Datum der letzten Änderung, die bei ihm aufgezeichnet wurde.
  • Er sendet eine Anfrage mit diesem Datum.
  • Als Antwort senden wir ihm alle Aktualisierungen, die seit diesem Datum erfolgt sind.
  • Danach stellt er eine Verbindung zum Live-Kanal her und schließt sie nicht, solange er diese Aktualisierungen benötigt:

Mikhail Salosin. Golang Meetup. Nutzung von Go im Backend der Anwendung „Smotri+“.

Wir schicken ihm eine Liste von Änderungen: Wenn jemand ein Tor schießt, Ă€ndern wir den Spielstand, erhĂ€lt er eine Verletzung – wird auch in Echtzeit gesendet. So erhalten die Clients in der Ereignisliste des Spiels sofort aktuelle Daten. Periodisch, damit der Client versteht, dass der Server nicht abgestĂŒrzt ist, dass ihm nichts passiert ist, senden wir alle 15 Sekunden einen Zeitstempel – damit er weiß, dass alles in Ordnung ist und er sich nicht neu verbinden muss.

Wie wird die Live-Verbindung gewartet?

  • Zuerst erstellen wir einen Kanal, in den die Aktualisierungen mit einem Puffer kommen.
  • Danach abonnieren wir diesen Kanal, um Aktualisierungen zu erhalten.
  • Wir setzen den richtigen Header, damit der Client weiß, dass alles in Ordnung ist.
  • Wir senden das erste Ping. Einfach den aktuellen Zeitstempel der Verbindung aufzeichnen.
  • Nach diesem Schritt lesen wir im Loop aus dem Kanal, bis der Update-Kanal geschlossen wird. In den Kanal kommt regelmĂ€ĂŸig entweder der aktuelle Timestamp oder Änderungen, die wir bereits in den geöffneten Verbindungen aufnehmen.

Mikhail Salosin. Golang Meetup. Nutzung von Go im Backend der Anwendung „Smotri+“.

Das erste Problem, mit dem wir konfrontiert waren, bestand darin, dass wir fĂŒr jede mit dem Client geöffnete Verbindung einen Timer erstellt haben, der alle 15 Sekunden tickte – das bedeutet, wenn wir 6000 Verbindungen zu einem Rechner (einem API-Server) offen hatten, wurden 6000 Timer erstellt. Das fĂŒhrte dazu, dass die Maschine die erforderliche Last nicht tragen konnte. Das Problem war fĂŒr uns nicht so offensichtlich, aber wir bekamen etwas UnterstĂŒtzung und haben es behoben.

In der Folge kommt unser Ping jetzt aus demselben Kanal, aus dem die Updates kommen.

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

Hier sind einige Hilfsfunktionen – das Senden des Headers, des Pings und der Struktur selbst. Das heißt, hier wird der Tabellenname (person, match, season) und die Informationen ĂŒber diesen Datensatz ĂŒbergeben:

Mikhail Salosin. Golang Meetup. Nutzung von Go im Backend der Anwendung „Smotri+“.

Mechanismus zum Senden von Updates

Jetzt ein wenig darĂŒber, woher die Änderungen kommen. Wir haben mehrere Personen, Redakteure, die die Übertragung in Echtzeit beobachten. Sie erzeugen alle Ereignisse: jemand wurde entfernt, jemand hat sich verletzt, ein Wechsel


Mithilfe von CMS gelangen die Daten in die Datenbank. Danach benachrichtigt die Datenbank ĂŒber den Listen/Notify-Mechanismus die API-Server darĂŒber. Die API-Server verbreiten diese Informationen dann an die Clients. So haben wir faktisch nur einige Server, die mit der Datenbank verbunden sind, und es gibt keine besondere Last auf der Datenbank, da der Client in keiner Weise direkt mit der Datenbank interagiert:

Mikhail Salosin. Golang Meetup. Nutzung von Go im Backend der Anwendung „Smotri+“.

PostgreSQL: Listen/Notify

Der Listen/Notify-Mechanismus in PostgreSQL ermöglicht es, Abonnenten ĂŒber Änderungen an Ereignissen zu informieren – wenn ein bestimmter Datensatz in der Datenbank erstellt wurde. Dazu haben wir einen einfachen Trigger und eine Funktion geschrieben:

Mikhail Salosin. Golang Meetup. Nutzung von Go im Backend der Anwendung „Smotri+“.

Bei einem Insert oder einer Änderung des Datensatzes rufen wir die Funktion notify auf dem Kanal data_updates auf, ĂŒbergeben den Tabellennamen und die ID des Datensatzes, der geĂ€ndert oder eingefĂŒgt wurde.

FĂŒr alle Tabellen, die mit dem Client synchronisiert werden sollen, definieren wir einen Trigger, der nach der Änderung/Aktualisierung eines Datensatzes die angegebene Funktion aufruft, die auf der Folie unten angegeben ist.
Wie meldet sich die API fĂŒr diese Änderungen an?

Ein Fanout-Mechanismus wird erstellt – er sendet Nachrichten an die Clients. Er sammelt alle KanĂ€le der Clients und verteilt die Updates, die er ĂŒber diese KanĂ€le erhalten hat:

Mikhail Salosin. Golang Meetup. Nutzung von Go im Backend der Anwendung „Smotri+“.

Hier ist die Standardbibliothek pq, die sich mit der Datenbank verbindet und angibt, dass sie den Kanal (data_updates) hören möchte, ĂŒberprĂŒft, ob die Verbindung geöffnet ist und alles in Ordnung ist. Ich lasse die FehlerschutzprĂŒfung aus, um Platz zu sparen (keine ÜberprĂŒfung kann riskant sein).

Dann legen wir asynchron einen Ticker fest, der alle 15 Sekunden ein Ping sendet, und beginnen, den Kanal zu hören, auf den wir uns angemeldet haben. Wenn ein Ping eingeht, veröffentlichen wir diesen Ping. Wenn wir einen Datensatz erhalten, veröffentlichen wir diesen Datensatz an alle Abonnenten dieses Fanouts.

Wie funktioniert Fan-out?

Auf Russisch wird es als „Verteiler“ ĂŒbersetzt. Wir haben ein Objekt, das die Abonnenten registriert, die Updates erhalten möchten. Sobald ein Update fĂŒr dieses Objekt eintrifft, verteilt es dieses Update an alle vorhandenen Abonnenten. Ganz einfach:

Mikhail Salosin. Golang Meetup. Nutzung von Go im Backend der Anwendung „Smotri+“.

Wie das in Go Ń€Đ”Đ°Đ»ĐžĐ·ĐŸĐČĐ°ĐœĐŸ:

Mikhail Salosin. Golang Meetup. Nutzung von Go im Backend der Anwendung „Smotri+“.

Es gibt eine Struktur, die mit Hilfe von Mutexen synchronisiert wird. Sie hat ein Feld, das den Zustand der Verbindung des Fanouts zur Datenbank speichert, d. h. im Moment hört sie zu und erhĂ€lt Updates, sowie eine Liste aller vorhandenen KanĂ€le – eine Map, deren SchlĂŒssel der Kanal ist und struct als Werte (im Grunde wird es nicht genutzt).

Die beiden Methoden – Connected und Disconnected – ermöglichen es dem Fanout zu sagen, dass wir eine Verbindung zur Datenbank haben, dass sie hergestellt wurde, und dass die Verbindung zur Datenbank getrennt wurde. Im zweiten Fall mĂŒssen alle Clients trennen und ihnen mitteilen, dass sie nichts mehr hören können und sie sich neu verbinden sollten, da die Verbindung zu ihnen abgebrochen wurde.

Es gibt auch die Methode Subscribe, die einen Kanal zu den „Zuhörern“ hinzufĂŒgt:

Mikhail Salosin. Golang Meetup. Nutzung von Go im Backend der Anwendung „Smotri+“.

Es gibt die Methode Unsubscribe, die einen Kanal aus den Zuhörern entfernt, wenn sich der Client getrennt hat, sowie die Methode Publish, die es ermöglicht, eine Nachricht an alle Abonnenten zu senden.

Frage: – Was wird ĂŒber diesen Kanal ĂŒbertragen?

MS: – Es wird ein Modell ĂŒbertragen, das sich geĂ€ndert hat, oder ein Ping (im Grunde nur eine Zahl, Integer).

MS: – Man kann alles, jede Struktur ĂŒbermitteln, veröffentlichen – sie wird einfach in JSON umgewandelt und das war's.

MS: – Wir erhalten eine Benachrichtigung von „Postgres“ – sie enthĂ€lt den Tabellennamen und die ID. Über den Tabellennamen und die ID erhalten wir den benötigten Datensatz und senden diese Struktur zur Veröffentlichung.

Infrastruktur

Wie sieht das aus Sicht der Infrastruktur aus? Wir haben 7 physische Server: einer davon ist vollstĂ€ndig fĂŒr die Datenbank reserviert, auf den anderen sechs laufen virtuelle Maschinen. Es gibt 6 Kopien der API: jede virtuelle Maschine mit der API lĂ€uft auf einem separaten physischen Server – das sorgt fĂŒr ZuverlĂ€ssigkeit.

Mikhail Salosin. Golang Meetup. Nutzung von Go im Backend der Anwendung „Smotri+“.

Wir haben zwei Frontends, auf denen Keepalived installiert ist, um die VerfĂŒgbarkeit zu verbessern, damit im Notfall ein Frontend das andere ersetzen kann. Außerdem gibt es zwei Kopien des CMS.

Es gibt auch einen Statistik-Importer. Es gibt einen DB-Slave, von dem regelmĂ€ĂŸig Backups erstellt werden. Und es gibt Pigeon Pusher – die Anwendung, die Push-Benachrichtigungen an Kunden sendet, sowie infrastrukturelle Dinge: Zabbix, Graylog2 und Chef.

Diese Infrastruktur ist tatsĂ€chlich ĂŒberdimensioniert, da man 100.000 auch mit weniger Servern bedienen kann. Aber die Hardware war da – wir haben sie genutzt (uns wurde gesagt, dass wir das können – warum nicht).

Vorteile von Go

Nachdem wir an dieser Anwendung gearbeitet haben, wurden einige offensichtliche Vorteile von Go deutlich.

  • Tolle HTTP-Bibliothek. Damit kann man schon „out of the box“ viel erstellen.
  • Außerdem ermöglichen uns die KanĂ€le, den Mechanismus zum Versenden von Benachrichtigungen an Kunden sehr einfach zu implementieren.
  • Das großartige Tool Race Detector hat uns geholfen, mehrere kritische Bugs (Staging-Infrastruktur) zu beseitigen. Alles, was auf der Staging-Umgebung lĂ€uft – wurde gestartet und mit dem SchlĂŒssel Race kompiliert; und wir können dementsprechend in der Staging-Infrastruktur sehen, welche potenziellen Probleme wir haben.
  • Minimalismus und Einfachheit der Sprache.

Mikhail Salosin. Golang Meetup. Nutzung von Go im Backend der Anwendung „Smotri+“.

Wir suchen Entwickler! Wenn jemand Interesse hat – bitte.

Fragen

Frage aus dem Publikum (im Folgenden – F): – Ich habe das GefĂŒhl, dass Sie einen wichtigen Punkt bezĂŒglich Fan-out ĂŒbersehen haben. Verstehe ich das richtig, dass Sie sich blockieren, wenn der Kunde nicht lesen möchte, wenn Sie ihm eine Antwort senden?

MS: – Nein, wir blockieren uns nicht. Erstens liegt alles hinter nginx, das heißt, es gibt keine Probleme mit langsamen Clients. Zweitens hat der Client einen gepufferten Kanal – im Grunde genommen können wir dort bis zu hundert Updates ablegen... Wenn wir in den Kanal nicht schreiben können, entfernt er ihn. Wenn wir sehen, dass der Kanal blockiert ist, schließen wir einfach den Kanal und alles – der Client wird sich erneut verbinden, falls ein Problem auftritt. Daher gibt es hier grundsĂ€tzlich keine Blockierungen.

Frage: – Konnte man nicht direkt den Listen/Notify-Eintrag senden und nicht die Tabellenkennung?

MS: – Bei Listen/Notify gibt es eine BeschrĂ€nkung von 8000 Byte fĂŒr das Preload, das gesendet wird. GrundsĂ€tzlich könnte man senden, wenn wir es mit einer geringen Datenmenge zu tun hĂ€tten, aber ich glaube, dass es so [wie wir es tun] einfach zuverlĂ€ssiger ist. Die EinschrĂ€nkungen kommen vom PostgreSQL selbst.

Frage: – Bekommen die Clients Updates zu Spielen, die sie nicht interessieren?

MS: – Im Prinzip ja. Normalerweise gibt es 2-3 Spiele, die parallel laufen, und das ist auch ziemlich selten. Wenn ein Client etwas sieht, schaut er normalerweise auf das Spiel, das gerade lĂ€uft. Außerdem hat der Client eine lokale Datenbank, in die all diese Updates eingeliefert werden, und selbst ohne Internetverbindung kann der Client alle vergangenen Spiele ansehen, zu denen er Updates hat. Im Grunde synchronisieren wir unsere Datenbank auf dem Server mit der lokalen Datenbank des Clients, damit er auch offline arbeiten kann.

Frage: – Warum habt ihr eure eigene ORM entwickelt?

Alexei (einer der Entwickler von „Sotri+“): – Zu dem Zeitpunkt (das war vor einem Jahr) gab es weniger ORMs als jetzt, wo es ziemlich viele gibt. Von den meisten bestehenden ORMs gefĂ€llt mir am wenigsten, dass die meisten von ihnen mit leeren Schnittstellen arbeiten. Das heißt, die Methoden, die in diesen ORMs vorhanden sind, sind bereit, alles Mögliche zu akzeptieren: Strukturen, Zeiger auf Strukturen, Zahlen, etwas, das ĂŒberhaupt nichts damit zu tun hat


Unsere ORM generiert Strukturen auf Basis des Datenmodells. SelbststÀndig. Und deswegen sind alle Methoden spezifisch, verwenden keine Reflexion usw. Sie akzeptieren Strukturen und erwarten die Verwendung jener Strukturen, die ankommen.

Frage: – Wie viele Personen haben daran teilgenommen?

MS: – In der Anfangsphase waren zwei Personen beteiligt. Irgendwann im Juni haben wir begonnen, im August war der grĂ¶ĂŸte Teil fertig (die erste Version). Im September gab es die Veröffentlichung.

Frage: – Dort, wo Sie SSE beschreiben, verwenden Sie kein Timeout. Warum ist das so?

MS: – Um es klar zu sagen, SSE ist letztlich ein HTML5-Protokoll: Der Standard von SSE ist fĂŒr die Kommunikation mit Browsern gedacht, soweit ich verstehe. Es gibt zusĂ€tzliche Funktionen, damit sich die Browser wieder verbinden können (und Ă€hnliches), aber die brauchen wir nicht, da wir Kunden hatten, die jede Logik fĂŒr die Verbindung und den Informationsabruf implementieren konnten. Wir haben eher etwas gemacht, das SSE Ă€hnlich ist. Es ist nicht das Protokoll selbst.
Es gab keine Notwendigkeit. Soweit ich verstehe, haben die Kunden den Verbindungsmechanismus praktisch von Grund auf neu erstellt. Es war ihnen prinzipiell egal.

Frage: – Welche zusĂ€tzlichen Tools habt ihr verwendet?

MS: – Am aktivsten haben wir govet und golint verwendet, um einen einheitlichen Stil zu gewĂ€hrleisten, sowie gofmt. DarĂŒber hinaus nichts.

Frage: – Womit habt ihr debuggt?

MS: – Das Debuggen erfolgte im Wesentlichen mit Tests. Wir haben keinen Debugger, GOP verwendet.

Frage: – Könntest du die Folie zurĂŒckgeben, auf der die Publish-Funktion implementiert ist? Stören dich einbuchstabige Variablennamen?

MS: – Nein. Sie haben ein recht "enges" Sichtfeld. Sie werden nirgendwo anders verwendet, außer hier (außer den Innenleben dieser Klasse), und sie sind sehr kompakt – nehmen nur 7 Zeilen ein.

Frage: – Irgendwie ist das trotzdem nicht intuitiv...

MS: – Nein, nein, das ist echter Code! Es liegt nicht am Stil. Es ist einfach eine utilitaristische, sehr kleine Klasse – nur 3 Felder innerhalb der Klasse...

Mikhail Salosin. Golang Meetup. Nutzung von Go im Backend der Anwendung „Smotri+“.

MS: – Im Großen und Ganzen Ă€ndern sich die Daten, die mit den Kunden synchronisiert werden (saisonale Spiele, Spieler), nicht. Grob gesagt, wenn wir eine weitere Sportart machen, bei der das Spiel geĂ€ndert werden muss, werden wir einfach in der neuen Version des Clients alles berĂŒcksichtigen, und die alten Versionen des Clients werden gesperrt.

Frage: – Gibt es irgendwelche Drittanbieter-Pakete zur Verwaltung von AbhĂ€ngigkeiten?

MS: – Wir haben go dep verwendet.

Frage: – Im Vortrag gab es etwas ĂŒber Video, aber im Bericht gibt es kein Video.

MS: – Nein, ich habe im Thema zum Video nichts. Es heißt „Sotri+“ – so heißt die App.

Frage: – Ihr habt gesagt, dass ihr an die Kunden streamt?..

MS: – Mit Streaming-Video haben wir uns nicht beschĂ€ftigt. Das hat vollstĂ€ndig „Megafon“ gemacht. Ja, ich habe nicht gesagt, dass die App von Megafon ist.

MS: – Go – zum Versenden aller Daten – zu Konten, zu Spielereignissen, Statistiken
 Go – ist die komplette Backend-Lösung fĂŒr die Anwendung. Der Client muss irgendwo erfahren, welchen Link er fĂŒr den Player verwenden soll, damit der Benutzer das Spiel sehen kann. Wir haben Links zu Videos und Streams, die vorbereitet sind.

Video abspielen

Ein wenig Werbung 🙂

Danke, dass Sie bei uns bleiben. Gefallen Ihnen unsere Artikel? Möchten Sie mehr interessante Inhalte sehen? UnterstĂŒtzen Sie uns, indem Sie eine Bestellung aufgeben oder uns Freunden empfehlen, Cloud-VPS fĂŒr Entwickler ab 4,99 $, ein einzigartiges Äquivalent zu Einsteigerservern, das wir fĂŒr Sie entwickelt haben: Die ganze Wahrheit ĂŒber VPS (KVM) E5-2697 v3 (6 Kerne) 10GB DDR4 480GB SSD 1Gbps ab 19 $ oder wie man einen Server richtig teilt? (es sind Optionen mit RAID1 und RAID10 bis zu 24 Kernen und bis zu 40GB DDR4 verfĂŒgbar).

Dell R730xd ist im Equinix Tier IV Rechenzentrum in Amsterdam doppelt so gĂŒnstig? Nur bei uns 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TB ab $199 in den Niederlanden! Dell R420 — 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — ab $99! Lesen Sie, wie man eine Unternehmensinfrastruktur der Klasse C mit Dell R730xd E5-2650 v4-Servern fĂŒr 9000 Euro im Preis-Leistungs-VerhĂ€ltnis aufbaut?

Quelle: habr.com

60GB SSD 8Gb DDR4