Mikhail Salosin. Meetup Golang. Utiliser Go dans le backend de l'application Watch +

Mikhail Salosin (ci-après - MS): - Bonjour à tous! Mon nom est Michael. Je travaille en tant que développeur backend chez MC2 Software, et je parlerai de l'utilisation de Go dans le backend de l'application mobile Watch +.



Est-ce que quelqu'un présente comme le hockey?



Ensuite, cette application est faite pour vous. C'est pour Android et iOS, utilisé pour regarder les émissions de divers événements sportifs en ligne et dans des enregistrements. Dans l'application, il existe également diverses statistiques, diffusions de texte, tableaux de conférences, tournois et autres informations utiles pour les fans.



Dans l'application, il y a aussi des moments vidéo, c'est-à-dire que vous pouvez voir les moments aigus des matchs (buts, combats, fusillades, etc.). Si vous ne voulez pas regarder la totalité de l'émission, vous ne pouvez regarder que les plus intéressantes.

Qu'est-ce qui a été utilisé dans le développement?


Le corps principal a été écrit en Go. L'API avec laquelle les clients mobiles ont communiqué a été écrite en Go. En outre, un service a été écrit sur Go pour envoyer des notifications push au mobile. Nous avons également dû écrire notre propre ORM, dont nous pourrions un jour parler. Et bien, quelques petits services sont écrits sur Go: redimensionnement et chargement d'images pour les éditeurs ...

Nous avons utilisé Postgres (PostgreSQL) comme base de données. L'interface pour les éditeurs a été écrite en Ruby on Rails à l'aide de la gemme ActiveAdmin. L'importation de statistiques à partir du fournisseur de statistiques est également écrite sur Ruby.

Pour les tests de l'API système, nous avons utilisé le Python unittest (Python). Memcached est utilisé pour limiter les demandes de paiement API, Chef pour contrôler la configuration, Zabbix pour collecter et surveiller les statistiques internes du système. Graylog2 - pour la collecte des journaux, Slate est une documentation API pour les clients.



Sélection du protocole


Le premier problème que nous avons rencontré: nous avons dû choisir un protocole d'interaction du backend avec les clients mobiles, basé sur les points suivants ...

  • L'exigence la plus importante: les données sur les clients doivent être mises à jour en temps réel. Autrement dit, tous ceux qui regardent actuellement la diffusion devraient recevoir des mises à jour presque instantanément.
  • Par souci de simplicité, nous avons accepté que les données synchronisées avec les clients ne soient pas supprimées, mais masquées à l'aide d'indicateurs spéciaux.
  • Toutes sortes de demandes rares (comme les statistiques, les files d'attente, les statistiques d'équipe) sont reçues par les demandes GET ordinaires.
  • De plus, le système était censé résister calmement à 100 000 utilisateurs en même temps.

Sur cette base, nous avions 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 un socket web est une option redondante.
  2. Les événements envoyés par le serveur (SSE) se sont parfaitement déroulés! C'est assez simple et satisfait fondamentalement tout ce dont nous avons besoin.

Événements envoyés par le serveur


Quelques mots sur le fonctionnement de cette chose ...

Elle fonctionne en plus de la connexion http. Le client envoie une demande, le serveur répond avec Content-Type: texte / flux d'événements et ne ferme pas la connexion avec le client, mais continue d'écrire des données sur la connexion: les



données peuvent être envoyées dans un format convenu avec les clients. Dans notre cas, nous avons envoyé ce formulaire: dans le champ d'événement, le nom de la structure modifiée (personne, joueur) a été envoyé, et dans le champ de données - JSON avec de nouveaux champs modifiés pour le joueur.

Maintenant, comment fonctionne l'interaction elle-même.
  • Tout d'abord, le client détermine quand la dernière synchronisation a été effectuée avec le service: il regarde sa base de données locale et détermine la date de la dernière modification enregistrée à partir de celle-ci.
  • Il envoie une demande avec cette date.
  • En réponse, nous lui envoyons toutes les mises à jour intervenues à cette date.
  • Après cela, il se connecte à la chaîne en direct et ne ferme pas tant qu'il n'a pas besoin de ces mises à jour:



Nous lui envoyons une liste de changements: si quelqu'un marque un but - nous changeons le score du match, se blesse - également envoyé en temps réel. Ainsi, dans le flux d'événements du match, les clients reçoivent instantanément les données pertinentes. Périodiquement, pour que le client comprenne que le serveur n'est pas mort, que rien ne lui est arrivé, nous envoyons un horodatage toutes les 15 secondes - pour qu'il sache que tout est en ordre et qu'il n'est pas nécessaire de se reconnecter.

Comment la connexion en direct est-elle desservie?


  • Tout d'abord, nous créons un canal dans lequel viendront les mises à jour avec un tampon.
  • Après cela, nous nous abonnons à cette chaîne pour recevoir des mises à jour.
  • Définissez l'en-tête correct pour que le client sache que tout va bien.
  • ping. timestamp .
  • , . timestamp, , .



Le premier problème que nous avons rencontré était le suivant: pour chaque connexion ouverte avec le client, nous avons créé une minuterie qui a coché une fois toutes les 15 secondes - il s'avère que nous avions 6 000 connexions avec une machine (avec un serveur API), 6 000 minuteries ont été créées. Cela a conduit au fait que la machine ne supportait pas la charge nécessaire. Le problème n'était pas si évident pour nous, mais ils nous ont un peu aidés et nous l'avons éliminé.

Par conséquent, nous avons maintenant un ping provenant du même canal d'où provient la mise à jour.

En conséquence, il n'y a qu'une seule minuterie qui émet une fois toutes les 15 secondes.

Voici quelques fonctions d'assistance - envoi de l'en-tête, du ping et de la structure elle-même. Autrement dit, le nom de la table (personne, match, saison) et les informations sur cet enregistrement sont transmis ici:



Mécanisme d'envoi des mises à jour


Maintenant, un peu sur la provenance des changements. Nous avons plusieurs personnes, éditeurs, qui regardent l'émission en temps réel. Ils créent tous les événements: quelqu'un a été supprimé, quelqu'un a été blessé, une sorte de remplacement ...

Avec l'aide de CMS, les données sont entrées dans la base de données. Après cela, la base de données utilisant le mécanisme Listen / Notify en informe les serveurs API. Les serveurs API envoient déjà ces informations aux clients. Ainsi, en fait, nous n'avons que quelques serveurs connectés à la base de données et il n'y a pas de charge spéciale sur la base de données, car le client n'interagit pas directement avec la base de données de quelque manière que ce soit:



PostgreSQL: écouter / notifier


Le mécanisme Listen / Notify de Postgres vous permet d'informer les abonnés des événements que certains événements ont changé - une sorte d'enregistrement a été créé dans la base de données. Pour ce faire, nous avons écrit un déclencheur et une fonction simples:



lors de l'insertion ou de la modification d'un enregistrement, nous appelons la fonction de notification sur le canal data_updates, transférons le nom de la table et l'identifiant de l'enregistrement qui a été modifié ou inséré là.

Pour toutes les tables qui doivent être synchronisées avec le client, nous définissons un déclencheur qui, après avoir modifié / mis à jour l'enregistrement, appelle la fonction indiquée sur la diapositive ci-dessous.
Comment l'API souscrit-elle à ces modifications?

Le mécanisme Fanout est créé - il envoie des messages au client. Il recueille tous les canaux clients et envoie les mises à jour qu'il a reçues via ces canaux:



Ici, la bibliothèque pq standard, qui se connecte à la base de données et dit qu'elle veut écouter le canal (data_updates), vérifie que la connexion est ouverte et que tout va bien. J'omet la vérification des erreurs pour économiser de l'espace (ne pas vérifier lourd).

Ensuite, nous définissons le Ticker de manière asynchrone, qui enverra un ping toutes les 15 secondes, et commencera à écouter la chaîne à laquelle nous nous sommes abonnés. Si nous avons un ping, nous publions ce ping. Si nous avons reçu un enregistrement, nous publions cet enregistrement à tous les abonnés de ce Fanout.

Comment fonctionne le fan-out?


En russe, cela se traduit par un «séparateur». Nous avons un objet qui enregistre les abonnés qui souhaitent recevoir des mises à jour. Et dès qu'une mise à jour de cet objet arrive, il propage cette mise à jour à tous les abonnés qu'il possède. Assez simple:



comment il est implémenté sur Go:



il y a une structure, il est synchronisé à l'aide de Mutex. Il a un champ qui enregistre l'état de la connexion Fanout à la base de données, c'est-à-dire au moment où il écoute et recevra les mises à jour, ainsi qu'une liste de tous les canaux disponibles - carte, dont la clé est le canal et la structure sous forme de valeurs (en fait, il utilisé en aucune façon).

Deux méthodes - Connecté et Déconnecté - vous permettent de dire à Fanout que nous avons une connexion à la base, il est apparu et que la connexion à la base est déconnectée. Dans le second cas, vous devez déconnecter tous les clients et leur dire qu'ils ne peuvent plus rien écouter et qu'ils se reconnectent, car la connexion avec eux est fermée.

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



il existe une méthode Unsubscribe qui supprime un canal des écouteurs si le client se déconnecte, ainsi qu'une méthode Publish qui vous permet d'envoyer un message à tous les abonnés.

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

MS: - Un modèle est transmis qui a changé ou ping (essentiellement juste un nombre, un entier).

SP:- Vous pouvez envoyer n'importe quoi, publier n'importe quelle structure, cela se transforme simplement en JSON et c'est tout.

MS: - Nous recevons une notification de Postgres - il contient le nom et l'identifiant de la table. Par le nom de la table que nous obtenons et l'identifiant nous obtenons l'enregistrement dont nous avons besoin, et déjà cette structure est envoyée pour publication.

Infrastructure


À quoi cela ressemble-t-il en termes d'infrastructure? Nous avons 7 serveurs de fer: l'un d'eux est entièrement dédié à la base, des ordinateurs virtuels tournent sur les six autres. Il y a 6 copies de l'API: chaque machine virtuelle avec l'API s'exécute sur un serveur de fer distinct - c'est pour la fiabilité.



Nous avons deux frontaux sur lesquels Keepalived est installé pour améliorer l'accessibilité, de sorte que dans le cas où un frontend peut remplacer l'autre. Deux autres copies du CMS.

Il existe également un importateur de statistiques. Il existe un esclave DB à partir duquel des sauvegardes sont effectuées périodiquement. Il y a Pigeon Pusher - l'application qui envoie des pushies aux clients, ainsi que des éléments d'infrastructure: Zabbix, Graylog2 et Chef.

En fait, cette infrastructure est redondante, car 100 000 peuvent être servis avec moins de serveurs. Mais il y avait du fer - nous l'avons utilisé (on nous a dit que c'était possible - pourquoi pas).

Avantages de Go


Après avoir travaillé sur cette application, ces avantages évidents de Go ont été révélés.
  • Cool bibliothèque http. En l'utilisant, vous pouvez en créer déjà beaucoup hors de la boîte.
  • De plus, les canaux qui nous ont permis de mettre en œuvre très facilement le mécanisme d'envoi de notifications aux clients.
  • Le merveilleux détecteur Race nous a permis d'éliminer plusieurs bugs critiques (staging-infrastructure). Tout ce qui fonctionne sur la mise en scène est en cours d'exécution, compilé avec la touche Race; et, par conséquent, nous pouvons voir quels problèmes potentiels nous avons sur l'infrastructure de transit.
  • Minimalisme et simplicité du langage.




Nous recherchons des développeurs! Si quelqu'un veut - s'il vous plaît.

Des questions


Question du public (ci-après - B): - Il me semble que vous avez manqué un point important concernant le Fan-out. Je comprends bien que lorsque vous envoyez une réponse à un client, vous êtes bloqué si le client ne veut pas lire?

MS: - Non, nous ne bloquons pas. Premièrement, nous avons tout derrière nginx, c'est-à-dire qu'il n'y a aucun problème avec les clients lents. Deuxièmement, le client a un canal avec un tampon - en fait, nous pouvons y mettre jusqu'à cent mises à jour ... Si nous ne pouvons pas écrire sur le canal, il le supprime. Si nous voyons que le canal est bloqué, alors nous fermons simplement le canal, et c'est tout - le client se reconnectera en cas de problème. Par conséquent, en principe, le blocage ne se produit pas ici.

Q: - Pourriez-vous envoyer immédiatement à Listen / Notify un enregistrement, pas une table d'identification?

SP:- Listen / Notify a une limite de 8 000 octets par précharge, qu'il envoie. En principe, il serait possible d'envoyer si nous avions affaire à une petite quantité de données, mais il me semble que la manière [comme nous le faisons] est simplement plus fiable. Les limitations sont dans Postgres même.

Q: - Les clients reçoivent-ils des mises à jour sur les correspondances qui ne les intéressent pas?

SP:- En général, oui. En règle générale, il y a 2-3 matchs en parallèle, puis très rarement. Si le client regarde quelque chose, il regarde généralement le match en cours. Ensuite, sur le client, il existe une base de données locale dans laquelle toutes ces mises à jour s'additionnent, et même sans connexion Internet, le client peut voir toutes les correspondances passées pour lesquelles il a des mises à jour. En fait, nous synchronisons notre base de données sur le serveur avec la base de données locale du client afin qu'elle puisse fonctionner hors ligne.

Q: - Pourquoi avez-vous fait votre ORM?

Alexey (l'un des développeurs de «Watch +»):- A cette époque (c'était il y a un an) ORM était moins que maintenant, alors qu'il y en a pas mal. De la majorité des ORM existants, ce que je n'aime pas le plus, c'est que la plupart d'entre eux fonctionnent sur des interfaces vides. Autrement dit, les méthodes que dans ces ORM sont prêtes à prendre n'importe quoi: structure, pointeur de structure, nombre, quelque chose de non pertinent du tout ...

Notre ORM génère des structures basées sur le modèle de données. Lui-même. Et donc, toutes les méthodes sont concrètes, n'utilisent pas la réflexion, etc. Ils acceptent les structures et s'attendent à utiliser les structures qui viennent.

Q: - Combien de personnes ont participé?

MS: - Au stade initial, deux personnes ont participé. Quelque part en juin, nous avons commencé, en août la partie principale était prête (première version). En septembre, il y a eu une libération.

À:- Lorsque vous décrivez SSE, vous n'utilisez pas de délai d'expiration. Pourquoi donc?

MS: - Pour être honnête, SSE est toujours un protocole html5: la norme SSE est conçue pour communiquer avec les navigateurs, si je comprends bien. Il a des fonctionnalités supplémentaires pour que les navigateurs puissent se reconnecter (et ainsi de suite), mais nous n'en avons pas besoin, car nous avions des clients qui pouvaient implémenter n'importe quelle logique pour se connecter et recevoir des informations. Nous n'avons probablement pas fait d'ESS, mais quelque chose de similaire à SSE. Ce n'est pas le protocole lui-même.
Ce n'était pas nécessaire. Autant que je sache, les clients ont implémenté le mécanisme de connexion à partir de zéro. En principe, ils s'en fichaient.

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

SP:- Les plus actifs, nous avons utilisé govet et golint, afin que le style soit unifié, ainsi que gofmt. Ils n’ont rien utilisé d’autre.

Q: - Avec quoi avez-vous débogué?

MS: - Dans l'ensemble, le débogage a été effectué à l'aide de tests. Pas de débogueur, GOP que nous n'avons pas utilisé.

Q: - Pouvez-vous retourner la diapositive où la fonction de publication est implémentée? Les noms de variables à une seule lettre ne vous dérangent pas?

MS: - Non. Ils ont une portée assez "étroite". Ils ne sont utilisés nulle part ailleurs (sauf ici) (sauf pour l'intérieur de cette classe), et il est très compact - il ne prend que 7 lignes.

Q: - D'une certaine manière, ce n'est toujours pas intuitif ...

MS:- Non, non, c'est un vrai code! Ce n'est pas une question de style. C'est juste une classe utilitaire, très petite - il n'y a que 3 champs à l'intérieur de la classe ...



MS: - Dans l'ensemble, toutes les données synchronisées avec les clients (matchs saisonniers, joueurs) ne changent pas. En gros, si nous allons pratiquer un autre type de sport dans lequel il sera nécessaire de changer de match, nous ne considérerons que tout dans la nouvelle version du client, et les anciennes versions du client seront interdites.

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

MS: - Nous avons utilisé go dep.

Q: - Il y avait quelque chose à propos de la vidéo dans le sujet du rapport, mais rien à propos de la vidéo dans le rapport.

MS: - Non, je n'ai rien dans le sujet sur la vidéo. Il s'appelle «Look +» - c'est le nom de l'application.

À:- Vous avez dit que vous diffusiez en continu vers des clients? ..

MS: - Nous n'avons pas fait de streaming vidéo. Cela a été entièrement fait par Megaphone. Oui, je n'ai pas dit que l'application est un mégaphone.

MS: - Go - pour envoyer toutes les données - par score, par événements de match, statistiques ... Go - c'est tout le backend de l'application. Le client doit trouver quelque part quel lien utiliser pour le joueur afin que l'utilisateur puisse regarder le match. Nous avons des liens vers des vidéos et des flux qui sont préparés.


Un peu de publicité :)


Merci de rester avec nous. Aimez-vous nos articles? Vous voulez voir des matériaux plus intéressants? Soutenez-nous en passant une commande ou en recommandant à vos amis des VPS basés sur le cloud pour les développeurs à partir de 4,99 $ , un analogue unique de serveurs d'entrée de gamme que nous avons inventés pour vous: Toute la vérité sur les VPS (KVM) E5-2697 v3 (6 cœurs) 10 Go DDR4 480 Go SSD 1 Gbit / s à partir de 19 $ ou comment diviser le serveur? (les options sont disponibles avec RAID1 et RAID10, jusqu'à 24 cœurs et jusqu'à 40 Go de DDR4).

Dell R730xd 2 fois moins cher au centre de données Equinix Tier IV à Amsterdam? Nous avons seulement 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TV à partir de 199 $ aux Pays-Bas!Dell R420 - 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB - à partir de 99 $! En savoir plus sur la création d'un bâtiment d'infrastructure. classe c utilisant des serveurs Dell R730xd E5-2650 v4 coûtant 9 000 euros pour un sou?

All Articles