Mihail Salosin. Întâlnirea Golang. Folosind Go în backend-ul aplicației Look+

Mihail Salosin (denumit în continuare – MS): - Salutare tuturor! Numele meu este Michael. Lucrez ca dezvoltator backend la MC2 Software și voi vorbi despre utilizarea Go în backend-ul aplicației mobile Look+.

Mihail Salosin. Întâlnirea Golang. Folosind Go în backend-ul aplicației Look+

Ii place cuiva de aici hocheiul?

Mihail Salosin. Întâlnirea Golang. Folosind Go în backend-ul aplicației Look+

Atunci această aplicație este pentru tine. Este pentru Android și iOS și este folosit pentru a viziona transmisiuni ale diferitelor evenimente sportive online și înregistrate. Aplicația conține, de asemenea, diverse statistici, transmisii de text, tabele pentru conferințe, turnee și alte informații utile pentru fani.

Mihail Salosin. Întâlnirea Golang. Folosind Go în backend-ul aplicației Look+

De asemenea, în aplicație există și momente video, adică puteți viziona cele mai importante momente ale meciurilor (goluri, lupte, schimburi de focuri etc.). Dacă nu doriți să urmăriți întreaga emisiune, le puteți urmări doar pe cele mai interesante.

Ce ai folosit în dezvoltare?

Partea principală a fost scrisă în Go. API-ul cu care au comunicat clienții mobili a fost scris în Go. În Go a fost scris și un serviciu de trimitere a notificărilor push către telefoanele mobile. De asemenea, a trebuit să scriem propriul nostru ORM, despre care s-ar putea să vorbim cândva. Ei bine, câteva servicii mici au fost scrise în Go: redimensionarea și încărcarea imaginilor pentru editori...

Am folosit PostgreSQL ca bază de date. Interfața editorului a fost scrisă în Ruby on Rails folosind bijuteria ActiveAdmin. Importarea statisticilor de la un furnizor de statistici este scrisă și în Ruby.

Pentru testele API de sistem, am folosit test unitar Python. Memcached este folosit pentru a accelera apelurile de plată API, „Chef” este folosit pentru a controla configurația, Zabbix este folosit pentru a colecta și monitoriza statisticile interne ale sistemului. Graylog2 este pentru colectarea jurnalelor, Slate este documentația API pentru clienți.

Mihail Salosin. Întâlnirea Golang. Folosind Go în backend-ul aplicației Look+

Selectarea protocolului

Prima problemă pe care am întâlnit-o: trebuia să alegem un protocol de interacțiune între backend și clienții mobili, pe baza următoarelor puncte...

  • Cea mai importantă cerință: datele despre clienți trebuie să fie actualizate în timp real. Adică, toți cei care urmăresc în prezent emisiunea ar trebui să primească actualizări aproape instantaneu.
  • Pentru a simplifica lucrurile, am presupus că datele care sunt sincronizate cu clienții nu sunt șterse, ci sunt ascunse folosind steaguri speciale.
  • Tot felul de solicitări rare (cum ar fi statistici, componențele echipelor, statisticile echipei) sunt obținute prin solicitări GET obișnuite.
  • În plus, sistemul trebuia să suporte cu ușurință 100 de mii de utilizatori în același timp.

Pe baza acestui lucru, am avut două opțiuni de protocol:

  1. Websocket-uri. Dar nu aveam nevoie de canale de la client la server. Aveam nevoie doar să trimitem actualizări de la server la client, așa că un websocket este o opțiune redundantă.
  2. Evenimentele trimise de server (SSE) au apărut exact! Este destul de simplu și practic satisface tot ce avem nevoie.

Evenimente trimise de server

Câteva cuvinte despre cum funcționează chestia asta...

Acesta rulează pe o conexiune http. Clientul trimite o cerere, serverul răspunde cu Content-Type: text/event-stream și nu închide conexiunea cu clientul, ci continuă să scrie date în conexiune:

Mihail Salosin. Întâlnirea Golang. Folosind Go în backend-ul aplicației Look+

Datele pot fi trimise într-un format convenit cu clienții. În cazul nostru, l-am trimis în această formă: numele structurii modificate (persoană, jucător) a fost trimis în câmpul evenimentului, iar JSON cu câmpuri noi, modificate pentru jucător, a fost trimis în câmpul de date.

Acum să vorbim despre cum funcționează interacțiunea în sine.

  • Primul lucru pe care îl face clientul este să determine ultima dată când a fost efectuată sincronizarea cu serviciul: se uită la baza de date locală și determină data ultimei modificări înregistrate de acesta.
  • Trimite o cerere cu această dată.
  • Ca răspuns, îi trimitem toate actualizările care au avut loc de la acea dată.
  • După aceea, se conectează la canalul live și nu se închide până când nu are nevoie de aceste actualizări:

Mihail Salosin. Întâlnirea Golang. Folosind Go în backend-ul aplicației Look+

Îi trimitem o listă de modificări: dacă cineva marchează un gol, schimbăm scorul meciului, dacă se accidentează, acesta este și acesta transmis în timp real. Astfel, clienții primesc instantaneu date actualizate în feedul evenimentului meciului. Periodic, pentru ca clientul să înțeleagă că serverul nu a murit, că nu i s-a întâmplat nimic, trimitem un timestamp la fiecare 15 secunde - pentru ca acesta să știe că totul este în regulă și nu este nevoie să se reconecteze.

Cum este deservită conexiunea live?

  • În primul rând, creăm un canal în care vor fi primite actualizări tampon.
  • După aceea, ne abonăm la acest canal pentru a primi actualizări.
  • Setăm antetul corect, astfel încât clientul să știe că totul este în regulă.
  • Trimite primul ping. Înregistrăm pur și simplu marcajul de timp al conexiunii curente.
  • După aceea, citim de pe canal într-o buclă până când canalul de actualizare este închis. Canalul primește periodic fie marcajul de timp actual, fie modificările pe care le scriem deja pentru a deschide conexiunile.

Mihail Salosin. Întâlnirea Golang. Folosind Go în backend-ul aplicației Look+

Prima problemă pe care am întâlnit-o a fost următoarea: pentru fiecare conexiune deschisă cu clientul, am creat un cronometru care bifa o dată la 15 secunde - se dovedește că dacă aveam 6 mii de conexiuni deschise cu o singură mașină (cu un server API), 6 au fost create mii de cronometre. Acest lucru a făcut ca mașina să nu țină sarcina necesară. Problema nu era atât de evidentă pentru noi, dar am primit puțin ajutor și am rezolvat-o.

Drept urmare, acum ping-ul nostru vine de pe același canal din care vine actualizarea.

În consecință, există un singur cronometru care bifează o dată la 15 secunde.

Există mai multe funcții auxiliare aici - trimiterea antetului, ping și structura în sine. Adică numele mesei (persoană, meci, sezon) și informațiile despre această intrare sunt transmise aici:

Mihail Salosin. Întâlnirea Golang. Folosind Go în backend-ul aplicației Look+

Mecanism de trimitere a actualizărilor

Acum puțin despre de unde vin schimbările. Avem mai multe persoane, redactori, care urmăresc emisiunea în timp real. Ei creează toate evenimentele: cineva a fost dat afară, cineva a fost rănit, un fel de înlocuitor...

Folosind un CMS, datele intră în baza de date. După aceasta, baza de date anunță serverele API despre acest lucru folosind mecanismul Ascultare/Notificare. Serverele API trimit deja aceste informații către clienți. Astfel, avem în esență doar câteva servere conectate la baza de date și nu există nicio încărcare specială pe baza de date, deoarece clientul nu interacționează direct cu baza de date în niciun fel:

Mihail Salosin. Întâlnirea Golang. Folosind Go în backend-ul aplicației Look+

PostgreSQL: Ascultă/Notifică

Mecanismul de ascultare/notificare din Postgres vă permite să notificați abonații la eveniment că un eveniment s-a schimbat - o înregistrare a fost creată în baza de date. Pentru a face acest lucru, am scris un declanșator și o funcție simplă:

Mihail Salosin. Întâlnirea Golang. Folosind Go în backend-ul aplicației Look+

La introducerea sau modificarea unei înregistrări apelăm funcția notify pe canalul data_updates, trecând acolo numele tabelului și identificatorul înregistrării care a fost schimbată sau introdusă.

Pentru toate tabelele care trebuie sincronizate cu clientul, definim un trigger, care, dupa modificarea/actualizarea unei inregistrari, apeleaza functia indicata in slide-ul de mai jos.
Cum se abonează API-ul la aceste modificări?

Este creat un mecanism Fanout - acesta trimite mesaje clientului. Acesta colectează toate canalele clienților și trimite actualizările primite prin aceste canale:

Mihail Salosin. Întâlnirea Golang. Folosind Go în backend-ul aplicației Look+

Aici biblioteca standard pq, care se conectează la baza de date și spune că vrea să asculte canalul (data_updates), verifică dacă conexiunea este deschisă și totul este în regulă. Omit verificarea erorilor pentru a economisi spațiu (neverificarea este periculoasă).

Apoi, setăm asincron Ticker, care va trimite un ping la fiecare 15 secunde și începe să ascultăm canalul la care ne-am abonat. Dacă primim un ping, publicăm acest ping. Dacă primim un fel de intrare, atunci publicăm această intrare tuturor abonaților acestui Fanout.

Cum funcționează Fan-out?

În rusă, acest lucru se traduce prin „splitter”. Avem un obiect care înregistrează abonații care doresc să primească niște actualizări. Și de îndată ce o actualizare ajunge la acest obiect, acesta distribuie această actualizare tuturor abonaților săi. Destul de simplu:

Mihail Salosin. Întâlnirea Golang. Folosind Go în backend-ul aplicației Look+

Cum este implementat în Go:

Mihail Salosin. Întâlnirea Golang. Folosind Go în backend-ul aplicației Look+

Există o structură, este sincronizată folosind Mutexuri. Are un câmp care salvează starea conexiunii Fanout la baza de date, adică ascultă în prezent și va primi actualizări, precum și o listă cu toate canalele disponibile - hartă, a cărei cheie este canalul și structura sub formă de valori (în esență, nu este folosit în niciun fel).

Două metode - Connected și Disconnected - ne permit să-i spunem lui Fanout că avem o conexiune la bază, a apărut și că conexiunea la bază a fost întreruptă. În al doilea caz, trebuie să deconectați toți clienții și să le spuneți că nu mai pot asculta nimic și că se reconectează pentru că conexiunea cu aceștia s-a închis.

Există, de asemenea, o metodă de abonare care adaugă canalul la „ascultători”:

Mihail Salosin. Întâlnirea Golang. Folosind Go în backend-ul aplicației Look+

Există o metodă Unsubscribe, care elimină canalul de la ascultători dacă clientul se deconectează, precum și o metodă Publicare, care vă permite să trimiteți un mesaj tuturor abonaților.

Întrebare: – Ce se transmite prin acest canal?

DOMNIȘOARĂ: – Modelul care s-a schimbat sau este transmis ping (în esență doar un număr, un întreg).

DOMNIȘOARĂ: – Puteți să trimiteți orice, să trimiteți orice structură, să o publicați – pur și simplu se transformă în JSON și gata.

DOMNIȘOARĂ: – Primim o notificare de la Postgres – conține numele și identificatorul tabelului. Pe baza numelui și a identificatorului tabelului, obținem înregistrarea de care avem nevoie și apoi trimitem această structură pentru publicare.

Infrastructură

Cum arată asta din perspectiva infrastructurii? Avem 7 servere hardware: unul dintre ele este complet dedicat bazei de date, celelalte șase rulează mașini virtuale. Există 6 copii ale API: fiecare mașină virtuală cu API rulează pe un server hardware separat - asta pentru fiabilitate.

Mihail Salosin. Întâlnirea Golang. Folosind Go în backend-ul aplicației Look+

Avem două frontend-uri cu Keepalived instalat pentru a îmbunătăți accesibilitatea, astfel încât, dacă se întâmplă ceva, un frontend îl poate înlocui pe celălalt. De asemenea – două copii ale CMS.

Există și un importator de statistici. Există un DB Slave din care se fac periodic copii de rezervă. Există Pigeon Pusher, o aplicație care trimite notificări push clienților, precum și lucruri de infrastructură: Zabbix, Graylog2 și Chef.

De fapt, această infrastructură este redundantă, deoarece 100 de mii pot fi deservite cu mai puține servere. Dar era fier – noi l-am folosit (ni s-a spus că se poate – de ce nu).

Avantajele Go

După ce am lucrat la această aplicație, au apărut astfel de avantaje evidente ale Go.

  • Bibliotecă http cool. Cu el puteți crea destul de multe din cutie.
  • Plus, canale care ne-au permis să implementăm foarte ușor un mecanism de trimitere a notificărilor către clienți.
  • Lucrul minunat Detectorul de curse ne-a permis să eliminăm câteva erori critice (infrastructură de punere în scenă). Se lansează tot ce funcționează la punere în scenă, compilat cu cheia Race; și, în consecință, ne putem uita la infrastructura de punere în scenă pentru a vedea ce probleme potențiale avem.
  • Minimalismul și simplitatea limbajului.

Mihail Salosin. Întâlnirea Golang. Folosind Go în backend-ul aplicației Look+

Căutăm dezvoltatori! Daca vrea cineva, va rog.

întrebări

Întrebare din partea publicului (în continuare – B): – Mi se pare că ați omis un punct important referitor la Fan-out. Am dreptate să înțeleg că atunci când trimiți un răspuns unui client, blochezi dacă clientul nu vrea să citească?

DOMNIȘOARĂ: - Nu, nu blocăm. În primul rând, avem toate acestea în spatele nginx, adică nu există probleme cu clienții lenți. În al doilea rând, clientul are un canal cu un buffer - de fapt, putem pune până la o sută de actualizări acolo... Dacă nu putem scrie pe canal, atunci îl șterge. Dacă vedem că canalul este blocat, atunci pur și simplu vom închide canalul și asta este tot - clientul se va reconecta dacă apare vreo problemă. Prin urmare, în principiu, nu există nicio blocare aici.

ÎN: – Nu ar putea fi posibilă trimiterea imediată a unei înregistrări către Listen/Notify și nu a unui tabel de identificare?

DOMNIȘOARĂ: – Listen/Notify are o limită de 8 mii de octeți pentru preîncărcarea pe care o trimite. În principiu, ar fi posibil să trimitem dacă am avea de-a face cu o cantitate mică de date, dar mi se pare că așa [modul în care o facem] este pur și simplu mai fiabil. Limitările sunt în Postgres însuși.

ÎN: – Clienții primesc actualizări despre meciurile de care nu sunt interesați?

DOMNIȘOARĂ: - În general, da. De regulă, se desfășoară 2-3 meciuri în paralel și chiar și atunci destul de rar. Dacă un client urmărește ceva, atunci de obicei se uită la meciul care are loc. Apoi, clientul are o bază de date locală în care sunt adăugate toate aceste actualizări și chiar și fără o conexiune la Internet, clientul poate vizualiza toate meciurile anterioare pentru care are actualizări. În esență, sincronizăm baza noastră de date de pe server cu baza de date locală a clientului, astfel încât acesta să poată lucra offline.

ÎN: – De ce ți-ai făcut propriul ORM?

Alexey (unul dintre dezvoltatorii Look+): – Pe atunci (a fost acum un an) erau mai puține ORM-uri decât acum, când sunt destul de multe. Lucrul meu preferat la majoritatea ORM-urilor este că majoritatea rulează pe interfețe goale. Adică, metodele din aceste ORM-uri sunt gata să preia orice: o structură, un indicator de structură, un număr, ceva complet irelevant...

ORM-ul nostru generează structuri bazate pe modelul de date. Eu insumi. Și, prin urmare, toate metodele sunt concrete, nu folosesc reflexia etc. Acceptă structuri și se așteaptă să folosească acele structuri care vin.

ÎN: – Câți oameni au participat?

DOMNIȘOARĂ: – La etapa inițială, au participat două persoane. Am început undeva în iunie, iar în august partea principală era gata (prima versiune). A fost o lansare în septembrie.

ÎN: – Acolo unde descrii SSE, nu folosești timeout. De ce este asta?

DOMNIȘOARĂ: – Sincer să fiu, SSE este încă un protocol html5: standardul SSE este conceput pentru a comunica cu browserele, din câte am înțeles. Are funcții suplimentare, astfel încât browserele să se poată reconecta (și așa mai departe), dar nu avem nevoie de ele, pentru că aveam clienți care puteau implementa orice logică pentru conectarea și primirea informațiilor. Noi nu am făcut SSE, ci ceva asemănător SSE. Acesta nu este protocolul în sine.
Nu era nevoie. Din câte am înțeles, clienții au implementat mecanismul de conectare aproape de la zero. Nu prea le păsa.

ÎN: – Ce utilități suplimentare ați folosit?

DOMNIȘOARĂ: – Am folosit cel mai activ govet și golint pentru a unifica stilul, precum și gofmt. Nu s-a folosit nimic altceva.

ÎN: – Ce ai folosit pentru a depana?

DOMNIȘOARĂ: – Depanarea a fost efectuată în mare parte folosind teste. Nu am folosit niciun depanator sau GOP.

ÎN: – Puteți returna slide-ul în care este implementată funcția Publicare? Numele variabilelor cu o singură literă vă derutează?

DOMNIȘOARĂ: - Nu. Au o rază de vizibilitate destul de „îngustă”. Nu sunt folosite în altă parte decât aici (cu excepția elementelor interne ale acestei clase) și este foarte compact - durează doar 7 linii.

ÎN: – Cumva, încă nu este intuitiv...

DOMNIȘOARĂ: - Nu, nu, acesta este un cod adevărat! Nu e vorba de stil. Este doar o clasă atât de utilitară, foarte mică - doar 3 câmpuri în interiorul clasei...

Mihail Salosin. Întâlnirea Golang. Folosind Go în backend-ul aplicației Look+

DOMNIȘOARĂ: – În general, toate datele care sunt sincronizate cu clienții (meciuri de sezon, jucători) nu se modifică. Aproximativ, dacă facem un alt sport în care trebuie să schimbăm meciul, pur și simplu vom ține cont de totul în noua versiune a clientului, iar versiunile vechi ale clientului vor fi interzise.

ÎN: – Există pachete de gestionare a dependenței de la terți?

DOMNIȘOARĂ: – Am folosit go dep.

ÎN: – A fost ceva despre video în subiectul raportului, dar nu era nimic în raport despre video.

DOMNIȘOARĂ: – Nu, nu am nimic în subiect despre videoclip. Se numește „Look+” - acesta este numele aplicației.

ÎN: – Ai spus că este transmis clienților?...

DOMNIȘOARĂ: – Nu am fost implicați în streaming video. Acest lucru a fost realizat în întregime de Megafon. Da, nu am spus că aplicația este MegaFon.

DOMNIȘOARĂ: – Go – pentru trimiterea tuturor datelor – despre scor, despre meciuri, statistici... Go este întregul backend al aplicației. Clientul trebuie să știe de undeva ce link să folosească pentru jucător, astfel încât utilizatorul să poată urmări meciul. Avem link-uri către videoclipuri și fluxuri care au fost pregătite.

Câteva reclame 🙂

Vă mulțumim că ați rămas cu noi. Vă plac articolele noastre? Vrei să vezi mai mult conținut interesant? Susține-ne plasând o comandă sau recomandând prietenilor, cloud VPS pentru dezvoltatori de la 4.99 USD, un analog unic al serverelor entry-level, care a fost inventat de noi pentru tine: Întregul adevăr despre VPS (KVM) E5-2697 v3 (6 nuclee) 10GB DDR4 480GB SSD 1Gbps de la 19 USD sau cum să partajezi un server? (disponibil cu RAID1 și RAID10, până la 24 de nuclee și până la 40 GB DDR4).

Dell R730xd de 2 ori mai ieftin în centrul de date Equinix Tier IV din Amsterdam? Numai aici 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TV de la 199 USD in Olanda! Dell R420 - 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB - de la 99 USD! Citește despre Cum se construiește infrastructura corp. clasa cu folosirea serverelor Dell R730xd E5-2650 v4 in valoare de 9000 euro pentru un ban?

Sursa: www.habr.com

Adauga un comentariu