Mihail Salosin. Golang Meetup. Utilizarea Go în backend-ul aplicației „Smatri+”

Mihail Salosin (în continuare – MS): – Salut tuturor! Numele meu este Mihail. Lucrez ca dezvoltator backend la compania MC2 Software, iar eu voi vorbi despre utilizarea Go în backendul aplicației mobile „Smotri+”.

Mihail Salosin. Golang Meetup. Utilizarea Go în backend-ul aplicației „Smatri+”

Cine dintre cei prezenți iubește hocheiul?

Mihail Salosin. Golang Meetup. Utilizarea Go în backend-ul aplicației „Smatri+”

Atunci această aplicație este pentru voi. Este disponibilă pentru Android și iOS, servește pentru a viziona transmisii din diferite evenimente sportive în direct și înregistrate. De asemenea, aplicația oferă diferite statistici, transmisii textuale, tabele pentru conferințe, turnee și alte informații utile pentru fani.

Mihail Salosin. Golang Meetup. Utilizarea Go în backend-ul aplicației „Smatri+”

De asemenea, în aplicație există o funcționalitate numită momente video, adică poți viziona cele mai intense momente ale meciurilor (goluri, bătăi, penalty-uri etc.). Dacă nu vrei să vezi toată transmisiunea, poți viziona doar cele mai interesante părți.

Ce am folosit în dezvoltare?

Partea principală a fost scrisă în Go. API-ul cu care au comunicat clienții mobili a fost scris în Go. De asemenea, un serviciu pentru trimiterea notificărilor push pe dispozitive mobile a fost scris în Go. Ne-am mai creat și propriul ORM, despre care poate vom povesti cândva. Și au fost scrise în Go câteva servicii mici: redimensionarea și încărcarea imaginilor pentru partea editorilor...

Ca bază de date, am folosit PostgreSQL. Interfața pentru editori a fost scrisă în Ruby on Rails cu ajutorul gem-ului ActiveAdmin. Importul statisticilor de la furnizorul de statistici a fost realizat tot în Ruby.

Pentru testele de sistem ale API-ului, am folosit unittest din Python. Memcached este utilizat pentru throttlingul apelurilor API de plată, Chef – pentru controlul configurației, Zabbix – pentru colectarea și monitorizarea datelor statistice interne ale sistemului. Graylog2 – pentru colectarea logurilor, Slate – documentația API pentru clienți.

Mihail Salosin. Golang Meetup. Utilizarea Go în backend-ul aplicației „Smatri+”

Alegerea protocolului

Prima problemă cu care ne-am confruntat: a trebuit să alegem un protocol de interacțiune între backend și clienții mobili, ținând cont de următoarele puncte...

  • Cea mai importantă cerință: datele pe clienți trebuie să se actualizeze în timp real. Adică toți cei care vizionează în acel moment transmisia trebuie să primească actualizări practic instantaneu.
  • Pentru simplificare, am acceptat că datele care sunt sincronizate cu clienții nu sunt șterse, ci sunt ascunse prin intermediul unor steaguri speciale.
  • Diverse cereri rare (cum ar fi statistici, echipe, statistici ale echipelor) sunt obținute prin interogări GET standard.
  • În plus, sistemul trebuia să reziste fără probleme la 100 de mii de utilizatori simultan.

Pe această bază, aveam două opțiuni de protocol:

  1. Websocket-uri. Dar nu aveam nevoie de canal de la client la server. Aveam nevoie doar să trimitem actualizări de la server la client, așa că websocket-ul era o variantă excesivă.
  2. Server-Sent Events (SSE) s-a dovedit a fi alegerea perfectă! Este suficient de simplu și satisface în principiu toate cerințele noastre.

Server-Sent Events

Câteva cuvinte despre cum funcționează această tehnologie...

Funcționează pe deasupra unei conexiuni http. Clientul trimite o interogare, iar serverul răspunde cu Content-Type: text/event-stream și nu închide conexiunea cu clientul, continuând să scrie date în conexiune:

Mihail Salosin. Golang Meetup. Utilizarea Go în backend-ul aplicației „Smatri+”

Datele pot fi trimise în formatul convenit cu clienții. În cazul nostru, trimiteam astfel: în câmpul event se trimitea numele structurii schimbate (persoană, jucător), iar în câmpul data - JSON cu noile câmpuri modificate pentru jucător.

Acum, să vedem cum funcționează interacțiunea.

  • Primul lucru pe care îl face clientul este să determine când a fost ultima sincronizare cu serviciul: se uită în baza sa de date locală și stabilește data ultimei modificări înregistrate.
  • Trimite o interogare cu această dată.
  • În răspuns, îi trimitem toate actualizările care au avut loc de la acea dată.
  • După aceasta, el stabilește o conexiune cu canalul live și nu îl închide atâta timp cât are nevoie de aceste actualizări:

Mihail Salosin. Golang Meetup. Utilizarea Go în backend-ul aplicației „Smatri+”

Îi trimitem o listă de modificări: dacă cineva a marcat un gol – modificăm scorul, dacă s-a accidentat – aceasta este, de asemenea, trimisă în timp real. Astfel, în fluxul de evenimente al meciului, clienții primesc instantaneu datele actualizate. Perioadele de timp sunt trimise din 15 în 15 secunde pentru a se asigura că clientul știe că serverul este activ și nu s-a întâmplat nimic, astfel încât să știe că nu trebuie să se reconecteze.

Cum este gestionată conexiunea live?

  • În primul rând, creăm un canal în care vor veni actualizările cu un buffer.
  • După aceea, ne abonăm la acest canal pentru a primi actualizări.
  • Stabilim antetul corect, astfel încât clientul să știe că totul este în regulă.
  • Trimitem primul ping. Pur și simplu înregistrăm timestamp-ul curent al conexiunii.
  • După aceasta, citim din canal într-un ciclu, până când canalul de actualizări este închis. În canal vin periodic fie timestamp-ul curent, fie modificările pe care deja le înregistrăm în conexiunile deschise.

Mihail Salosin. Golang Meetup. Utilizarea Go în backend-ul aplicației „Smatri+”

Prima problemă cu care ne-am confruntat a fost următoarea: pentru fiecare conexiune deschisă cu clientul, creăm un timer care ticăie la fiecare 15 secunde - astfel, dacă aveam deschise 6.000 de conexiuni cu o singură mașină (cu un singur server API), se creau 6.000 de timere. Aceasta a făcut ca mașina să nu suporte sarcina necesară. Problema nu a fost atât de evidentă pentru noi, dar ne-a ajutat puțin și am rezolvat-o.

În final, acum ping-ul vine din același canal din care vine actualizarea.

Prin urmare, există un singur timer, care ticăie la fiecare 15 secunde.

Aici sunt câteva funcții auxiliare - trimiterea antetului, ping-ului și structurii în sine. Astfel, se transmite numele tabelului (person, match, season) și informațiile despre acest record:

Mihail Salosin. Golang Meetup. Utilizarea Go în backend-ul aplicației „Smatri+”

Mecanismul de trimitere a actualizărilor

Acum, puțin despre sursele modificărilor. Avem câțiva oameni, editori, care monitorizează transmisia în timp real. Ei creează toate evenimentele: cineva a fost eliminat, cineva s-a accidentat, o înlocuire…

Prin intermediul CMS, datele ajung în baza de date. După aceea, baza, prin mecanismul Listen/Notify, notifică serverele API despre acest lucru. Serverele API, la rândul lor, transmit aceste informații clienților. Astfel, practic avem conectate la baza de date doar câteva servere și nu există o sarcină deosebită pe bază, deoarece clientul nu interacționează direct cu baza:

Mihail Salosin. Golang Meetup. Utilizarea Go în backend-ul aplicației „Smatri+”

