Mikhail Salosin (in seguitu - MS): - Salute à tutti ! Mi chjamu Michael. U travagliu cum'è sviluppatore di backend in MC2 Software, è parleraghju di utilizà Go in u backend di l'applicazione mobile Look+.
Qualchissia quì piace u hockey?
Allora sta applicazione hè per voi. Hè per Android è iOS è hè adupratu per fighjà trasmissioni di diversi avvenimenti sportivi in linea è arregistrati. L'applicazione cuntene ancu diverse statistiche, trasmissioni di testu, tavule per cunferenze, tornei è altre informazioni utili per i fan.
Ancu in l'applicazione ci hè una cosa cum'è i mumenti video, vale à dì chì pudete fighjà i mumenti più impurtanti di partite (ubiettivi, cumbattimenti, shootouts, etc.). Se ùn vulete micca vede tutta a trasmissione, pudete fighjà solu i più interessanti.
Chì avete usatu in u sviluppu?
A parte principale hè stata scritta in Go. L'API chì i clienti mobili anu cumunicatu cù hè stata scritta in Go. Un serviziu per mandà notificazioni push à i telefuni mobili hè statu ancu scrittu in Go. Avemu avutu ancu à scrive u nostru propiu ORM, chì pudemu parlà un ghjornu. Ebbè, alcuni picculi servizii sò stati scritti in Go: ridimensionà è carica l'imaghjini per l'editori...
Avemu usatu PostgreSQL cum'è a basa di dati. L'interfaccia di l'editore hè stata scritta in Ruby on Rails cù a gemma ActiveAdmin. L'importazione di statistiche da un fornitore di statistiche hè ancu scrittu in Ruby.
Per i testi di l'API di u sistema, avemu usatu Python unittest. Memcached hè adupratu per accelerà e chjama di pagamentu API, "Chef" hè utilizatu per cuntrullà a cunfigurazione, Zabbix hè utilizatu per cullà è monitorà e statistiche di u sistema internu. Graylog2 hè per a cullizzioni di logs, Slate hè a documentazione API per i clienti.
Selezzione di u protocolu
U primu prublema chì avemu scontru: avemu bisognu di sceglie un protokollu per l'interazzione trà u backend è i clienti mobili, basatu annantu à i seguenti punti...
- U requisitu più impurtante: i dati nantu à i clienti devenu esse aghjurnati in tempu reale. Vale à dì, tutti quelli chì stanu à vede a trasmissione duveranu riceve l'aghjurnamenti quasi istantaneamente.
- Per simplificà e cose, assumemu chì e dati chì sò sincronizati cù i clienti ùn sò micca sguassati, ma sò oculati cù bandieri speciali.
- Ogni tipu di richieste rare (cum'è statistiche, cumpusizioni di squadra, statistiche di squadra) sò ottenute da richieste GET ordinarie.
- In più, u sistema avia da sustene facilmente 100 mila utilizatori à u stessu tempu.
Basatu nantu à questu, avemu avutu duie opzioni di protokollu:
- Websockets. Ma ùn avemu micca bisognu di canali da u cliente à u servitore. Avemu bisognu solu di mandà l'aghjurnamenti da u servitore à u cliente, cusì un websocket hè una opzione redundante.
- L'avvenimenti mandati da u servitore (SSE) sò venuti ghjustu! Hè abbastanza simplice è basamente satisface tuttu ciò chì avemu bisognu.
Avvenimenti mandati da u servitore
Uni pochi parolle nantu à cumu funziona sta cosa...
Funziona sopra à una cunnessione http. U cliente manda una dumanda, u servitore risponde cù Content-Type: text/event-stream è ùn chjude micca a cunnessione cù u cliente, ma cuntinueghja à scrive dati à a cunnessione:
I dati ponu esse mandati in un furmatu accunsentutu cù i clienti. In u nostru casu, l'avemu mandatu in questa forma: u nome di a struttura cambiata (persona, ghjucadore) hè stata mandata à u campu di l'avvenimentu, è JSON cù novi campi cambiati per u ghjucatore hè statu mandatu à u campu di dati.
Avà parlemu di cumu funziona l'interazzione stessu.
- A prima cosa chì u cliente fa hè determinà l'ultima volta chì a sincronizazione cù u serviziu hè stata realizata: guarda a so basa di dati lucale è determina a data di l'ultimu cambiamentu registratu da ellu.
- Manda una dumanda cù questa data.
- In risposta, li mandemu tutte l'aghjurnamenti chì sò accaduti da quella data.
- Dopu questu, faci una cunnessione à u canali in diretta è ùn si chjude micca finu à chì hà bisognu di sti aghjurnamenti:
Mandemu una lista di cambiamenti: se qualcunu marca un gol, cambiamu u puntuatu di u partitu, s'ellu si ferite, questu hè ancu mandatu in tempu reale. Cusì, i clienti ricevenu istantaneamente dati aghjurnati in u feed di l'avvenimentu di partita. Periòdicamente, per chì u cliente capisce chì u servitore ùn hè micca mortu, chì nunda ùn hè accadutu, mandemu un timestamp ogni 15 seconde - per sapè chì tuttu hè in ordine è ùn ci hè bisognu di ricunnisce.
Cumu hè servita a cunnessione in diretta?
- Prima di tuttu, creemu un canale in quale l'aghjurnamenti buffered seranu ricevuti.
- Dopu quì, sottumettemu stu canale per riceve l'aghjurnamenti.
- Avemu stabilitu l'intestazione curretta per chì u cliente sà chì tuttu hè bè.
- Mandate u primu ping. Avemu simpricimenti registrà u timestamp di cunnessione attuale.
- Dopu quì, leghjemu da u canali in un ciclu finu à chì u canali di l'aghjurnamentu hè chjusu. U canali riceve periodicamente sia u timestamp attuale o cambiamenti chì avemu digià scrittu per apre e cunnessione.
U primu prublema chì avemu scontru era u seguitu: per ogni cunnessione aperta cù u cliente, avemu creatu un cronometru chì marcava una volta ogni 15 seconde - risulta chì se avemu avutu 6 mila cunnessione aperte cù una macchina (cù un servitore API), 6. mille timers sò stati creati. Questu hà purtatu à a macchina chì ùn mantene micca a carica necessaria. U prublema ùn era micca cusì evidente per noi, ma avemu un pocu aiutu è l'avemu riparatu.
In u risultatu, avà u nostru ping vene da u listessu canale da quale vene l'aghjurnamentu.
Dunque, ci hè solu un timer chì tick una volta ogni 15 seconde.
Ci sò parechje funzioni ausiliarii quì - mandà l'intestazione, ping è a struttura stessa. Questu hè, u nome di a tavula (persona, partita, stagione) è l'infurmazioni nantu à sta entrata sò trasmessi quì:
Meccanismu per mandà l'aghjurnamenti
Avà un pocu di induve venenu i cambiamenti. Avemu parechje persone, editori, chì fighjanu a trasmissione in tempu reale. Creanu tutti l'avvenimenti: qualcunu hè statu mandatu, qualcunu hè statu feritu, qualchì tipu di rimpiazzamentu...
Utilizendu un CMS, a dati entra in a basa di dati. Dopu questu, a basa di dati notifica à i servitori API nantu à questu utilizendu u mecanismu Listen / Notify. I servitori API mandanu digià sta informazione à i clienti. Cusì, avemu essenzialmente solu uni pochi servitori cunnessi à a basa di dati è ùn ci hè micca una carica speciale nantu à a basa di dati, perchè u cliente ùn interagisce micca direttamente cù a basa di dati in ogni modu:
PostgreSQL: Listen / Notify
U mecanismu Listen / Notify in Postgres permette di avvisà l'abbonati di l'avvenimentu chì qualchì avvenimentu hà cambiatu - qualchì record hè statu creatu in a basa di dati. Per fà questu, avemu scrittu un attivatore simplice è funzione:
Quandu si inserisce o cambia un registru, chjamemu a funzione di notificazione nantu à u canali data_updates, passendu quì u nome di a tavula è l'identificatore di u registru chì hè statu cambiatu o inseritu.
Per tutti i tavulini chì deve esse sincronizzati cù u cliente, definiscemu un trigger, chì, dopu avè cambiatu / aghjurnatu un registru, chjama a funzione indicata nantu à a slide sottu.
Cumu l'API sottumette à sti cambiamenti?
Un mecanismu Fanout hè creatu - manda missaghji à u cliente. Raccoglie tutti i canali di i clienti è manda l'aghjurnamenti ricevuti attraversu questi canali:
Quì a libreria pq standard, chì cunnetta à a basa di dati è dice chì vole à sente à u canali (data_updates), verifica chì a cunnessione hè aperta è tuttu hè bè. Omettu a verificazione d'errore per risparmià spaziu (ùn cuntrollà hè periculosu).
Dopu, avemu stabilitu in modu asincronu Ticker, chì mandarà un ping ogni 15 seconde, è cumincianu à sente u canali chì avemu abbonatu. Se ricevemu un ping, publichemu stu ping. Se ricevemu qualchì tipu di entrata, allora publichemu sta entrata à tutti l'abbonati di questu Fanout.
Cumu funziona Fan-out?
In russu questu si traduce cum'è "splitter". Avemu un ughjettu chì registra l'abbonati chì volenu riceve alcune aghjurnamenti. È appena una aghjurnazione ghjunghje à questu ughjettu, distribuisce sta aghjurnazione à tutti i so abbonati. Semplice abbastanza:
Cumu hè implementatu in Go:
Ci hè una struttura, hè sincronizata cù Mutexes. Hà un campu chì salva u statu di a cunnessione di Fanout à a basa di dati, vale à dì chì hè attualmente à sente è riceverà l'aghjurnamenti, è ancu una lista di tutti i canali dispunibili - mappa, a chjave di quale hè u canali è struct in forma di valori (essenzialmente ùn hè micca usatu in ogni modu).
Dui metudi - Connected and Disconnected - permettenu di dì à Fanout chì avemu una cunnessione à a basa, hè apparsu è chì a cunnessione à a basa hè stata rotta. In u sicondu casu, avete bisognu di disconnect tutti i clienti è dite à elli chì ùn ponu più sente à nunda è ch'elli ricunniscenu perchè a cunnessione cù elli hè chjusa.
Ci hè ancu un metudu Subscribe chì aghjunghje u canali à i "ascultori":
Ci hè un metudu Unsubscribe, chì sguassate u canali da l'ascultori se u cliente disconnects, è ancu un metudu Publish, chì permette di mandà un missaghju à tutti l'abbonati.
Quistione: – Chì hè trasmessu attraversu stu canali ?
MS: - U mudellu chì hà cambiatu o ping hè trasmessu (essenzialmente solu un numeru, integer).
MS: - Pudete mandà qualcosa, mandà qualsiasi struttura, pubblicà - si trasforma solu in JSON è questu hè.
MS: - Ricevemu una notificazione da Postgres - cuntene u nome di a tavola è l'identificatore. Basatu nantu à u nome di a tavula è l'identificatore, avemu u registru chì avemu bisognu, è dopu mandemu sta struttura per a publicazione.
Infrastruttura
Cosa hè questu da una perspettiva di l'infrastruttura? Avemu 7 servitori di hardware: unu di elli hè cumpletamente dedicatu à a basa di dati, l'altri sei run machine virtuale. Ci sò 6 copie di l'API: ogni macchina virtuale cù l'API corre nantu à un servitore hardware separatu - questu hè per affidabilità.
Avemu dui frontends cù Keepalived installatu per migliurà l'accessibilità, perchè se qualcosa succede, un frontend pò rimpiazzà l'altru. Inoltre - duie copie di u CMS.
Ci hè ancu un importatore di statistiche. Ci hè un DB Slave da quale i backups sò fatti periodicamente. Ci hè Pigeon Pusher, una applicazione chì manda notifiche push à i clienti, è ancu cose di infrastruttura: Zabbix, Graylog2 è Chef.
In fatti, sta infrastruttura hè redundante, perchè 100 mila ponu esse servuti cù menu servitori. Ma ci era u ferru - l'avemu usatu (ci anu dettu chì era pussibule - perchè micca).
Pro di Go
Dopu avè travagliatu nantu à sta applicazione, emergenu tali vantaghji evidenti di Go.
- Cool http biblioteca. Cù ellu pudete creà assai assai fora di a scatula.
- Inoltre, i canali chì ci permettenu di implementà assai facilmente un mecanismu per mandà notificazioni à i clienti.
- A cosa maravigliosa Race detector ci hà permessu di eliminà parechji bug critichi (infrastruttura di staging). Tuttu ciò chì travaglia nantu à staging hè lanciatu, cumpilatu cù a chjave Race; è noi, in cunsiquenza, pudemu guardà l'infrastruttura di staging per vede quale prublemi potenziali avemu.
- Minimalismu è simplicità di lingua.
Cerchemu sviluppatori! Sè qualchissia vole, per piacè.
I vostri dumanni
Quistione da l'audienza (in seguitu - B): - Mi pare chì avete mancatu un puntu impurtante in quantu à Fan-out. Sò currettu à capisce chì quandu mandate una risposta à un cliente, bluccà se u cliente ùn vole micca leghje?
MS: - Innò, ùn simu micca bluccatu. Prima, avemu tuttu questu daretu à nginx, vale à dì, ùn ci sò micca prublemi cù i clienti lenti. Siconda, u cliente hà un canale cù un buffer - in fattu, pudemu mette finu à un centu d'aghjurnamenti quì ... Se ùn pudemu micca scrive à u canali, allora l'elimina. Se vedemu chì u canali hè bluccatu, allora solu chjuderemu u canali, è questu hè - u cliente si ricunniscerà s'ellu ci hè un prublema. Dunque, in principiu, ùn ci hè micca bluccatu quì.
IN: - Ùn puderia micca esse pussibule di mandà immediatamente un registru à Listen / Notify, è micca una tabella d'identificatore?
MS: - Listen / Notify hà un limitu di 8 mila byte nantu à a precarica chì manda. In principiu, saria pussìbule di mandà s'è avemu trattatu cù una piccula quantità di dati, ma mi pari chì sta manera [a manera di fà] hè simplicemente più affidabile. E limitazioni sò in Postgres stessu.
IN: - I clienti ricevenu l'aghjurnamenti nantu à i partiti chì ùn anu micca interessatu?
MS: - In generale, sì. In regula, ci sò 2-3 partiti chì passanu in parallelu, è ancu cusì raramenti. Se un cliente fighja qualcosa, di solitu guarda u match chì si passa. Allora, u cliente hà una basa di dati lucale in quale tutte queste aghjurnamenti sò aghjuntu, è ancu senza una cunnessione Internet, u cliente pò vede tutti i partiti passati per quale hà aghjurnamenti. Essenzialmente, sincronizemu a nostra basa di dati nantu à u servitore cù a basa di dati lucale di u cliente per ch'ellu pò travaglià offline.
IN: – Perchè avete fattu u vostru propiu ORM ?
Alexey (unu di i sviluppatori di Look+): – À quellu tempu (era un annu fà) ci era menu ORM chè avà, quandu ci sò assai. A mo cosa preferita di a maiò parte di l'ORM hè chì a maiò parte di elli funzionanu in interfacce vacanti. Vale à dì, i metudi in questi ORM sò pronti per piglià qualcosa: una struttura, un punteru di struttura, un numeru, qualcosa completamente irrilevante ...
U nostru ORM genera strutture basate nantu à u mudellu di dati. Me stessu. È dunque tutti i metudi sò cuncreti, ùn aduprate micca a riflessione, etc. Acceptanu strutture è aspettanu di utilizà quelli strutture chì venenu.
IN: – Quanta ghjente hà participatu ?
MS: – In u stadiu iniziale, duie persone anu participatu. Avemu principiatu in un locu in u ghjugnu, è in l'aostu a parte principale era pronta (a prima versione). Ci hè stata una liberazione in settembre.
IN: - Induve discrive SSE, ùn utilizate micca timeout. Perchè hè questu?
MS: - Per esse onestu, SSE hè sempre un protokollu html5: u standard SSE hè pensatu per cumunicà cù i navigatori, finu à chì aghju capitu. Hà funzioni supplementari per chì i navigatori ponu ricunnisce (è cusì), ma ùn avemu micca bisognu, perchè avemu avutu i clienti chì puderanu implementà ogni logica per cunnette è riceve infurmazioni. Ùn avemu micca fattu SSE, ma piuttostu qualcosa simili à SSE. Questu ùn hè micca u protocolu stessu.
Ùn ci era micca bisognu. In quantu capiscu, i clienti implementanu u mecanismu di cunnessione quasi da zero. Ùn li importava micca veramente.
IN: - Chì utilità supplementari avete usatu?
MS: - Avemu più attivamente utilizatu govet è golint per unificà u stile, è ancu gofmt. Nunda altru hè stata utilizata.
IN: – Chì avete usatu per debug ?
MS: - A debugging hè stata largamente realizata cù e teste. Ùn avemu micca usatu debugger o GOP.
IN: - Pudete rinvià a diapositiva induve a funzione Publish hè implementata? I nomi di variabili di una sola lettera ti confondenu?
MS: - Innò. Hanu un scopu di visibilità abbastanza "strettu". Ùn sò micca usati in altre locu, salvu quì (eccettu per l'internu di sta classa), è hè assai compactu - hè solu 7 linee.
IN: - In qualchì manera ùn hè ancu intuitivu ...
MS: - Innò, nò, hè un veru codice ! Ùn si tratta micca di stile. Hè solu una classe utilitaria, assai chjuca - solu 3 campi in a classe ...
MS: - In generale, tutti i dati chì sò sincronizati cù i clienti (partite di stagione, ghjucatori) ùn cambianu micca. À pocu pressu, se facemu un altru sportu in u quale avemu bisognu di cambià a partita, avemu da piglià solu tuttu in contu in a nova versione di u cliente, è i vechji versioni di u cliente seranu pruibiti.
IN: - Ci hè qualchì pacchettu di gestione di a dependenza di terzu?
MS: – Avemu usatu go dep.
IN: - Ci era qualcosa di video in u tema di u rapportu, ma ùn ci era nunda in u rapportu di video.
MS: - Innò, ùn aghju nunda in u tema nantu à u video. Hè chjamatu "Look +" - questu hè u nome di l'applicazione.
IN: - Avete dettu chì hè trasmessu à i clienti ?...
MS: - Ùn eramu micca implicati in streaming video. Questu hè statu interamente fattu da Megafon. Iè, ùn aghju micca dettu chì l'applicazione era MegaFon.
MS: - Vai - per mandà tutte e dati - nantu à u puntuatu, nantu à l'avvenimenti di partita, statistiche ... Go hè u backend tutale per l'applicazione. U cliente deve sapè da un locu quale ligame per aduprà per u ghjucatore per chì l'utilizatore pò fighjà u match. Avemu ligami per i video è i flussi chì sò stati preparati.
Certi annunzii 🙂
Grazie per stà cun noi. Ti piace i nostri articuli ? Vulete vede più cuntenutu interessante? Supportaci facendu un ordine o ricumandendu à l'amichi,
Dell R730xd 2 volte più prezzu in u centru di dati Equinix Tier IV in Amsterdam? Solu quì
Source: www.habr.com