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 «Guarda+».

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

Qualcuno tra voi ama l'hockey?

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

Allora questa è l'app per voi. È disponibile per Android e iOS e permette di guardare in diretta e in differita le trasmissioni di vari eventi sportivi. Inoltre, l'app offre statistiche, trasmissioni testuali, tabelle per conferenze, tornei e altre informazioni utili per i tifosi.

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

Inoltre, l'app ha una funzione chiamata momenti video, ovvero è possibile vedere i momenti salienti delle partite (gol, risse, rigori, ecc.). Se non volete guardare tutta la trasmissione, potete vedere solo le parti più interessanti.

Cosa è stato usato nello sviluppo?

La parte principale è stata scritta in Go. L'API con cui comunicavano i client mobili è stata scritta in Go. Anche il servizio per l'invio di notifiche push sui dispositivi mobili è stato realizzato in Go. Inoltre, abbiamo dovuto scrivere il nostro ORM, di cui forse parleremo un giorno. Infine, sono stati scritti in Go alcuni piccoli servizi: ridimensionamento e caricamento delle immagini per il lato editoriale...

Come database, abbiamo utilizzato PostgreSQL. L'interfaccia per gli editor è stata realizzata in Ruby on Rails utilizzando la gemma ActiveAdmin. Inoltre, è stato scritto in Ruby anche il modulo per l'importazione delle statistiche dal fornitore di statistiche.

Per i test di sistema dell'API, abbiamo utilizzato unittest di Python. Memcached viene utilizzato per limitare le chiamate all'API di pagamento, Chef per il controllo della configurazione, Zabbix per la raccolta e il monitoraggio dei dati statistici interni 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 incontrato è stato scegliere un protocollo per l'interazione tra il backend e i client mobili, basandoci sui seguenti punti...

  • Il requisito principale è che i dati sui clienti devono essere aggiornati in tempo reale. Ciò significa che tutti coloro che stanno guardando la diretta devono ricevere aggiornamenti quasi istantaneamente.
  • Per semplificare, abbiamo stabilito che i dati che vengono sincronizzati con i clienti non vengono eliminati, ma nascosti tramite flag speciali.
  • Qualsiasi richiesta rara (come statistiche, composizioni delle squadre, statistiche delle squadre) viene effettuata mediante normali richieste GET.
  • Inoltre, il sistema deve essere in grado di gestire senza problemi 100.000 utenti contemporaneamente.

Da questo, avevamo due opzioni di protocollo:

  1. Websocket. Ma non avevamo bisogno di canali dal client al server. Avevamo solo bisogno di inviare aggiornamenti dal server al client, quindi il websocket è un'opzione ridondante.
  2. Gli Eventi Inviati dal Server (Server-Sent Events, SSE) si adattano perfettamente! Sono abbastanza semplici e soddisfano fondamentalmente tutto ciò di cui abbiamo bisogno.

Eventi Inviati dal Server

Due parole su come funziona questa cosa...

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

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

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

Ora parliamo del funzionamento stesso dell'interazione.

  • Per prima cosa, il cliente determina quando è stata effettuata l'ultima sincronizzazione con il servizio: controlla nel suo database locale e definisce la data dell'ultima modifica registrata.
  • Invia una richiesta con questa data.
  • In risposta, noi gli inviamo tutti gli aggiornamenti che sono avvenuti a partire da questa data.
  • Dopo di che, stabilisce una connessione con il canale live e non la chiude finché ha bisogno di questi aggiornamenti:

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

Noi gli inviamo un elenco di modifiche: se qualcuno segna un gol – aggiorniamo il punteggio della partita, se subisce un infortunio – viene inviato anche questo in tempo reale. In questo modo, nella cronologia degli eventi della partita, i clienti ricevono immediatamente i dati aggiornati. Periodicamente, per far sapere al cliente che il server non è morto e che non è successo nulla, inviamo un timestamp ogni 15 secondi, in modo che sappia che tutto è a posto e non è necessario riconnettersi.

Come viene gestita la connessione live?

  • Prima di tutto creiamo un canale in cui arriveranno gli aggiornamenti con un buffer.
  • Successivamente, iscriviamo questo canale per ricevere aggiornamenti.
  • Impostiamo l'intestazione corretta affinché il client sappia che tutto va bene.
  • Inviamo il primo ping. Registriamo semplicemente il timestamp attuale della connessione.
  • Dopo di che, in un ciclo, leggiamo dal canale finché il canale degli aggiornamenti non è chiuso. Nel canale arrivano periodicamente sia il timestamp attuale che le modifiche, che registriamo già nelle connessioni aperte.

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

Il primo problema che abbiamo incontrato è stato il seguente: per ogni connessione aperta con il cliente creavamo un timer che scattava ogni 15 secondi; quindi, se avevamo 6.000 connessioni aperte con una stessa macchina (con un singolo server API), si creavano 6.000 timer. Questo portava la macchina a non reggere il carico necessario. Il problema non era così ovvio per noi, ma ci hanno dato un po' di aiuto e lo abbiamo risolto.

Di conseguenza, ora riceviamo il ping dallo stesso canale da cui arriva l'update.

Pertanto, abbiamo solo un timer che scatta ogni 15 secondi.

Qui ci sono alcune funzioni ausiliarie: invio dell'intestazione, ping e la struttura stessa. In altre parole, qui viene trasmesso il nome della tabella (person, match, season) e le informazioni relative a questo record.

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

Meccanismo di invio aggiornamenti

Ora parliamo di dove provengono le modifiche. Abbiamo alcune persone, redattori, che guardano la trasmissione in tempo reale. Creano tutti gli eventi: qualcuno è stato espulso, qualcuno ha subito un infortunio, c'è stato un cambio...

Attraverso il CMS, i dati vengono inseriti nel database. Successivamente, il database utilizza il meccanismo Listen/Notify per avvisare i server API. I server API inviano quindi queste informazioni ai clienti. Così, sostanzialmente, abbiamo solo alcuni server collegati al database e non c'è un 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 avvisare gli iscritti sugli eventi riguardo a cosa è cambiato, ossia se è stata creata una nuova registrazione nel database. Per questo abbiamo scritto un semplice trigger e funzione:

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

Durante l'inserimento o la 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 o l'aggiornamento di un record, chiama la funzione specificata nella slide in basso.
Come si iscrive l'API a queste modifiche?

Si crea un meccanismo Fanout – esso diffonde messaggi ai clienti. Raccoglie tutti i canali dei clienti e invia aggiornamenti che ha ricevuto su questi canali:

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

Qui c'è la libreria standard pq, che si connette al database e comunica di voler ascoltare il canale (data_updates), verificando che la connessione sia aperta e tutto sia a posto. Non mostro il controllo degli errori per risparmiare spazio (non verificarli è rischioso).

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

Come funziona il Fan-out?

In italiano si traduce come «distributore». Abbiamo un oggetto che registra gli iscritti che desiderano ricevere aggiornamenti. Non appena arriva un aggiornamento a questo oggetto, lo distribuisce a tutti gli iscritti. È 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 usando Mutex. Ha un campo che salva lo stato della connessione Fanout al database, cioè in questo momento sta ascoltando e riceverà aggiornamenti, oltre a un elenco di tutti i canali esistenti – una mappa, dove la chiave è il canale e il valore è una struct (che in sostanza non viene utilizzata).

Due metodi – Connected e Disconnected – permettono al Fanout di sapere che abbiamo una connessione al database, che è stata stabilita e che la connessione è stata interrotta. Nel secondo caso, è necessario disconnettere tutti i client e informarli che non possono più ascoltare e che devono riconnettersi, poiché la loro connessione è 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+"

Esiste un metodo Unsubscribe che rimuove il canale dagli ascoltatori se il cliente si disconnette, insieme a un metodo Publish che consente di inviare un messaggio a tutti gli iscritti.

Domanda: – Cosa viene trasmesso attraverso questo canale?

Risposta: – Viene trasmesso un modello che è cambiato oppure un ping (in sostanza solo un numero, un intero).

Risposta: – Può essere qualsiasi cosa, qualsiasi struttura può essere inviata, pubblicata – essa viene semplicemente convertita in JSON e basta.

Risposta: – Riceviamo una notifica da «PostgreSQL» – essa contiene il nome della tabella e l'identificatore. Attraverso il nome della tabella e l'identificatore, otteniamo il record necessario, e poi inviamo quella struttura per la pubblicazione.

Infrastruttura

Come si presenta dal punto di vista dell'infrastruttura? Abbiamo 7 server fisici: uno di essi è completamente dedicato al database, mentre gli altri sei ospitano virtual machine. Ci sono 6 copie dell'API: ogni virtual machine con l'API gira su un server fisico separato – questo per garantire affidabilità.

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

Abbiamo due frontend, sui quali è installato Keepalived per migliorare la disponibilità, in modo che, in caso di problemi, un frontend possa sostituire l'altro. Inoltre, ci sono due copie del CMS.

È disponibile anche un importatore di statistiche. C'è un DB Slave da cui vengono eseguiti regolarmente i backup. Abbiamo anche Pigeon Pusher – l'applicazione che invia notifiche push ai clienti, oltre a componenti infrastrutturali: Zabbix, Graylog2 e Chef.

In realtà, questa infrastruttura è ridondante, poiché possiamo gestire 100.000 con un numero inferiore di server. Ma c'era l'hardware – lo abbiamo utilizzato (ci hanno detto che era possibile – perché non farlo?).

Vantaggi di Go

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

  • Una fantastica libreria http. Con essa è possibile creare già molto 'out of the box'.
  • Inoltre, i canali ci hanno permesso di implementare molto facilmente il meccanismo di invio di notifiche ai clienti.
  • Il fantastico strumento Race detector ci ha permesso di risolvere diversi bug critici (infrastruttura di staging). Tutto ciò che funziona in staging è stato avviato e compilato con l'opzione Race; quindi possiamo vedere quali problemi potenziali abbiamo nell'infrastruttura di staging.
  • 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 trascurato un aspetto importante riguardo al Fan-out. Ho capito bene che quando inviate una risposta al cliente, vi bloccate se il cliente non vuole leggere?

Risposta: – No, non ci blocchiamo. In primo luogo, tutto ciò è gestito tramite nginx, quindi non ci sono problemi con i clienti lenti. In secondo luogo, il cliente ha un canale con buffer – in sostanza possiamo mettere fino a cento aggiornamenti... Se non riusciamo a scrivere nel canale, lo elimina. Se vediamo che il canale si è bloccato, semplicemente chiudiamo il canale e basta – il cliente si riconnetterà se c'è qualche problema. Quindi, in realtà, non ci sono blocchi.

D: – Non sarebbe stato possibile inviare immediatamente una registrazione in Listen/Notify anziché un identificatore della tabella?

Risposta: – Listen/Notify ha una limitazione di 8.000 byte per il preload che invia. In teoria si potrebbe inviare, se avessimo a che fare con una quantità ridotta di dati, ma mi sembra che in questo modo [come facciamo noi] sia semplicemente più affidabile. Le limitazioni sono nel ‘Postgres’ stesso.

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

Risposta: In generale, sì. Di solito ci sono 2-3 partite in parallelo, e ciò accade abbastanza raramente. Se un cliente guarda qualcosa, di solito è quella partita che stanno seguendo. Inoltre, nel client c'è un database locale dove vengono salvati tutti questi aggiornamenti, e anche senza connessione a Internet, il cliente può rivedere tutte le partite passate per le quali ha aggiornamenti. In sostanza, sincronizziamo il nostro database sul server con il database locale del cliente, così può lavorare anche offline.

D: Perché avete creato il vostro ORM?

Aleksej (uno degli sviluppatori di "Smetri+"): A quel tempo (era un anno fa) gli ORM erano meno diffusi di oggi, quando ce ne sono molti. Di quelli esistenti, non mi piace che la maggior parte lavori su interfacce vuote. Questo significa che i metodi in questi ORM sono pronti ad accettare qualsiasi cosa: strutture, puntatori a strutture, numeri, qualcosa che non ha nulla a che fare con il contesto...

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

D: – Quante persone hanno partecipato?

Risposta: – All'inizio hanno partecipato due persone. Abbiamo iniziato a giugno e ad agosto gran parte era pronta (la prima versione). A settembre c'è stata la rilascio.

D: – Dove descrivi l'SSE, non utilizzi il timeout. Perché?

Risposta: – A dire il vero, l'SSE è un protocollo HTML5: lo standard SSE è destinato alla comunicazione con i browser, per quanto ne so. Ha funzionalità aggiuntive per consentire ai browser di riconnettersi (e altro), ma a noi non servivano, perché avevamo clienti in grado di implementare qualsiasi logica di connessione e ottenimento informazioni. Abbiamo fatto più che altro un qualcosa di simile all'SSE. Non è proprio il protocollo stesso.
Non c'era necessità. Da quanto ne so, i clienti hanno implementato il meccanismo di connessione praticamente da zero. Per loro era in fondo indifferente.

D: – Quali strumenti aggiuntivi hai utilizzato?

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

D: – Con cosa avete fatto il debug?

Risposta: – Il debug è avvenuto per lo più attraverso i test. Non abbiamo utilizzato alcun debugger, né GOP.

D: – Puoi restituire il diapositive dove è implementata la funzione Publish? I nomi delle variabili a una lettera non ti preoccupano?

Risposta: – No. Hanno un ambito di visibilità abbastanza "ristretto". Non vengono utilizzati altrove, eccetto qui (e all'interno di questa classe), ed è molto compatto – occupa solo 7 righe.

D: – In qualche modo non è intuitivo...

Risposta: – No-no, questo è codice vero! Non è una questione di stile. È solo una classe utilitaria, davvero piccola – ha solo 3 campi all'interno della classe...

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

Risposta: – In linea di massima, tutti i dati che vengono sincronizzati con i client (partite stagionali, giocatori) non cambiano. In altre parole, se vogliano sviluppare un altro sport in cui è necessario modificare la partita, lo terremo in considerazione nella nuova versione del client, mentre le vecchie versioni del client verranno bloccate.

D: – Ci sono pacchetti di terze parti per la gestione delle dipendenze?

Risposta: – Abbiamo usato go dep.

D: – Nella presentazione c'era qualcosa riguardo ai video, ma nella conferenza non c'è nulla sui video.

Risposta: – No, nel mio argomento sui video non c'è niente. Si chiama "Guarda+" – è così che si chiama l'app.

D: – Hai detto che viene trasmesso ai client?..

Risposta: – Non ci siamo occupati di video in streaming. Questo è stato fatto completamente da «MegaFon». Sì, non ho detto che l'app è di MegaFon.

Risposta: – Go serve per inviare tutti i dati – sulle fatture, sugli eventi delle partite, 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 vedere la partita. Abbiamo link ai video e agli streaming preparati.

Riproduci video

Un po' di pubblicità 🙂

Grazie per essere con noi. Ti piacciono i nostri articoli? Vuoi vedere più contenuti interessanti? Supportaci effettuando un ordine o raccomandandoci ai tuoi conoscenti, VPS cloud per sviluppatori a partire da $4,99, un'alternativa unica ai server entry-level, che abbiamo creato per te: Tutta la verità su VPS (KVM) E5-2697 v3 (6 Core) 10GB DDR4 480GB SSD 1Gbps a partire da $19. Come dividere il server correttamente? (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! Scopri di più su Come costruire un'infrastruttura di classe enterprise con server Dell R730xd E5-2650 v4 dal costo di 9000 euro a prezzi stracciati?

Fonte: habr.com

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