PostgreSQL: Listen/Notify

Mecanismul Listen/Notify din PostgreSQL permite notificarea abonaților despre evenimentele în care s-a schimbat un anumit eveniment - a fost creat un anumit record în baza de date. Pentru aceasta, am scris un trigger simplu și o funcție:

Mihail Salosin. Golang Meetup. Utilizarea Go în backend-ul aplicației „Smatri+”

La inserarea sau modificarea unui record, apelăm funcția notify pe canalul data_updates, transmitând numele tabelului și identificatorul recordului care a fost modificat sau inserat.

Pentru toate tabelele care trebuie sincronizate cu clientul, definim un trigger care, după modificarea/actualizarea unui record, apelează funcția menționată pe diapozitivul de mai jos.
Cum se abonează API-ul la aceste modificări?

Se creează un mecanism Fanout – acesta trimite mesaje clienților. Acesta colectează toate canalele clienților și trimite actualizările pe care le-a primit prin aceste canale:

Mihail Salosin. Golang Meetup. Utilizarea Go în backend-ul aplicației „Smatri+”

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

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

Cum funcționează Fan-out?

În română, acest termen se traduce prin 'divizor'. Avem un obiect care înregistrează abonații care doresc să primească actualizări. Și de îndată ce o actualizare pentru acest obiect sosește, acesta împrăștie actualizarea tuturor abonaților săi. Este destul de simplu:

Mihail Salosin. Golang Meetup. Utilizarea Go în backend-ul aplicației „Smatri+”

Cum este implementat în Go:

Mihail Salosin. Golang Meetup. Utilizarea Go în backend-ul aplicației „Smatri+”

Există o structură, care se sincronizează cu ajutorul Mutex-urilor. Are un câmp care păstrează starea conexiunii Fanout la baza de date, adică în prezent ascultă și va primi actualizări, precum și o listă a tuturor canalelor disponibile – o mapare, a cărei cheie este canalul și structura în formă de valori (de fapt, aceasta nu este utilizată în niciun fel).

Două metode – Connected și Disconnected – permit Fanout-ului să spună că avem o conexiune cu baza de date, că aceasta a fost stabilită și că conexiunea cu baza de date a fost întreruptă. În cazul al doilea, trebuie să deconectăm toți clienții și să le comunicăm că nu mai pot asculta nimic și că trebuie să se reconecteze, deoarece conexiunea cu ei s-a încheiat.

De asemenea, există o metodă Subscribe, care adaugă canalul la 'ascultători':

Mihail Salosin. Golang Meetup. Utilizarea Go în backend-ul aplicației „Smatri+”

Există o metodă Unsubscribe, care elimină canalul din cei care ascultă, dacă clientul s-a deconectat, precum și o metodă Publish, care permite trimiterea unui mesaj tuturor abonaților.

Întrebare: – Ce se transmite prin acest canal?

MC: – Se transmite modelul care s-a modificat sau un ping (de fapt, doar un număr, integer).

MC: – Poți trimite orice, orice structură, publica – aceasta se transformă pur și simplu în JSON și atât.

MC: – Primim o notificare de la „Postgres” – aceasta conține numele tabelului și identificatorul. Pe baza numelui tabelului, obținem înregistrarea dorită și o trimitem spre publicare.

Infrastructură

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

Mihail Salosin. Golang Meetup. Utilizarea Go în backend-ul aplicației „Smatri+”

Avem două front-enduri, pe care este instalat Keepalived pentru a spori disponibilitatea, astfel încât, în caz de nevoie, un frontend să îl poată înlocui pe celălalt. De asemenea, două copii ale CMS-ului.

De asemenea, există un importator de statistici. Există DB Slave, de pe care se fac periodic backup-uri. Există Pigeon Pusher – aplicația care trimite notificări clienților, precum și instrumente de infrastructură: Zabbix, Graylog2 și Chef.

De fapt, această infrastructură este redundantă, deoarece 100 de mii se pot deservi și cu un număr mai mic de servere. Dar având hardware-ul – l-am folosit (ni s-a spus că se poate – de ce nu?).

Avantajele Go

După ce am lucrat la această aplicație, s-au evidențiat câteva avantaje evidente ale Go.

  • O bibliotecă http excelentă. Cu ajutorul ei, poți crea destul de multe deja „din cutie”.
  • În plus, canalele care ne-au permis să implementăm foarte ușor mecanismul de trimitere a notificărilor clienților.
  • Un instrument minunat, detectorul de condiții de competiție, ne-a permis să eliminăm câteva bug-uri critice (infrastructura de staging). Tot ce rulează pe staging a fost lansat, compilat cu cheia Race; și astfel, putem observa pe infrastructura de staging care sunt problemele noastre potențiale.
  • Minimalismul și simplitatea limbajului.

Mihail Salosin. Golang Meetup. Utilizarea Go în backend-ul aplicației „Smatri+”

Căutăm dezvoltatori! Dacă cineva este interesat – vă rog.

Întrebări

Întrebare din audiență (U): – Mi se pare că ați omis un aspect important referitor la Fan-out. Înțeleg corect că atunci când trimiteți un răspuns clientului, vă blocați dacă clientul nu dorește să citească?

MC: – Nu, nu ne blocăm. În primul rând, totul este gestionat prin nginx, așa că nu avem probleme cu clienții lenți. În al doilea rând, clientul are un canal cu buffer – practic, putem depozita acolo până la o sută de actualizări... Dacă nu putem scrie în canal, atunci acesta se șterge. Dacă observăm că canalul s-a blocat, pur și simplu închidem canalul și totul – clientul se reconectează dacă apare vreo problemă. Așadar, practic, nu apar blocaje aici.

M: – Nu ar fi fost posibil să trimitem direct o înregistrare în Listen/Notify, și nu o tabelă-identificator?

MC: – Există o limitare în Listen/Notify de 8000 de byte pentru preload-ul pe care îl trimite. Practic, ar putea fi trimis, dacă am avea de-a face cu o cantitate mică de date, dar cred că așa [cum facem noi] este pur și simplu mai sigur. Limitările sunt în „Postgres” în sine.

M: – Primește clienții actualizări despre meciuri care nu îi interesează?

MC: – În general, da. De obicei, sunt 2-3 meciuri care se desfășoară simultan, iar asta este destul de rar. Dacă clientul urmărește ceva, de obicei urmărește meciul care se desfășoară. Apoi, clientul are o bază de date locală, în care se acumulează toate aceste actualizări, și chiar și fără conexiune la internet, clientul poate viziona toate meciurile trecute pentru care are actualizări. Practic, ne sincronizăm baza de date de pe server cu baza de date locală a clientului, astfel încât acesta să poată lucra și offline.

M: – De ce ați realizat propriul ORM?

Alexey (unul dintre dezvoltatorii „Svetri+”): – La acel moment (acum un an) erau mai puține ORM-uri decât acum, când există destul de multe. Din majoritatea ORM-urilor existente, ceea ce nu-mi place cel mai mult este că majoritatea funcționează cu interfețe goale. Adică metodele din aceste ORM-uri sunt pregătite să accepte orice: structură, pointer la structură, număr, ceva complet necorespunzător...

ORM-ul nostru generează structuri pe baza modelului de date. Singur. De aceea, toate metodele sunt specifice, nu folosesc reflecție etc. Acestea acceptă structuri și se așteaptă să utilizeze acele structuri care sosesc.

M: – Câți oameni au participat?

MC: – La etapa inițială au participat două persoane. Undeva în iunie am început, în august majoritatea a fost gata (prima versiune). În septembrie a fost lansarea.

M: – Acolo unde descrieți SSE, nu folosiți timeout. De ce așa?

MC: – Dacă vorbim sincer, SSE este într-adevăr un protocol html5: standardul SSE este destinat comunicării cu browserele, cât am înțeles. Are funcții suplimentare pentru ca browserele să se reconecteze (și altele), dar nu le avem nevoie, deoarece am avut clienți care puteau implementa orice logică de conectare și obținere a informațiilor. Am realizat mai degrabă nu SSE, ci ceva similar cu SSE. Acesta nu este protocolul în sine.
Nu a fost nevoie. Cât am înțeles, clienții au implementat mecanismul de conectare practic de la zero. Le-a fost în principiu indiferent.

M: – Ce unelte suplimentare ați folosit?

MC: – Cel mai activ am folosit govet și golint, pentru a avea un stil uniform, precum și gofmt. Nu am folosit nimic altceva.

M: – Cu ce ați realizat depanarea?

MC: – Depanarea, în mare parte, s-a realizat prin teste. Nu am folosit niciun debugger, nu am folosit GOP.

M: – Puteți întoarce slide-ul în care este implementată funcția Publish? Nu vă deranjează numele de variabile formate dintr-o literă?

MC: – Nu. Ele au un domeniu de vizibilitate destul de „îngust”. Nu sunt folosite nicăieri altundeva, în afară de aici (în interiorul acestei clase), și este foarte compact – ocupă doar 7 linii.

M: – Totuși, nu este foarte intuitiv...

MC: – Nu-nu, acesta este un cod real! Nu este vorba despre stil. Pur și simplu este o clasă atât de utilitară, foarte mică – are doar 3 câmpuri în interiorul clasei...

Mihail Salosin. Golang Meetup. Utilizarea Go în backend-ul aplicației „Smatri+”

MC: – În principiu, toate acele date care sunt sincronizate cu clienții (meciuri de sezon, jucători) nu se schimbă. Pe scurt, dacă vom crea o altă disciplină sportivă în care va trebui să modificăm meciurile, ne vom lua în seamă toate acestea în noua versiune a clientului, iar versiunile vechi ale clientului vor fi blocate.

M: – Există pachete externe pentru gestionarea dependențelor?

MC: – Am folosit go dep.

M: – În tema prezentării era ceva despre video, dar în prezentare nu este nimic despre video.

MC: – Nu, în tema mea despre video nu am nimic. Se numește „Sunt+” – așa se numește aplicația.

M: – Ați spus că se face streaming către clienți?..

MC: – Nu ne-am ocupat de video streaming. Acesta era realizat complet de „Megafon”. Da, nu am menționat că aplicația este a Megafon.

MC: – Go – pentru trimiterea tuturor datelor – referitor la factură, la evenimentele meciului, statistici… Go – este complet backend-ul aplicației. Clientul trebuie să afle de unde să utilizeze linkul pentru player, astfel încât utilizatorul să poată viziona meciul. Noi avem linkuri pentru videoclipuri și pentru transmisiuni, care sunt pregătite.

Redați video

Puțin publicitate 🙂

Mulțumim că rămâneți cu noi. Vă plac articolele noastre? Doriți să vedeți mai multe materiale interesante? Susțineți-ne, efectuând o comandă sau recomandându-ne prietenilor, VPS cloud pentru dezvoltatori de la 4,99 $, un echivalent unic pentru serverele entry-level, care a fost creat de noi pentru voi: Toată adevărul despre VPS (KVM) E5-2697 v3 (6 nuclee) 10GB DDR4 480GB SSD 1Gbps de la 19 $ sau cum să împărțiți corect un server? (sunt disponibile opțiuni cu RAID1 și RAID10, până la 24 nuclee și până la 40GB DDR4).

Dell R730xd la jumătate de preț în centrul de date Equinix Tier IV din Amsterdam? Numai la noi 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TB de la 199 $ în Olanda! Dell R420 — 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — de la 99 $! Citiți despre Cum să construiți o infrastructură de clasă enterprise folosind servere Dell R730xd E5-2650 v4 la prețuri foarte mici de 9000 €?

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster