Mikhail Salosin. Golang Meetup. Utilisation de Go dans le backend de l'application « Smotri+ »

Mikhail Salosin (ci-aprĂšs – MS) : – Bonjour Ă  tous ! Je m'appelle Mikhail. Je travaille en tant que dĂ©veloppeur backend chez MC2 Software et je vais vous parler de l'utilisation de Go dans le backend de l'application mobile « Smotri+ ».

Mikhail Salosin. Golang Meetup. Utilisation de Go dans le backend de l'application « Smotri+ »

Y a-t-il des amateurs de hockey parmi vous ?

Mikhail Salosin. Golang Meetup. Utilisation de Go dans le backend de l'application « Smotri+ »

Alors cette application est faite pour vous. Elle est disponible sur Android et iOS, et permet de regarder en direct ou en replay différentes retransmissions d'événements sportifs. L'application propose également diverses statistiques, des retransmissions textuelles, des tableaux de conférences, de tournois, et d'autres informations utiles pour les fans.

Mikhail Salosin. Golang Meetup. Utilisation de Go dans le backend de l'application « Smotri+ »

L'application inclut aussi une fonctionnalité appelée moments vidéo, c'est-à-dire que vous pouvez revoir les moments forts des matchs (buts, bagarres, tirs au but, etc.). Si vous ne souhaitez pas regarder l'intégralité de la retransmission, vous pouvez vous concentrer uniquement sur les moments intéressants.

Qu'avons-nous utilisé pour le développement ?

La majeure partie a Ă©tĂ© Ă©crite en Go. L'API utilisĂ©e par les clients mobiles a Ă©tĂ© dĂ©veloppĂ©e en Go. De plus, un service pour l'envoi de notifications push aux mobiles a Ă©galement Ă©tĂ© créé en Go. Nous avons Ă©galement dĂ» dĂ©velopper notre propre ORM, dont nous parlerons peut-ĂȘtre un jour. Quelques petits services, tels que le redimensionnement et le tĂ©lĂ©chargement d'images pour les Ă©diteurs, ont Ă©galement Ă©tĂ© rĂ©alisĂ©s en Go.

Comme base de données, nous avons utilisé PostgreSQL. L'interface pour les éditeurs a été réalisée en Ruby on Rails avec le gem ActiveAdmin. L'importation des statistiques du fournisseur de statistiques a également été écrite en Ruby.

Pour les tests systĂšme de l'API, nous avons utilisĂ© unittest de Python. Memcached est utilisĂ© pour limiter les requĂȘtes Ă  l'API de paiement, Chef pour la gestion de la configuration, Zabbix pour la collecte et la surveillance des statistiques internes du systĂšme. Graylog2 est utilisĂ© pour la collecte des logs, et Slate pour la documentation de l'API Ă  destination des clients.

Mikhail Salosin. Golang Meetup. Utilisation de Go dans le backend de l'application « Smotri+ »

Choix du protocole

Le premier problÚme auquel nous avons été confrontés : nous devions choisir un protocole d'interaction entre le backend et les clients mobiles, en tenant compte des éléments suivants...

  • La principale exigence : les donnĂ©es des clients doivent ĂȘtre mises Ă  jour en temps rĂ©el. Cela signifie que tous ceux qui regardent actuellement la retransmission doivent recevoir les mises Ă  jour presque instantanĂ©ment.
  • Pour simplifier, nous avons supposĂ© que les donnĂ©es synchronisĂ©es avec les clients ne sont pas supprimĂ©es, mais masquĂ©es Ă  l'aide de drapeaux spĂ©ciaux.
  • Diverses requĂȘtes rares (comme des statistiques, des compositions d'Ă©quipes, des statistiques d'Ă©quipes) sont effectuĂ©es par des requĂȘtes GET classiques.
  • De plus, le systĂšme devait supporter tranquillement 100 000 utilisateurs simultanĂ©ment.

Nous avions donc deux options de protocole :

  1. Websockets. Mais nous n'avions pas besoin de canaux du client vers le serveur. Nous avions seulement besoin d'envoyer des mises Ă  jour du serveur au client, donc le websocket est une option redondante.
  2. Les événements envoyés par le serveur (Server-Sent Events, SSE) conviennent parfaitement ! C'est assez simple et répond en principe à tout ce dont nous avons besoin.

