Mikhail Salosin. Golang Meetup. Utilizzo di Go nel backend dell'applicazione “Guarda+”

Mikhail Salosin (di seguito – MS): – Ciao a tutti! Mi chiamo Mikhail. Lavoro come sviluppatore backend presso MC2 Software e parlerò dell'uso di Go nel backend dell'app mobile «Smotri+».

Mikhail Salosin. Golang Meetup. Utilizzo di Go nel backend dell'applicazione “Guarda+”

Qualcuno tra i presenti ama l'hockey?

Mikhail Salosin. Golang Meetup. Utilizzo di Go nel backend dell'applicazione “Guarda+”

Allora quest'app è per voi. È disponibile per Android e iOS e serve per guardare le trasmissioni di eventi sportivi, sia in diretta che in registrazione. Inoltre, l'app offre diverse statistiche, cronache testuali, classifiche per conferenze, tornei e altre informazioni utili per i tifosi.

Mikhail Salosin. Golang Meetup. Utilizzo di Go nel backend dell'applicazione “Guarda+”

C'è anche una funzione chiamata video momenti, ovvero si possono vedere i momenti salienti delle partite (gol, risse, rigori, ecc.). Se non vuoi guardare l'intera trasmissione, puoi vedere solo il meglio.

Cosa è stato utilizzato nello sviluppo?

La maggior parte è stata scritta in Go. L'API con cui comunicavano i client mobili è stata sviluppata in Go. Inoltre, è stato creato un servizio in Go per inviare notifiche push ai dispositivi mobili. Ci siamo anche dovuti scrivere un ORM, di cui forse un giorno parleremo. Infine, sono stati scritti in Go alcuni piccoli servizi: ridimensionamento e caricamento delle immagini per la parte redazionale...

Come database, abbiamo utilizzato PostgreSQL. L'interfaccia per i redattori è stata sviluppata in Ruby on Rails utilizzando la gemma ActiveAdmin. In Ruby è stato implementato anche l'import delle statistiche dal fornitore di statistiche.

Per i test di sistema dell'API abbiamo utilizzato il unittest di Python. Memcached è utilizzato per il throttling delle chiamate all'API di pagamento, Chef per il controllo della configurazione, Zabbix per la raccolta e il monitoraggio delle statistiche interne del sistema. Graylog2 per la raccolta dei log, Slate è la documentazione API per i clienti.

Mikhail Salosin. Golang Meetup. Utilizzo di Go nel backend dell'applicazione “Guarda+”

Scelta del protocollo

Il primo problema che abbiamo affrontato: dovevamo scegliere il protocollo di interazione tra il backend e i clienti mobili, tenendo conto dei seguenti punti...

  • Il requisito principale: i dati sui dispositivi client devono aggiornarsi in tempo reale. Cioè, tutti coloro che attualmente guardano la trasmissione devono ricevere aggiornamenti praticamente in tempo reale.
  • Per semplificare, abbiamo stabilito che i dati sincronizzati con i client non vengono cancellati, ma nascosti tramite flag speciali.
  • Qualsiasi richiesta rara (come statistiche, composizioni della squadra, statistiche di squadra) viene effettuata tramite normali richieste GET.
  • Inoltre, il sistema doveva gestire tranquillamente 100.000 utenti contemporaneamente.

Da qui, avevamo due opzioni di protocollo:

  1. Websocket. Ma non avevamo bisogno di canali dal client al server. Era necessario solo inviare aggiornamenti dal server al client, quindi il websocket è un'opzione superflua.
  2. Server-Sent Events (SSE) è stato perfetto! È abbastanza semplice e soddisfa fondamentalmente tutto ciò di cui abbiamo bisogno.

Server-Sent Events

Un paio di parole su come funziona questa cosa…

Funziona sopra una connessione http. Il client invia una richiesta, a cui il server risponde con Content-Type: text/event-stream e non chiude la connessione con il client, continuando a inviare dati attraverso la connessione:

Mikhail Salosin. Golang Meetup. Utilizzo di Go nel backend dell'applicazione “Guarda+”

I dati possono essere inviati in un formato concordato con i client. Nel nostro caso, inviavamo in questo modo: nel campo event veniva inserito il nome della struttura modificata (persona, giocatore), e nel campo data – JSON con i nuovi campi modificati per il giocatore.

Ora parliamo di come avviene l'interazione.

  • Innanzitutto, il client stabilisce quando è stata l'ultima volta che è avvenuta la sincronizzazione con il servizio: controlla il proprio DB locale e determina la data dell'ultimo cambiamento registrato.
  • Invia una richiesta con questa data.
  • In risposta, inviamo tutte le aggiornamenti che sono avvenuti da quella data.
  • Dopo di che, stabilisce una connessione al canale live e non lo chiude fino a quando ha bisogno di questi aggiornamenti:

Mikhail Salosin. Golang Meetup. Utilizzo di Go nel backend dell'applicazione “Guarda+”

Gli inviamo un elenco di cambiamenti: se qualcuno segna un gol – aggiorniamo il punteggio della partita, se subisce un infortunio – questo viene inviato anche in tempo reale. In questo modo, nella cronologia degli eventi della partita, i client ricevono immediatamente dati aggiornati. Periodicamente, affinché il client capisca che il server non è morto, che non è successo niente, inviamo un timestamp ogni 15 secondi – per fargli sapere che va tutto bene e che non è necessario riconnettersi.

Come viene gestita la connessione live?

  • In primo luogo, creiamo un canale in cui verranno ricevuti aggiornamenti da un buffer.
  • Dopodiché, iscriviamo questo canale per ricevere aggiornamenti.
  • Impostiamo l'intestazione corretta, affinché il client sappia che va tutto bene.
  • Inviamo il primo ping. Registriamo semplicemente il timestamp attuale della connessione.
  • Dopo di ciò, leggiamo dal canale in un ciclo finché il canale degli aggiornamenti non viene chiuso. Nel canale arrivano periodicamente o il timestamp attuale, o le modifiche che stiamo già registrando nelle connessioni aperte.

Mikhail Salosin. Golang Meetup. Utilizzo di Go nel backend dell'applicazione “Guarda+”

Il primo problema che abbiamo affrontato è stato il seguente: per ogni connessione aperta con il cliente creavamo un timer che scattava ogni 15 secondi - di conseguenza, se avevamo 6000 connessioni aperte con una macchina (con un server API), venivano creati 6000 timer. Questo portava a una mancanza di carico necessario sulla macchina. Il problema non era così ovvio per noi, ma abbiamo ricevuto un po' di aiuto e lo abbiamo risolto.

Ora l'ping proviene dallo stesso canale da cui arriva l'aggiornamento.

Pertanto, c'è solo un timer che scatta ogni 15 secondi.

Qui ci sono alcune funzioni ausiliarie - invio dell'intestazione, ping e della struttura stessa. Cioè, qui si trasmette il nome della tabella (persona, partita, stagione) e le stesse informazioni su questo record:

Mikhail Salosin. Golang Meetup. Utilizzo di Go nel backend dell'applicazione “Guarda+”

Meccanismo di invio degli aggiornamenti

Ora parliamo un po' di dove provengono le modifiche. Abbiamo alcune persone, editor, che guardano la trasmissione in tempo reale. Creano tutti gli eventi: qualcuno è stato espulso, qualcuno si è infortunato, c'è stata una sostituzione…

Grazie al CMS, i dati vengono inseriti nel database. Dopo di che, il database informa i server API tramite il meccanismo Listen/Notify. I server API poi inviano queste informazioni ai clienti. In questo modo, abbiamo fondamentalmente solo alcuni server collegati al database e nessun carico particolare sul database, poiché i clienti non interagiscono direttamente con il database:

Mikhail Salosin. Golang Meetup. Utilizzo di Go nel backend dell'applicazione “Guarda+”

PostgreSQL: Listen/Notify

Il meccanismo Listen/Notify in "PostgreSQL" consente di notificare agli iscritti agli eventi che è cambiato qualche evento - è stato creato qualche record nel database. Per questo, abbiamo scritto un semplice trigger e funzione:

Mikhail Salosin. Golang Meetup. Utilizzo di Go nel backend dell'applicazione “Guarda+”

Al momento dell'inserimento o della modifica di un record, chiamiamo la funzione notify sul canale data_updates, passando il nome della tabella e l'identificatore del record che è stato modificato o inserito.

Per tutte le tabelle che devono essere sincronizzate con il cliente, definiamo un trigger che, dopo la modifica/aggiornamento del record, richiama la funzione indicata nella slide in basso.
Come si iscrive l'API a queste modifiche?

Viene creato il meccanismo Fanout – che invia messaggi ai client. Raccoglie tutti i canali dei clienti e invia aggiornamenti che ha ricevuto tramite questi canali:

Mikhail Salosin. Golang Meetup. Utilizzo di Go nel backend dell'applicazione “Guarda+”

Qui c'è la libreria standard pq, che si collega al database e indica che desidera ascoltare il canale (data_updates), verifica che la connessione sia aperta e tutto funzioni correttamente. Salto il controllo degli errori per risparmiare spazio (non controllare è rischioso).

Successivamente, impostiamo in modo asincrono un Ticker, che invierà ping ogni 15 secondi, e iniziamo ad ascoltare il canale a cui ci siamo iscritti. Se riceviamo un ping, pubblichiamo questo ping. Se riceviamo qualche registrazione, pubblichiamo questa registrazione a tutti gli abbonati di questo Fanout.

Come funziona il Fan-out?

In russo è tradotto come "diramatore". Abbiamo un oggetto che registra gli abbonati che desiderano ricevere aggiornamenti. E non appena un aggiornamento arriva a questo oggetto, lo distribuisce a tutti gli abbonati esistenti. È abbastanza semplice:

Mikhail Salosin. Golang Meetup. Utilizzo di Go nel backend dell'applicazione “Guarda+”

Come è implementato in Go:

Mikhail Salosin. Golang Meetup. Utilizzo di Go nel backend dell'applicazione “Guarda+”

C'è una struttura, che si sincronizza tramite Mutex. Ha un campo che memorizza lo stato della connessione del Fanout al database, ovvero al momento sta ascoltando e riceverà aggiornamenti, così come un elenco di tutti i canali disponibili – map, la cui chiave è il canale e la struct con i valori (di fatto non viene utilizzata).

Due metodi – Connected e Disconnected – permettono di informare il Fanout che abbiamo una connessione con il database, che è stata stabilita, e che la connessione con il database è stata interrotta. Nel secondo caso, è necessario disconnettere tutti i client e comunicare loro che non possono più ascoltare nulla e che devono riconnettersi, poiché la connessione con loro è stata chiusa.

C'è anche un metodo Subscribe, che aggiunge un canale agli "ascoltatori":

Mikhail Salosin. Golang Meetup. Utilizzo di Go nel backend dell'applicazione “Guarda+”

C'è un metodo Unsubscribe, che rimuove un canale dagli ascoltatori se il client si è disconnesso, così come un metodo Publish, che consente di inviare un messaggio a tutti gli abbonati.

Domanda: – Cosa viene trasmesso su questo canale?

MS: – Viene trasmesso il modello che è stato modificato o un ping (in sostanza solo un numero, intero).

MS: – Puoi inviare qualsiasi cosa, qualsiasi struttura, pubblicarla – si trasforma semplicemente in JSON e basta.

MS: – Riceviamo una notifica da «Postgres» – contiene il nome della tabella e l'identificativo. Dal nome della tabella e dall'identificativo otteniamo il record di cui abbiamo bisogno, e questa struttura la inviamo per la pubblicazione.

Infrastruttura

Come si presenta dal punto di vista dell'infrastruttura? Abbiamo 7 server fisici: uno di essi è completamente dedicato al database, sugli altri sei girano delle macchine virtuali. Ci sono 6 copie dell'API: ogni macchina virtuale con API gira su un server fisico separato – questo per garantirne l'affidabilità.

Mikhail Salosin. Golang Meetup. Utilizzo di Go nel backend dell'applicazione “Guarda+”

Abbiamo due front-end, sui quali è installato Keepalived per migliorare la disponibilità, in modo che, in caso di necessità, un front-end possa sostituire l'altro. Inoltre, abbiamo due copie del CMS.

C'è anche un importatore per la statistica. Abbiamo un DB Slave, da cui vengono periodicamente eseguiti dei backup. C'è Pigeon Pusher – l'applicazione che invia le notifiche push ai clienti, oltre ad alcuni strumenti infrastrutturali: Zabbix, Graylog2 e Chef.

In realtà, questa infrastruttura è eccessiva, perché 100.000 possono essere gestiti anche con un numero inferiore di server. Ma avevamo l'hardware – lo abbiamo utilizzato (ci hanno detto che era possibile – perché no?).

I vantaggi di Go

Dopo aver lavorato su questa applicazione, sono emersi alcuni vantaggi evidenti di Go.

  • Una fantastica libreria http. Con essa si può creare abbastanza già 'fuori dalla scatola'.
  • Inoltre, i canali ci hanno permesso di implementare molto facilmente un meccanismo di invio notifiche ai clienti.
  • Un'ottima funzione, il detector di race, ci ha permesso di eliminare diversi bug critici (infrastruttura di staging). Tutto ciò che funziona in staging è stato avviato, compilato con il flag Race; e pertanto possiamo vedere nell'infrastruttura di staging quali problemi potenziali abbiamo.
  • Minimalismo e semplicità del linguaggio.

Mikhail Salosin. Golang Meetup. Utilizzo di Go nel backend dell'applicazione “Guarda+”

Stiamo cercando sviluppatori! Se qualcuno è interessato, prego.

Domande

Domanda dal pubblico (di seguito – D): – Mi sembra che abbiate saltato un punto importante riguardo al Fan-out. Ho capito bene che quando inviate una risposta al cliente, vi bloccate se il cliente non desidera leggere?

MS: – No, non ci blocchiamo. In primo luogo, tutto ciò si trova dietro nginx, quindi non ci sono problemi con i client lenti. In secondo luogo, il cliente ha un canale con un buffer: in sostanza possiamo inserire fino a cent'anni di aggiornamenti… Se non possiamo scrivere nel canale, lo elimina. Se vediamo che il canale è bloccato, lo chiudiamo semplicemente e basta: il cliente si riconnetterà se si verifica qualche problema. Pertanto, qui in linea di principio non ci sono blocchi.

D: – Non si poteva semplicemente inviare una registrazione in Listen/Notify, invece di una tabella identificativa?

MS: – In Listen/Notify c'è un limite di 8.000 byte sul preload che invia. In linea di massima si potrebbe inviare, se avessimo a che fare con una piccola quantità di dati, ma mi sembra che così [come facciamo noi] sia semplicemente più affidabile. I limiti sono all'interno di "Postgres".

D: – I clienti ricevono aggiornamenti su partite che non li interessano?

MS: – In effetti, sì. Di solito ci sono 2-3 partite in parallelo, e anche questo è piuttosto raro. Se un cliente sta guardando qualcosa, di solito è quella partita che sta andando. Poi, sul cliente c'è un database locale, in cui vengono accumulati tutti questi aggiornamenti, e anche senza connessione a Internet il cliente può guardare tutte le partite passate per cui ha aggiornamenti. In sostanza, sincronizziamo il nostro database sul server con il database locale del cliente, in modo che possa lavorare anche offline.

D: – Perché avete creato il vostro ORM?

Alexey (uno degli sviluppatori di "Smotri+"): – All'epoca (era circa un anno fa) c'erano meno ORM rispetto a ora, quando ce ne sono abbastanza. Di gran parte degli ORM esistenti, ciò che mi piace di meno è che la maggior parte di essi lavora su interfacce vuote. Cioè, i metodi all'interno di questi ORM possono accettare qualsiasi cosa: una struttura, un puntatore a una struttura, un numero, qualcosa che non ha nulla a che fare con la questione…

Il nostro ORM genera strutture basate sul modello di dati. Da solo. E quindi tutti i metodi sono specifici, non utilizzano riflessione, ecc. Accettano strutture e si aspettano di utilizzare quelle strutture che arriveranno.

D: – Quante persone hanno partecipato?

MS: – Nella fase iniziale erano coinvolte due persone. Circa a giugno abbiamo iniziato, a agosto la parte principale era pronta (prima versione). A settembre c'è stata la release.

D: – Lì, dove descrivete SSE, non utilizzate il timeout. Perché?

MS: – Se parliamo chiaramente, SSE è comunque un protocollo html5: lo standard SSE è progettato per comunicare con i browser, per quanto ne so. Ha funzionalità aggiuntive per permettere ai browser di riconnettersi (e altro), ma a noi non servono, perché avevamo client che potevano realizzare qualsiasi logica di connessione e ottenimento di informazioni. Abbiamo fatto più qualcosa di simile a SSE, piuttosto che SSE stesso. Non è il protocollo vero e proprio.
Non c'era necessità. Per quanto ne so, i client realizzavano il meccanismo di connessione praticamente da zero. Non gliene importava molto.

D: – Quali utilità aggiuntive avete utilizzato?

MS: – Abbiamo utilizzato principalmente govet e golint, per uniformare lo stile, oltre a gofmt. Non abbiamo usato altro.

D: – Con cosa avete svolto il debugging?

MS: – Il debugging si è fatto principalmente tramite i test. Non abbiamo usato alcun debugger, GOP.

D: – Puoi restituire la slide in cui è realizzata la funzione Publish? I nomi delle variabili di una lettera non ti disturbano?

MS: – No. Hanno un'area di visibilità piuttosto "ristretta". Non vengono utilizzate da nessun'altra parte, tranne che qui (eccetto per l'interno di questa classe), ed è molto compatto – occupa solo 7 righe.

D: – In un certo senso, non è comunque intuitivo…

MS: – No-no, questo è codice reale! Non è una questione di stile. È solo una classe così utilitaria, con solo 3 campi all'interno della classe…

Mikhail Salosin. Golang Meetup. Utilizzo di Go nel backend dell'applicazione “Guarda+”

MS: – In sostanza, tutti i dati che vengono sincronizzati con i client (partite stagionali, giocatori) non cambiano. In soldoni, se facciamo un altro sport in cui sarà necessario modificare la partita, ci occuperemo di tutto nella nuova versione del client, e le versioni precedenti del client saranno bannate.

D: – Ci sono pacchetti esterni per la gestione delle dipendenze?

MS: – Abbiamo usato go dep.

D: – Nella tematica della relazione c'era qualcosa riguardo ai video, ma nella relazione sui video non c'è.

MS: – No, nella mia tematica non ho nulla riguardo ai video. Si chiama "Guarda+" – è così che si chiama l'app.

D: – Hai detto che si trasmette ai clienti?..

MS: – Non ci siamo occupati di video in streaming. Questo lo faceva completamente "MegaFon". Sì, non ho detto che l'app è di MegaFon.

MS: – Go – per l'invio di tutti i dati – sul conto, sugli eventi della partita, sulle statistiche… Go è interamente il backend per l'applicazione. Il cliente deve sapere da qualche parte quale link utilizzare per il lettore, affinché l'utente possa guardare la partita. Abbiamo link a video e streaming già preparati.

Guarda il video

Un po' di pubblicità 🙂

Grazie per rimanere con noi. Ti piacciono i nostri articoli? Vuoi vedere più contenuti interessanti? Supportaci effettuando un ordine o raccomandandoci a qualcuno. VPS cloud per sviluppatori a partire da $4.99., unica alternativa ai server entry-level, concepita da noi per te: Tutta la verità sui VPS (KVM) E5-2697 v3 (6 Core) 10GB DDR4 480GB SSD 1Gbps a partire da $19 o come dividere correttamente un server? (sono disponibili opzioni con RAID1 e RAID10, fino a 24 core e fino a 40GB DDR4).

Dell R730xd a metà prezzo nel data center Equinix Tier IV ad Amsterdam? Solo da noi 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TB a partire da $199 nei Paesi Bassi! Dell R420 — 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — a partire da $99! Leggi di Come costruire un'infrastruttura di livello enterprise utilizzando server Dell R730xd E5-2650 v4 del valore di 9000 euro a pochi spiccioli?

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster