Mikhail Salosin. Trobada de Golang. Utilitzant Go a la part posterior de l'aplicació Look+

Mikhail Salosin (d'ara endavant - MS): - Hola a tots! Em dic Michael. Treballo com a desenvolupador de fons a MC2 Software i parlaré de l'ús de Go al backend de l'aplicació mòbil Look+.

Mikhail Salosin. Trobada de Golang. Utilitzant Go a la part posterior de l'aplicació Look+

A algú aquí li agrada l'hoquei?

Mikhail Salosin. Trobada de Golang. Utilitzant Go a la part posterior de l'aplicació Look+

Aleshores, aquesta aplicació és per a vostè. És per a Android i iOS i s'utilitza per veure emissions de diversos esdeveniments esportius en línia i gravats. L'aplicació també conté diverses estadístiques, emissions de text, taules per a conferències, tornejos i altra informació útil per als aficionats.

Mikhail Salosin. Trobada de Golang. Utilitzant Go a la part posterior de l'aplicació Look+

També a l'aplicació hi ha moments de vídeo, és a dir, podeu veure els moments més importants dels partits (gols, baralles, shootouts, etc.). Si no voleu veure l'emissió sencera, només podeu veure les més interessants.

Què vas utilitzar en el desenvolupament?

La part principal va ser escrita en Go. L'API amb la qual es comunicaven els clients mòbils es va escriure a Go. A Go també es va escriure un servei per enviar notificacions push als telèfons mòbils. També vam haver d'escriure el nostre propi ORM, del qual podríem parlar algun dia. Bé, alguns petits serveis es van escriure a Go: redimensionar i carregar imatges per als editors...

Hem utilitzat PostgreSQL com a base de dades. La interfície de l'editor es va escriure en Ruby on Rails mitjançant la joia ActiveAdmin. La importació d'estadístiques d'un proveïdor d'estadístiques també està escrita en Ruby.

Per a les proves de l'API del sistema, hem utilitzat Python unittest. Memcached s'utilitza per accelerar les trucades de pagament de l'API, "Chef" s'utilitza per controlar la configuració, Zabbix s'utilitza per recopilar i supervisar les estadístiques internes del sistema. Graylog2 és per recopilar registres, Slate és documentació de l'API per als clients.

Mikhail Salosin. Trobada de Golang. Utilitzant Go a la part posterior de l'aplicació Look+

Selecció del protocol

El primer problema que vam trobar: calia triar un protocol d'interacció entre el backend i els clients mòbils, a partir dels següents punts...

  • El requisit més important: les dades dels clients s'han d'actualitzar en temps real. És a dir, tothom que està veient l'emissió actualment hauria de rebre actualitzacions gairebé a l'instant.
  • Per simplificar les coses, vam suposar que les dades que es sincronitzen amb els clients no s'eliminen, sinó que s'amaguen mitjançant senyals especials.
  • Tot tipus de sol·licituds rares (com ara estadístiques, composicions d'equip, estadístiques d'equip) s'obtenen mitjançant sol·licituds GET normals.
  • A més, el sistema havia de suportar fàcilment 100 mil usuaris alhora.

A partir d'això, teníem dues opcions de protocol:

  1. Websockets. Però no necessitàvem canals del client al servidor. Només ens calia enviar actualitzacions del servidor al client, de manera que un websocket és una opció redundant.
  2. Els esdeveniments enviats pel servidor (SSE) van sortir bé! És bastant senzill i bàsicament satisfà tot el que necessitem.

Esdeveniments enviats pel servidor

Unes paraules sobre com funciona aquesta cosa...

S'executa a sobre d'una connexió http. El client envia una sol·licitud, el servidor respon amb Content-Type: text/event-stream i no tanca la connexió amb el client, però continua escrivint dades a la connexió:

Mikhail Salosin. Trobada de Golang. Utilitzant Go a la part posterior de l'aplicació Look+

Les dades es poden enviar en un format acordat amb els clients. En el nostre cas, l'hem enviat d'aquesta forma: el nom de l'estructura modificada (persona, jugador) s'ha enviat al camp d'esdeveniment, i el JSON amb camps nous i modificats per al jugador s'ha enviat al camp de dades.

Ara parlem de com funciona la interacció en si.

  • El primer que fa el client és determinar la darrera vegada que s'ha realitzat la sincronització amb el servei: mira la seva base de dades local i determina la data de l'últim canvi que ha registrat.
  • Envia una sol·licitud amb aquesta data.
  • Com a resposta, li enviem totes les actualitzacions que s'han produït des d'aquella data.
  • Després d'això, es connecta al canal en directe i no es tanca fins que necessita aquestes actualitzacions:

Mikhail Salosin. Trobada de Golang. Utilitzant Go a la part posterior de l'aplicació Look+

Li enviem una llista de canvis: si algú marca un gol, canviem el marcador del partit, si es lesiona, aquest també s'envia en temps real. Així, els clients reben instantàniament dades actualitzades al feed d'esdeveniments de partit. Periòdicament, perquè el client entengui que el servidor no ha mort, que no li ha passat res, enviem una marca de temps cada 15 segons, perquè sàpiga que tot està en ordre i no cal tornar a connectar-se.

Com es presta el servei de connexió en directe?

  • En primer lloc, creem un canal on es rebran les actualitzacions de memòria intermèdia.
  • Després d'això, ens subscrivim a aquest canal per rebre actualitzacions.
  • Establim la capçalera correcta perquè el client sàpiga que tot està bé.
  • Envia el primer ping. Simplement registrem la marca de temps de connexió actual.
  • Després d'això, llegim del canal en bucle fins que es tanqui el canal d'actualització. El canal rep periòdicament la marca de temps actual o els canvis que ja estem escrivint per obrir connexions.

Mikhail Salosin. Trobada de Golang. Utilitzant Go a la part posterior de l'aplicació Look+

El primer problema que vam trobar va ser el següent: per cada connexió oberta amb el client, vam crear un temporitzador que marcava un cop cada 15 segons; resulta que si teníem 6 mil connexions obertes amb una màquina (amb un servidor API), 6 es van crear mil temporitzadors. Això va fer que la màquina no aguanti la càrrega requerida. El problema no era tan obvi per a nosaltres, però vam rebre una mica d'ajuda i el vam solucionar.

Com a resultat, ara el nostre ping prové del mateix canal del qual prové l'actualització.

En conseqüència, només hi ha un temporitzador que marca una vegada cada 15 segons.

Aquí hi ha diverses funcions auxiliars: enviament de la capçalera, ping i la pròpia estructura. És a dir, el nom de la taula (persona, partit, temporada) i la informació sobre aquesta entrada es transmet aquí:

Mikhail Salosin. Trobada de Golang. Utilitzant Go a la part posterior de l'aplicació Look+

Mecanisme d'enviament d'actualitzacions

Ara una mica d'on provenen els canvis. Tenim diverses persones, editors, que miren l'emissió en temps real. Creen tots els esdeveniments: algú va ser expulsat, algú va resultar ferit, algun tipus de substitut...

Mitjançant un CMS, les dades entren a la base de dades. Després d'això, la base de dades ho notifica als servidors de l'API mitjançant el mecanisme Escolta/Notifica. Els servidors de l'API ja envien aquesta informació als clients. Així, essencialment només tenim uns quants servidors connectats a la base de dades i no hi ha cap càrrega especial a la base de dades, perquè el client no interactua directament amb la base de dades de cap manera:

Mikhail Salosin. Trobada de Golang. Utilitzant Go a la part posterior de l'aplicació Look+

PostgreSQL: Escoltar/Notificar

El mecanisme Escolta/Notifica a Postgres us permet notificar als subscriptors d'esdeveniments que algun esdeveniment ha canviat: s'ha creat algun registre a la base de dades. Per fer-ho, hem escrit un activador i una funció senzills:

Mikhail Salosin. Trobada de Golang. Utilitzant Go a la part posterior de l'aplicació Look+

En inserir o canviar un registre, cridem a la funció de notificació del canal data_updates, passant-hi el nom de la taula i l'identificador del registre que s'ha modificat o inserit.

Per a totes les taules que s'han de sincronitzar amb el client, definim un disparador que, després de canviar/actualitzar un registre, crida a la funció indicada a la diapositiva següent.
Com es subscriu l'API a aquests canvis?

Es crea un mecanisme Fanout: envia missatges al client. Recull tots els canals dels clients i envia les actualitzacions que ha rebut a través d'aquests canals:

Mikhail Salosin. Trobada de Golang. Utilitzant Go a la part posterior de l'aplicació Look+

Aquí la biblioteca pq estàndard, que es connecta a la base de dades i diu que vol escoltar el canal (data_updates), comprova que la connexió està oberta i tot està bé. Estic ometent la comprovació d'errors per estalviar espai (no comprovar-ho és perillós).

A continuació, configurem Ticker de manera asíncrona, que enviarà un ping cada 15 segons i començarem a escoltar el canal al qual ens vam subscriure. Si rebem un ping, el publiquem. Si rebem algun tipus d'entrada, aleshores la publiquem a tots els subscriptors d'aquest Fanout.

Com funciona Fan-out?

En rus això es tradueix com "divisor". Tenim un objecte que registra els subscriptors que volen rebre algunes actualitzacions. I tan bon punt arriba una actualització a aquest objecte, distribueix aquesta actualització a tots els seus subscriptors. Prou senzill:

Mikhail Salosin. Trobada de Golang. Utilitzant Go a la part posterior de l'aplicació Look+

Com s'implementa a Go:

Mikhail Salosin. Trobada de Golang. Utilitzant Go a la part posterior de l'aplicació Look+

Hi ha una estructura, es sincronitza mitjançant Mutexes. Té un camp que desa l'estat de la connexió de Fanout a la base de dades, és a dir, actualment està escoltant i rebrà actualitzacions, així com una llista de tots els canals disponibles: mapa, la clau del qual és el canal i l'estructura en forma de valors (essencialment no s'utilitza de cap manera).

Dos mètodes - Connectat i Desconnectat - ens permeten dir a Fanout que tenim una connexió amb la base, ha aparegut i que la connexió amb la base s'ha trencat. En el segon cas, cal desconnectar tots els clients i dir-los que ja no poden escoltar res i que es tornen a connectar perquè s'ha tancat la connexió amb ells.

També hi ha un mètode de subscripció que afegeix el canal als "oients":

Mikhail Salosin. Trobada de Golang. Utilitzant Go a la part posterior de l'aplicació Look+

Hi ha un mètode Unsubscribe, que elimina el canal dels oients si el client es desconnecta, així com un mètode Publish, que permet enviar un missatge a tots els subscriptors.

Pregunta: – Què es transmet per aquest canal?

SENYORA: – Es transmet el model que ha canviat o ping (essencialment només un número, un nombre enter).

SENYORA: – Pots enviar qualsevol cosa, enviar qualsevol estructura, publicar-la; només es converteix en JSON i ja està.

SENYORA: – Rebem una notificació de Postgres: conté el nom i l'identificador de la taula. A partir del nom i l'identificador de la taula, obtenim el registre que necessitem i després enviem aquesta estructura per a la seva publicació.

Infraestructura

Com és això des d'una perspectiva d'infraestructures? Disposem de 7 servidors de maquinari: un d'ells està totalment dedicat a la base de dades, els altres sis fan servir màquines virtuals. Hi ha 6 còpies de l'API: cada màquina virtual amb l'API s'executa en un servidor de maquinari independent, això és per fiabilitat.

Mikhail Salosin. Trobada de Golang. Utilitzant Go a la part posterior de l'aplicació Look+

Tenim dues interfícies amb Keepalived instal·lat per millorar l'accessibilitat, de manera que si passa alguna cosa, una interfície pot substituir l'altra. També: dues còpies del CMS.

També hi ha un importador d'estadístiques. Hi ha un esclau DB des del qual es fan còpies de seguretat periòdicament. Hi ha Pigeon Pusher, una aplicació que envia notificacions push als clients, així com coses d'infraestructura: Zabbix, Graylog2 i Chef.

De fet, aquesta infraestructura és redundant, perquè 100 mil es poden servir amb menys servidors. Però hi havia ferro, el vam fer servir (ens van dir que era possible, per què no).

Avantatges de Go

Després de treballar en aquesta aplicació, van sorgir avantatges tan evidents de Go.

  • Biblioteca http genial. Amb ell pots crear bastant des de la caixa.
  • A més, canals que ens van permetre implementar molt fàcilment un mecanisme d'enviament de notificacions als clients.
  • El meravellós que el detector de carreres ens va permetre eliminar diversos errors crítics (infraestructura de posada en escena). Es posa en marxa tot el que funciona en la posada en escena, compilat amb la clau de cursa; i, en conseqüència, podem mirar la infraestructura d'escenificació per veure quins problemes potencials tenim.
  • Minimalisme i senzillesa del llenguatge.

Mikhail Salosin. Trobada de Golang. Utilitzant Go a la part posterior de l'aplicació Look+

Busquem desenvolupadors! Si algú vol, si us plau.

Les vostres preguntes

Pregunta de l'audiència (d'ara endavant – B): – Em sembla que t'has perdut un punt important sobre Fan-out. Tinc raó en entendre que quan envieu una resposta a un client, bloquegeu si el client no vol llegir?

SENYORA: - No, no estem bloquejant. En primer lloc, tenim tot això darrere de nginx, és a dir, no hi ha problemes amb clients lents. En segon lloc, el client té un canal amb un buffer; de fet, hi podem posar fins a un centenar d'actualitzacions... Si no podem escriure al canal, l'elimina. Si veiem que el canal està bloquejat, simplement tancarem el canal i ja està: el client es tornarà a connectar si sorgeix algun problema. Per tant, en principi, aquí no hi ha cap bloqueig.

AT: – No podria ser possible enviar immediatament un registre a Listen/Notify, i no una taula d'identificadors?

SENYORA: – Listen/Notify té un límit de 8 mil bytes a la precàrrega que envia. En principi, seria possible enviar si estiguéssim tractant amb una petita quantitat de dades, però em sembla que així [la manera com ho fem] és simplement més fiable. Les limitacions es troben al mateix Postgres.

AT: – Els clients reben actualitzacions sobre partits que no els interessen?

SENYORA: - En general, sí. Per regla general, hi ha 2-3 partits en paral·lel, i fins i tot molt poques vegades. Si un client està veient alguna cosa, normalment està veient el partit que està passant. Aleshores, el client té una base de dades local a la qual s'afegeixen totes aquestes actualitzacions, i fins i tot sense connexió a Internet, el client pot veure totes les coincidències anteriors per a les quals té actualitzacions. Bàsicament, sincronitzem la nostra base de dades al servidor amb la base de dades local del client perquè pugui treballar fora de línia.

AT: – Per què vas fer el teu propi ORM?

Alexey (un dels desenvolupadors de Look+): – En aquella època (fa un any) hi havia menys ORM que ara, quan n'hi ha força. El que més m'agrada de la majoria dels ORM és que la majoria s'executen amb interfícies buides. És a dir, els mètodes d'aquests ORM estan preparats per assumir qualsevol cosa: una estructura, un punter d'estructura, un nombre, quelcom completament irrellevant...

El nostre ORM genera estructures basades en el model de dades. jo mateix. I, per tant, tots els mètodes són concrets, no fan servir la reflexió, etc. Accepten estructures i esperen utilitzar aquelles estructures que vénen.

AT: – Quanta gent hi va participar?

SENYORA: – En l'etapa inicial hi van participar dues persones. Vam començar en algun lloc al juny, i a l'agost la part principal estava llesta (la primera versió). Hi va haver un llançament al setembre.

AT: – Quan descriu SSE, no utilitzeu el temps d'espera. Per què això?

SENYORA: – Per ser honest, SSE segueix sent un protocol html5: l'estàndard SSE està dissenyat per comunicar-se amb els navegadors, pel que entenc. Té funcions addicionals perquè els navegadors es puguin tornar a connectar (i així successivament), però no les necessitem, perquè teníem clients que podien implementar qualsevol lògica per connectar-se i rebre informació. No vam fer SSE, sinó quelcom semblant a SSE. Aquest no és el protocol en si.
No hi havia necessitat. Pel que tinc entès, els clients van implementar el mecanisme de connexió gairebé des de zero. Realment no els importava.

AT: – Quines utilitats addicionals heu utilitzat?

SENYORA: – Hem utilitzat de manera més activa govet i golint per unificar l'estil, així com gofmt. No es va utilitzar res més.

AT: – Què vas utilitzar per depurar?

SENYORA: – La depuració es va dur a terme en gran part mitjançant proves. No hem utilitzat cap depurador ni GOP.

AT: – Pots tornar la diapositiva on està implementada la funció Publicar? Els noms de variables d'una sola lletra et confonen?

SENYORA: - No. Tenen un àmbit de visibilitat força "estret". No s'utilitzen en cap altre lloc excepte aquí (excepte els elements interns d'aquesta classe) i és molt compacte: només necessita 7 línies.

AT: – D'alguna manera encara no és intuïtiu...

SENYORA: - No, no, aquest és un codi real! No es tracta d'estil. És una classe tan utilitària i molt petita: només 3 camps dins de la classe...

Mikhail Salosin. Trobada de Golang. Utilitzant Go a la part posterior de l'aplicació Look+

SENYORA: – En general, totes les dades que es sincronitzen amb els clients (partides de temporada, jugadors) no canvien. A grans trets, si fem un altre esport en el qual hem de canviar el partit, simplement ho tindrem en compte en la nova versió del client, i les versions antigues del client quedaran prohibides.

AT: – Hi ha algun paquet de gestió de dependències de tercers?

SENYORA: – Hem utilitzat go dep.

AT: – Hi havia alguna cosa sobre vídeo al tema de l'informe, però no hi havia res a l'informe sobre vídeo.

SENYORA: – No, no tinc res al tema sobre el vídeo. Es diu "Mira +": aquest és el nom de l'aplicació.

AT: – Vas dir que es transmet als clients?...

SENYORA: – No estàvem involucrats en streaming de vídeo. Això va ser fet completament per Megafon. Sí, no vaig dir que l'aplicació fos MegaFon.

SENYORA: – Go, per enviar totes les dades, sobre el marcador, els esdeveniments del partit, les estadístiques... Go és el backend complet de l'aplicació. El client ha de saber des d'algun lloc quin enllaç utilitzar per al jugador perquè l'usuari pugui veure el partit. Tenim enllaços a vídeos i fluxos que s'han preparat.

Alguns anuncis 🙂

Gràcies per quedar-te amb nosaltres. T'agraden els nostres articles? Vols veure més contingut interessant? Doneu-nos suport fent una comanda o recomanant als amics, Cloud VPS per a desenvolupadors des de 4.99 dòlars, un anàleg únic dels servidors d'entrada, que vam inventar per a vosaltres: Tota la veritat sobre VPS (KVM) E5-2697 v3 (6 nuclis) 10 GB DDR4 480 GB SSD 1 Gbps des de 19 dòlars o com compartir un servidor? (disponible amb RAID1 i RAID10, fins a 24 nuclis i fins a 40 GB DDR4).

Dell R730xd 2 vegades més barat al centre de dades Equinix Tier IV a Amsterdam? Només aquí 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6 GHz 14C 64 GB DDR4 4 x 960 GB SSD 1 Gbps 100 TV des de 199 $ als Països Baixos! Dell R420 - 2x E5-2430 2.2 Ghz 6C 128 GB DDR3 2 x 960 GB SSD 1 Gbps 100 TB - a partir de 99 $! Llegeix sobre Com construir infrastructure corp. classe amb l'ús de servidors Dell R730xd E5-2650 v4 per valor de 9000 euros per un cèntim?

Font: www.habr.com

Afegeix comentari