ÉvĂ©nements envoyĂ©s par le serveur

Quelques mots sur le fonctionnement de ce systĂšme...

Il fonctionne par-dessus une connexion HTTP. Le client envoie une requĂȘte, Ă  laquelle le serveur rĂ©pond avec un Content-Type : text/event-stream et ne ferme pas la connexion avec le client, mais continue d'Ă©crire des donnĂ©es dans la connexion :

Mikhail Salosin. Golang Meetup. Utilisation de Go dans le backend de l'application « Smotri+ »

Les donnĂ©es peuvent ĂȘtre envoyĂ©es dans un format convenu avec les clients. Dans notre cas, nous envoyions sous cette forme : dans le champ event, le nom de la structure modifiĂ©e (personne, joueur) Ă©tait envoyĂ©, et dans le champ data – un JSON avec les nouveaux champs modifiĂ©s pour le joueur.

Maintenant, parlons de la maniĂšre dont l'interaction fonctionne.

  • Tout d'abord, le client dĂ©termine la derniĂšre fois qu'il a synchronisĂ© avec le service : il consulte sa base de donnĂ©es locale et dĂ©termine la date de la derniĂšre modification qu'il a enregistrĂ©e.
  • Il envoie une requĂȘte avec cette date.
  • En rĂ©ponse, nous lui envoyons toutes les mises Ă  jour qui ont eu lieu depuis cette date.
  • AprĂšs cela, il Ă©tablit une connexion au canal en direct et ne le ferme pas tant qu'il a besoin de ces mises Ă  jour :

Mikhail Salosin. Golang Meetup. Utilisation de Go dans le backend de l'application « Smotri+ »

Nous lui envoyons une liste de changements : si quelqu'un marque un but – le score du match est modifiĂ©, s'il se blesse – cela est Ă©galement envoyĂ© en temps rĂ©el. Ainsi, dans le fil d'Ă©vĂ©nements du match, les clients reçoivent instantanĂ©ment des donnĂ©es Ă  jour. PĂ©riodiquement, pour que le client comprenne que le serveur n'est pas hors ligne, qu'il ne s'est rien passĂ©, nous envoyons un timestamp toutes les 15 secondes – pour qu'il sache que tout va bien et qu'il n'est pas nĂ©cessaire de se reconnecter.

Comment la connexion en direct est-elle maintenue ?

  • Tout d'abord, nous crĂ©ons un canal sur lequel les mises Ă  jour arriveront avec un tampon.
  • AprĂšs cela, nous nous abonnissons Ă  ce canal pour recevoir des mises Ă  jour.
  • Nous dĂ©finissons l'en-tĂȘte appropriĂ©, afin que le client sache que tout va bien.
  • Nous envoyons le premier ping. Nous enregistrons simplement le timestamp actuel de la connexion.
  • AprĂšs cela, nous lisons en boucle Ă  partir du canal tant que le canal de mises Ă  jour n'est pas fermĂ©. Le canal reçoit pĂ©riodiquement soit l'horodatage actuel, soit des modifications que nous enregistrons dans les connexions ouvertes.

Mikhail Salosin. Golang Meetup. Utilisation de Go dans le backend de l'application « Smotri+ »

Le premier problĂšme auquel nous avons Ă©tĂ© confrontĂ©s Ă©tait le suivant : pour chaque connexion ouverte avec le client, nous crĂ©ions un minuteur qui s'activait toutes les 15 secondes – donc, si nous avions 6 000 connexions ouvertes sur une machine (avec un seul serveur API), cela crĂ©ait 6 000 minuteurs. Cela faisait que la machine ne supportait pas la charge nĂ©cessaire. Le problĂšme n'Ă©tait pas aussi Ă©vident pour nous, mais nous avons Ă©tĂ© un peu aidĂ©s et l'avons rĂ©solu.

En fin de compte, le ping provient dĂ©sormais du mĂȘme canal que celui des mises Ă  jour.

Par conséquent, il n'y a qu'un seul minuteur qui s'active toutes les 15 secondes.

Ici, plusieurs fonctions auxiliaires – envoi d'en-tĂȘte, de ping et de la structure elle-mĂȘme. Cela signifie qu'il passe le nom de la table (person, match, saison) et les informations relatives Ă  cet enregistrement :

Mikhail Salosin. Golang Meetup. Utilisation de Go dans le backend de l'application « Smotri+ »

Le mécanisme d'envoi des mises à jour

Un peu sur la provenance des modifications. Nous avons plusieurs personnes, des rédacteurs, qui regardent la diffusion en temps réel. Ils créent tous les événements : quelqu'un a été expulsé, quelqu'un s'est blessé, un remplacement


Avec l'aide de la CMS, les données entrent dans la base. AprÚs cela, la base utilise le mécanisme Listen/Notify pour informer les serveurs API. Les serveurs API distribuent ensuite cette information aux clients. Ainsi, nous avons en fait seulement quelques serveurs connectés à la base et aucune charge particuliÚre sur la base, car le client n'interagit en aucune façon directement avec celle-ci :

Mikhail Salosin. Golang Meetup. Utilisation de Go dans le backend de l'application « Smotri+ »

PostgreSQL : Listen/Notify

Le mĂ©canisme Listen/Notify dans PostgreSQL permet d'informer les abonnĂ©s des Ă©vĂ©nements qu'un certain Ă©vĂ©nement a Ă©tĂ© modifiĂ© – qu'un enregistrement a Ă©tĂ© créé dans la base. Pour cela, nous avons Ă©crit un simple dĂ©clencheur et une fonction :

Mikhail Salosin. Golang Meetup. Utilisation de Go dans le backend de l'application « Smotri+ »

Lors de l'insertion ou de la modification d'un enregistrement, nous invoquons la fonction notify sur le canal data_updates, en passant le nom de la table et l'identifiant de l'enregistrement qui a été modifié ou inséré.

Pour toutes les tables qui doivent ĂȘtre synchronisĂ©es avec le client, nous dĂ©finissons un dĂ©clencheur qui, aprĂšs la modification/mise Ă  jour de l'enregistrement, appelle la fonction indiquĂ©e sur la diapositive ci-dessous.
Comment l'API s'abonne-t-elle Ă  ces modifications ?

Un mĂ©canisme Fanout est créé – il envoie des messages aux clients. Il regroupe tous les canaux des clients et diffuse les mises Ă  jour qu'il a reçues par ces canaux :

Mikhail Salosin. Golang Meetup. Utilisation de Go dans le backend de l'application « Smotri+ »

Voici la bibliothĂšque standard pq, qui se connecte Ă  la base de donnĂ©es et indique qu'elle souhaite Ă©couter le canal (data_updates), vĂ©rifie que la connexion est ouverte et tout est normal. Je passe la vĂ©rification des erreurs pour gagner de la place (ne pas vĂ©rifier peut ĂȘtre risquĂ©).

Ensuite, nous définissons de maniÚre asynchrone un Ticker, qui enverra un ping toutes les 15 secondes, et nous commençons à écouter le canal auquel nous sommes abonnés. Si nous recevons un ping, nous publions ce ping. Si nous recevons un enregistrement quelconque, nous publions cet enregistrement à tous les abonnés de ce Fanout.

Comment fonctionne le Fan-out ?

En français, cela se traduit par « répartiteur ». Nous avons un objet qui enregistre les abonnés qui souhaitent recevoir des mises à jour. Et dÚs qu'une mise à jour arrive à cet objet, il distribue cette mise à jour à tous les abonnés qu'il a. C'est assez simple :

Mikhail Salosin. Golang Meetup. Utilisation de Go dans le backend de l'application « Smotri+ »

Comment cela est implémenté en Go :

Mikhail Salosin. Golang Meetup. Utilisation de Go dans le backend de l'application « Smotri+ »

Il y a une structure, qui est synchronisĂ©e Ă  l'aide de Mutex. Elle a un champ qui conserve l'Ă©tat de la connexion Fanout Ă  la base, c'est-Ă -dire qu'Ă  ce moment, elle Ă©coute et recevra des mises Ă  jour, ainsi qu'une liste de tous les canaux disponibles – une map, dont la clĂ© est le canal et une struct sous forme de valeurs (qui, en rĂ©alitĂ©, n'est pas utilisĂ©e).

Deux mĂ©thodes – Connected et Disconnected – permettent d'indiquer au Fanout que nous avons une connexion avec la base, qu'elle est Ă©tablie et que la connexion Ă  la base a Ă©tĂ© rompue. Dans ce dernier cas, il faut dĂ©connecter tous les clients et leur faire savoir qu'ils ne peuvent plus Ă©couter et qu'ils doivent se reconnecter, car la connexion avec eux est fermĂ©e.

Il existe également une méthode Subscribe, qui ajoute un canal aux « écouteurs » :

Mikhail Salosin. Golang Meetup. Utilisation de Go dans le backend de l'application « Smotri+ »

Il y a une méthode Unsubscribe, qui retire le canal des écouteurs, si le client s'est déconnecté, ainsi qu'une méthode Publish, permettant d'envoyer un message à tous les abonnés.

Question: – Qu'est-ce qui est transmis par ce canal ?

MS : – Un modĂšle qui a changĂ© ou un ping (essentiellement juste un nombre, un entier).

MS : – On peut transmettre n'importe quoi, n'importe quelle structure, la publier – elle est simplement convertie en JSON et c'est tout.

MS : Nous recevons une notification de « Postgres » - elle contient le nom de la table et l'identifiant. Avec le nom de la table, nous obtenons l'enregistrement dont nous avons besoin à partir de l'identifiant, puis nous envoyons cette structure pour publication.

Infrastructure

Comment cela se présente-t-il du point de vue de l'infrastructure ? Nous avons 7 serveurs physiques : l'un d'eux est entiÚrement dédié à la base de données, tandis que les six autres exécutent des machines virtuelles. Nous avons 6 copies de l'API : chaque machine virtuelle avec l'API fonctionne sur un serveur physique séparé - c'est pour la fiabilité.

Mikhail Salosin. Golang Meetup. Utilisation de Go dans le backend de l'application « Smotri+ »

Nous avons deux frontaux, dotés de Keepalived pour améliorer la disponibilité, afin qu'en cas de problÚme, un frontal puisse remplacer l'autre. De plus, nous avons deux copies du CMS.

Il y a aussi un importateur de statistiques. Il existe une base de donnĂ©es esclave, d'oĂč des sauvegardes sont pĂ©riodiquement effectuĂ©es. Il y a Pigeon Pusher - l'application qui envoie des notifications push aux clients, ainsi que des Ă©lĂ©ments d'infrastructure : Zabbix, Graylog2 et Chef.

En rĂ©alitĂ©, cette infrastructure est surdimensionnĂ©e, car 100 000 utilisateurs peuvent ĂȘtre servis avec moins de serveurs. Mais comme nous avions le matĂ©riel - nous l'avons utilisĂ© (on nous a dit que c'Ă©tait possible - alors pourquoi pas).

Les avantages de Go

AprÚs avoir travaillé sur cette application, certains avantages évidents de Go se sont révélés.

  • Une excellente bibliothĂšque HTTP. GrĂące Ă  elle, on peut crĂ©er beaucoup de choses "prĂȘtes Ă  l'emploi".
  • En plus, des canaux qui nous ont permis de rĂ©aliser trĂšs facilement le mĂ©canisme d'envoi de notifications aux clients.
  • Le dĂ©tecteur de concurrence est une fonctionnalitĂ© remarquable qui nous a permis de corriger plusieurs bugs critiques (infrastructure de staging). Tout ce qui fonctionne sur le staging est lancĂ©, compilĂ© avec l'option Race ; et nous, en consĂ©quence, pouvons vĂ©rifier sur l'infrastructure de staging les problĂšmes potentiels que nous pourrions avoir.
  • Minimalisme et simplicitĂ© du langage.

Mikhail Salosin. Golang Meetup. Utilisation de Go dans le backend de l'application « Smotri+ »

Nous recherchons des développeurs ! Si quelqu'un est intéressé, n'hésitez pas.

Questions

Question de l'auditoire (Q) : - J'ai l'impression que vous avez omis un point important concernant le Fan-out. Est-ce que je comprends bien que lorsque vous envoyez une réponse au client, vous vous bloquez si le client ne souhaite pas lire ?

MS : – Non, nous ne sommes pas bloquĂ©s. Tout d'abord, tout cela est derriĂšre nginx, donc il n'y a aucun problĂšme avec les clients lents. DeuxiĂšmement, le client dispose d'un canal avec un tampon – en gros, nous pouvons y mettre jusqu'Ă  cent mises Ă  jour... Si nous ne pouvons pas Ă©crire dans le canal, il l'efface. Si nous voyons que le canal est bloquĂ©, nous fermons simplement le canal, et c'est tout – le client se reconnectera s'il y a un problĂšme. Donc, il n'y a en principe pas de blocage ici.

Q : – N'aurait-il pas Ă©tĂ© possible d'envoyer immĂ©diatement l'enregistrement dans Listen/Notify plutĂŽt que dans une table-identifiant ?

MS : – Listen/Notify a une limite de 8000 octets sur le preload qu'il envoie. En principe, cela aurait pu ĂȘtre envoyĂ© si nous avions affaire Ă  un petit volume de donnĂ©es, mais je pense que [comme nous le faisons] c'est simplement plus fiable. Les limitations viennent du « PostgreSQL » lui-mĂȘme.

Q : – Les clients reçoivent-ils des mises Ă  jour sur les matchs qui ne les intĂ©ressent pas ?

MS : – En gĂ©nĂ©ral, oui. En rĂšgle gĂ©nĂ©rale, il y a 2-3 matchs en parallĂšle, et c'est assez rare. Si un client regarde quelque chose, c'est gĂ©nĂ©ralement le match en cours. De plus, le client dispose d'une base de donnĂ©es locale oĂč toutes ces mises Ă  jour sont stockĂ©es, et mĂȘme sans connexion Internet, le client peut consulter tous les matchs prĂ©cĂ©dents pour lesquels il a des mises Ă  jour. En gros, nous synchronisons notre base de donnĂ©es sur le serveur avec la base de donnĂ©es locale du client, afin qu'il puisse travailler en mode hors ligne.

Q : – Pourquoi avez-vous créé votre propre ORM ?

Alexey (un des dĂ©veloppeurs de « Smotri+ »): – À l'Ă©poque (c'Ă©tait il y a un an), il y avait moins d'ORM qu'aujourd'hui, oĂč il y en a beaucoup. Parmi la majoritĂ© des ORM existants, je trouve que ce que je n'aime pas le plus, c'est que beaucoup d'entre elles fonctionnent sur des interfaces vides. Cela signifie que les mĂ©thodes de ces ORM sont prĂȘtes Ă  accepter n'importe quoi : une structure, un pointeur de structure, un nombre, quelque chose de complĂštement hors de propos...

Notre ORM génÚre des structures sur la base du modÚle de données. Tout seul. Et donc, toutes les méthodes sont concrÚtes, n'utilisent pas la réflexion, etc. Elles acceptent des structures et s'attendent à utiliser les structures qui arrivent.

Q : – Combien de personnes ont participĂ© ?

MS : – Au dĂ©but, deux personnes ont participĂ©. Nous avons commencĂ© vers juin, en aoĂ»t la majeure partie Ă©tait prĂȘte (premiĂšre version). En septembre, il y a eu la sortie.

Q : – LĂ  oĂč vous dĂ©crivez SSE, vous n'utilisez pas de timeout. Pourquoi cela ?

MS : Si l'on parle franchement, le SSE est tout de mĂȘme un protocole html5 : la norme SSE est conçue pour communiquer avec les navigateurs, autant que je sache. Il a des fonctionnalitĂ©s supplĂ©mentaires pour que les navigateurs puissent se reconnecter (et autres), mais elles ne nous sont pas nĂ©cessaires, car nous avions des clients capables de rĂ©aliser n'importe quelle logique de connexion et d'obtention d'informations. Nous avons plutĂŽt créé quelque chose de semblable au SSE, mais ce n'est pas le protocole lui-mĂȘme.
Il n'y avait pas de nĂ©cessitĂ©. Autant que je sache, les clients ont mis en Ɠuvre le mĂ©canisme de connexion pratiquement Ă  partir de zĂ©ro. En gros, cela ne leur importait pas.

Q : Quelles utilitaires supplémentaires avez-vous utilisés ?

MS : Nous avons principalement utilisé govet et golint pour assurer une cohérence de style, ainsi que gofmt. Nous n'avons rien utilisé de plus.

Q : Comment avez-vous effectué le débogage ?

MS : Pour ĂȘtre franc, le dĂ©bogage se faisait principalement Ă  l'aide de tests. Nous n'avons utilisĂ© aucun dĂ©bogueur, nous n'avons pas utilisĂ© GOP.

Q : Pouvez-vous revenir Ă  la diapositive oĂč la fonction Publish est implĂ©mentĂ©e ? Les noms de variables Ă  une lettre ne vous dĂ©rangent pas ?

MS : Non. Leur portĂ©e est suffisamment « Ă©troite ». Elles ne sont utilisĂ©es nulle part ailleurs, sauf ici (Ă  part Ă  l'intĂ©rieur de cette classe), et c'est un code trĂšs compact – seulement 7 lignes.

Q : Tout ceci n'est pas trĂšs intuitif...

MS : Non-non, c'est du vrai code ! Ce n'est pas une question de style. C'est juste une petite classe utilitaire – seulement 3 champs Ă  l'intĂ©rieur de la classe...

Mikhail Salosin. Golang Meetup. Utilisation de Go dans le backend de l'application « Smotri+ »

MS : Dans l'ensemble, toutes ces donnĂ©es synchronisĂ©es avec les clients (matchs saisonniers, joueurs) ne changent pas. En gros, si nous crĂ©ons un autre type de sport oĂč il faudra modifier un match, nous prendrons simplement en compte cela dans une nouvelle version du client, et les anciennes versions seront bloquĂ©es.

Q : Y a-t-il des packages tiers pour la gestion des dépendances ?

MS : Nous avons utilisé go dep.

Q : Dans le sujet de la présentation, il y avait quelque chose sur la vidéo, mais il n'y a rien sur la vidéo dans la présentation.

MS : Non, je n'ai rien sur la vidĂ©o dans ma prĂ©sentation. Ça s'appelle « Savy+ » – c'est ainsi que se nomme l'application.

Q : Vous avez dit que ça se diffuse sur les clients ?

MS : Nous ne traitions pas de la vidéo en streaming. Cela était entiÚrement géré par « MegaFon ». Oui, je n'ai pas mentionné que c'est une application de MegaFon.

MS : – Go – pour l'envoi de toutes les donnĂ©es – concernant le compte, les Ă©vĂ©nements du match, les statistiques
 Go – c'est entiĂšrement le backend de l'application. Le client doit savoir d'oĂč obtenir le lien Ă  utiliser pour le lecteur, afin que l'utilisateur puisse regarder le match. Nous avons des liens vers des vidĂ©os et des streams qui sont prĂȘts.

Lire la vidéo

Un peu de publicitĂ© 🙂

Merci de rester avec nous. Aimez-vous nos articles ? Voulez-vous voir plus de contenu intĂ©ressant ? Soutenez-nous en passants une commande ou en nous recommandant Ă  des amis, VPS cloud pour dĂ©veloppeurs Ă  partir de 4,99 $, un Ă©quivalent unique des serveurs d'entrĂ©e de gamme, conçu pour vous : Toute la vĂ©ritĂ© sur le VPS (KVM) E5-2697 v3 (6 cƓurs) 10 Go DDR4 480 Go SSD 1 Gbps Ă  partir de 19 $ ou comment bien diviser un serveur ? (options disponibles avec RAID1 et RAID10, jusqu'Ă  24 cƓurs et jusqu'Ă  40 Go DDR4).

Dell R730xd deux fois moins cher dans le data center Equinix Tier IV Ă  Amsterdam ? Uniquement chez nous 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64Go DDR4 4x960Go SSD 1Gbps 100 To Ă  partir de 199 $ aux Pays-Bas ! Dell R420 — 2x E5-2430 2.2GHz 6C 128Go DDR3 2x960Go SSD 1Gbps 100To — Ă  partir de 99 $ ! Lisez sur Comment construire une infrastructure de classe entreprise avec des serveurs Dell R730xd E5-2650 v4 coĂ»tant 9000 euros pour des clopinettes ?

Source : habr.com

Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS đŸ”„ Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster