Michail Salosin. Incontro Golang. Utilizzando Go nel backend dell'applicazione Look+

Mikhail Salosin (di seguito – MS): - Ciao a tutti! Il mio nome è Michael. Lavoro come sviluppatore backend presso MC2 Software e parlerò dell'utilizzo di Go nel backend dell'applicazione mobile Look+.

Michail Salosin. Incontro Golang. Utilizzando Go nel backend dell'applicazione Look+

A qualcuno qui piace l'hockey?

Michail Salosin. Incontro Golang. Utilizzando Go nel backend dell'applicazione Look+

Allora questa applicazione fa per te. È per Android e iOS e viene utilizzato per guardare le trasmissioni di vari eventi sportivi online e registrati. L'applicazione contiene anche varie statistiche, trasmissioni di testo, tabelle per conferenze, tornei e altre informazioni utili per gli appassionati.

Michail Salosin. Incontro Golang. Utilizzando Go nel backend dell'applicazione Look+

Anche nell'applicazione esistono momenti video, ad es. puoi guardare i momenti più importanti delle partite (goal, combattimenti, sparatorie, ecc.). Se non vuoi guardare l'intera trasmissione, puoi guardare solo quelle più interessanti.

Cosa hai utilizzato nello sviluppo?

La parte principale è stata scritta in Go. L'API con cui comunicavano i client mobili è stata scritta in Go. In Go è stato scritto anche un servizio per l'invio di notifiche push ai cellulari. Abbiamo anche dovuto scrivere il nostro ORM, di cui potremmo parlare un giorno. Ebbene, in Go sono stati scritti alcuni piccoli servizi: ridimensionamento e caricamento delle immagini per gli editor...

Abbiamo utilizzato PostgreSQL come database. L'interfaccia dell'editor è stata scritta in Ruby on Rails utilizzando il gem ActiveAdmin. Anche l'importazione di statistiche da un fornitore di statistiche è scritta in Ruby.

Per i test API di sistema, abbiamo utilizzato unittest Python. Memcached viene utilizzato per limitare le chiamate di pagamento API, “Chef” viene utilizzato per controllare la configurazione, Zabbix viene utilizzato per raccogliere e monitorare le statistiche interne del sistema. Graylog2 serve per la raccolta dei log, Slate è la documentazione API per i client.

Michail Salosin. Incontro Golang. Utilizzando Go nel backend dell'applicazione Look+

Selezione del protocollo

Il primo problema che abbiamo riscontrato: dovevamo scegliere un protocollo per l'interazione tra backend e client mobile, basandosi sui seguenti punti...

  • Il requisito più importante: i dati sui clienti devono essere aggiornati in tempo reale. Cioè, tutti coloro che stanno attualmente guardando la trasmissione dovrebbero ricevere gli aggiornamenti quasi istantaneamente.
  • Per semplificare le cose, abbiamo ipotizzato che i dati sincronizzati con i client non vengano cancellati, ma nascosti tramite flag speciali.
  • Tutti i tipi di richieste rare (come statistiche, composizioni di squadre, statistiche di squadra) vengono ottenute tramite richieste GET ordinarie.
  • Inoltre, il sistema doveva supportare facilmente 100mila utenti contemporaneamente.

Sulla base di ciò, 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 un websocket è un'opzione ridondante.
  2. Gli eventi inviati dal server (SSE) si sono rivelati perfetti! È abbastanza semplice e sostanzialmente soddisfa tutto ciò di cui abbiamo bisogno.

Eventi inviati dal server

Qualche parola su come funziona questa cosa...

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

Michail Salosin. Incontro Golang. Utilizzando Go nel backend dell'applicazione Look+

I dati possono essere inviati in un formato concordato con i clienti. Nel nostro caso, l'abbiamo inviato in questo formato: il nome della struttura modificata (persona, giocatore) è stato inviato al campo evento e JSON con i nuovi campi modificati per il giocatore è stato inviato al campo dati.

Ora parliamo di come funziona l'interazione stessa.

  • La prima cosa che fa il client è determinare l'ora dell'ultima sincronizzazione con il servizio: esamina il suo database locale e determina la data dell'ultima modifica da esso registrata.
  • Invia una richiesta con questa data.
  • In risposta gli inviamo tutti gli aggiornamenti avvenuti da quella data.
  • Successivamente, si connette al canale live e non si chiude finché non sono necessari questi aggiornamenti:

Michail Salosin. Incontro Golang. Utilizzando Go nel backend dell'applicazione Look+

Gli inviamo l'elenco delle modifiche: se qualcuno segna un gol cambiamo il punteggio della partita, se si infortuna anche questo viene inviato in tempo reale. Pertanto, i clienti ricevono immediatamente dati aggiornati nel feed degli eventi della partita. Periodicamente, in modo che il client capisca che il server non è morto, che non gli è successo nulla, inviamo un timestamp ogni 15 secondi, in modo che sappia che tutto è in ordine e non è necessario riconnettersi.

Come viene servita la connessione live?

  • Prima di tutto, creiamo un canale in cui verranno ricevuti gli aggiornamenti bufferizzati.
  • Successivamente, iscriviamo questo canale per ricevere aggiornamenti.
  • Impostiamo l'intestazione corretta in modo che il cliente sappia che è tutto ok.
  • Invia il primo ping. Registriamo semplicemente il timestamp della connessione corrente.
  • Successivamente, leggiamo dal canale in loop finché il canale di aggiornamento non viene chiuso. Il canale riceve periodicamente il timestamp corrente o le modifiche che stiamo già scrivendo sulle connessioni aperte.

Michail Salosin. Incontro Golang. Utilizzando Go nel backend dell'applicazione Look+

Il primo problema che abbiamo riscontrato è stato il seguente: per ogni connessione aperta con il client, abbiamo creato un timer che ticchettava una volta ogni 15 secondi - risulta che se avessimo 6mila connessioni aperte con una macchina (con un server API), 6 sono stati creati migliaia di timer. Ciò ha portato la macchina a non sostenere il carico richiesto. Il problema non era così ovvio per noi, ma abbiamo ottenuto un piccolo aiuto e l'abbiamo risolto.

Di conseguenza, ora il nostro ping proviene dallo stesso canale da cui proviene l'aggiornamento.

Di conseguenza, esiste un solo timer che scatta una volta ogni 15 secondi.

Ci sono diverse funzioni ausiliarie qui: invio dell'intestazione, ping e della struttura stessa. Cioè, il nome della classifica (persona, partita, stagione) e le informazioni su questa voce vengono trasmessi qui:

Michail Salosin. Incontro Golang. Utilizzando Go nel backend dell'applicazione Look+

Meccanismo per l'invio di aggiornamenti

Ora un po’ sull’origine dei cambiamenti. Abbiamo diverse persone, redattori, che guardano la trasmissione in tempo reale. Creano tutti gli eventi: qualcuno è stato espulso, qualcuno si è infortunato, una sorta di sostituzione...

Utilizzando un CMS, i dati entrano nel database. Successivamente, il database notifica ciò ai server API utilizzando il meccanismo di ascolto/notifica. I server API inviano già queste informazioni ai client. Pertanto, essenzialmente abbiamo solo pochi server collegati al database e non vi è alcun carico speciale sul database, perché il client non interagisce in alcun modo direttamente con il database:

Michail Salosin. Incontro Golang. Utilizzando Go nel backend dell'applicazione Look+

PostgreSQL: ascolta/notifica

Il meccanismo di ascolto/notifica in Postgres ti consente di notificare agli abbonati agli eventi che alcuni eventi sono cambiati: alcuni record sono stati creati nel database. Per fare ciò, abbiamo scritto un semplice trigger e una funzione:

Michail Salosin. Incontro Golang. Utilizzando Go nel backend dell'applicazione Look+

Quando inseriamo o modifichiamo un record, chiamiamo la funzione di notifica sul canale data_updates, passando lì il nome della tabella e l'identificativo del record che è stato modificato o inserito.

Per tutte le tabelle che devono essere sincronizzate con il client, definiamo un trigger, che, dopo aver modificato/aggiornato un record, richiama la funzione indicata nella slide sottostante.
In che modo l'API sottoscrive queste modifiche?

Viene creato un meccanismo Fanout: invia messaggi al client. Raccoglie tutti i canali dei clienti e invia gli aggiornamenti ricevuti attraverso questi canali:

Michail Salosin. Incontro Golang. Utilizzando Go nel backend dell'applicazione Look+

Qui la libreria standard pq, che si connette al database e dice di voler ascoltare il canale (data_updates), controlla che la connessione sia aperta e che tutto vada bene. Sto omettendo il controllo degli errori per risparmiare spazio (non controllare è pericoloso).

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

Come funziona il Fan-out?

In russo questo si traduce come “splitter”. Abbiamo un oggetto che registra gli abbonati che desiderano ricevere alcuni aggiornamenti. E non appena arriva un aggiornamento a questo oggetto, lo distribuisce a tutti i suoi abbonati. Abbastanza semplice:

Michail Salosin. Incontro Golang. Utilizzando Go nel backend dell'applicazione Look+

Come è implementato in Go:

Michail Salosin. Incontro Golang. Utilizzando Go nel backend dell'applicazione Look+

C'è una struttura, è sincronizzata usando Mutex. Ha un campo che salva lo stato della connessione di Fanout al database, ad es. è attualmente in ascolto e riceverà aggiornamenti, nonché un elenco di tutti i canali disponibili - mappa, la cui chiave è il canale e la struttura sotto forma di valori (sostanzialmente non utilizzati in alcun modo).

Due metodi - Connesso e Disconnesso - ci permettono di dire a Fanout che abbiamo una connessione con la base, è apparsa e che la connessione con la base è stata interrotta. Nel secondo caso è necessario disconnettere tutti i client e dire loro che non possono più ascoltare nulla e che si riconnettono perché la connessione con loro è interrotta.

Esiste anche un metodo Iscriviti che aggiunge il canale agli “ascoltatori”:

Michail Salosin. Incontro Golang. Utilizzando Go nel backend dell'applicazione Look+

Esiste un metodo Annulla iscrizione, che rimuove il canale dagli ascoltatori se il client si disconnette, nonché un metodo Pubblica, che ti consente di inviare un messaggio a tutti gli iscritti.

Domanda: – Cosa viene trasmesso attraverso questo canale?

SM: – Viene trasmesso il modello che è cambiato o il ping (essenzialmente solo un numero, intero).

SM: – Puoi inviare qualsiasi cosa, inviare qualsiasi struttura, pubblicarla – si trasforma semplicemente in JSON e il gioco è fatto.

SM: – Riceviamo una notifica da Postgres: contiene il nome e l'identificatore della tabella. In base al nome e all'identificatore della tabella, otteniamo il record di cui abbiamo bisogno, quindi inviamo questa struttura per la pubblicazione.

infrastruttura

Come si presenta dal punto di vista delle infrastrutture? Disponiamo di 7 server hardware: uno di essi è completamente dedicato al database, gli altri sei eseguono macchine virtuali. Esistono 6 copie dell'API: ciascuna macchina virtuale con l'API viene eseguita su un server hardware separato: questo per ragioni di affidabilità.

Michail Salosin. Incontro Golang. Utilizzando Go nel backend dell'applicazione Look+

Abbiamo due frontend con Keepalived installato per migliorare l'accessibilità, in modo che se succede qualcosa, un frontend può sostituire l'altro. Inoltre – due copie del CMS.

C'è anche un importatore di statistiche. E' presente un DB Slave dal quale vengono effettuati periodicamente i backup. C'è Pigeon Pusher, un'applicazione che invia notifiche push ai clienti, oltre a cose infrastrutturali: Zabbix, Graylog2 e Chef.

Di fatto questa infrastruttura è ridondante, perché se ne possono servire 100mila con meno server. Ma c'era il ferro - l'abbiamo usato (ci è stato detto che era possibile - perché no).

Pro di Go

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

  • Bella libreria http. Con esso puoi creare parecchio fuori dagli schemi.
  • Inoltre, canali che ci hanno permesso di implementare molto facilmente un meccanismo per l'invio di notifiche ai clienti.
  • La cosa meravigliosa Race detector ci ha permesso di eliminare diversi bug critici (infrastruttura di staging). Tutto ciò che lavora sull'allestimento viene lanciato, compilato con la chiave Race; e noi, di conseguenza, possiamo esaminare l'infrastruttura di allestimento per vedere quali potenziali problemi abbiamo.
  • Minimalismo e semplicità del linguaggio.

Michail Salosin. Incontro Golang. Utilizzando Go nel backend dell'applicazione Look+

Cerchiamo sviluppatori! Se qualcuno vuole, per favore.

domande

Domanda del pubblico (di seguito – B): – Mi sembra che tu abbia mancato un punto importante riguardo al Fan-out. Ho ragione nel capire che quando invii una risposta a un cliente, blocchi se il cliente non vuole leggere?

SM: - No, non stiamo bloccando. In primo luogo, abbiamo tutto questo dietro nginx, ovvero non ci sono problemi con i client lenti. In secondo luogo, il client ha un canale con un buffer: in effetti, lì possiamo inserire fino a un centinaio di aggiornamenti... Se non riusciamo a scrivere sul canale, lo elimina. Se vediamo che il canale è bloccato, chiuderemo semplicemente il canale e il gioco è fatto: il client si ricollegherà in caso di problemi. Pertanto, in linea di principio, qui non vi è alcun blocco.

A: – Non potrebbe essere possibile inviare immediatamente un record ad Ascolta/Notifica e non una tabella identificatori?

SM: – Ascolta/Notifica ha un limite di 8mila byte sul precarico inviato. In linea di principio, sarebbe possibile inviare se avessimo a che fare con una piccola quantità di dati, ma mi sembra che in questo modo [il modo in cui lo facciamo] sia semplicemente più affidabile. Le limitazioni sono nello stesso Postgres.

A: – I clienti ricevono aggiornamenti sulle partite a cui non sono interessati?

SM: - In generale, sì. Di norma, si svolgono 2-3 partite in parallelo, e anche in questo caso abbastanza raramente. Se un cliente sta guardando qualcosa, di solito sta guardando la partita in corso. Quindi, il cliente dispone di un database locale in cui vengono sommati tutti questi aggiornamenti e, anche senza una connessione Internet, il cliente può visualizzare tutte le partite passate per le quali dispone di aggiornamenti. In sostanza, sincronizziamo il nostro database sul server con il database locale del cliente in modo che possa lavorare offline.

A: – Perché hai creato il tuo ORM?

Alexey (uno degli sviluppatori di Look+): – A quel tempo (era un anno fa) c’erano meno ORM di adesso, quando ce ne sono parecchi. La cosa che preferisco della maggior parte degli ORM in circolazione è che la maggior parte di essi funziona su interfacce vuote. Cioè, i metodi di questi ORM sono pronti ad affrontare qualsiasi cosa: una struttura, un puntatore a struttura, un numero, qualcosa di completamente irrilevante...

Il nostro ORM genera strutture basate sul modello dati. Me stessa. E quindi tutti i metodi sono concreti, non utilizzano la riflessione, ecc. Accettano strutture e pretendono di utilizzare quelle strutture che vengono.

A: – Quante persone hanno partecipato?

SM: – Nella fase iniziale hanno partecipato due persone. Abbiamo iniziato da qualche parte a giugno e ad agosto la parte principale era pronta (la prima versione). C'è stata un'uscita a settembre.

A: – Quando descrivi SSE, non usi il timeout. Perché?

SM: – A dire il vero, SSE è ancora un protocollo html5: lo standard SSE è progettato per comunicare con i browser, a quanto ho capito. Ha funzionalità aggiuntive in modo che i browser possano riconnettersi (e così via), ma non ne abbiamo bisogno, perché avevamo client in grado di implementare qualsiasi logica per la connessione e la ricezione di informazioni. Non abbiamo creato SSE, ma piuttosto qualcosa di simile a SSE. Questo non è il protocollo stesso.
Non ce n'era bisogno. Per quanto ho capito, i clienti hanno implementato il meccanismo di connessione quasi da zero. A loro non importava davvero.

A: – Quali utilità aggiuntive hai utilizzato?

SM: – Abbiamo utilizzato più attivamente govet e golint per rendere lo stile unificato, così come gofmt. Nient'altro è stato utilizzato.

A: – Cosa hai usato per eseguire il debug?

SM: – Il debugging è stato effettuato in gran parte tramite test. Non abbiamo utilizzato alcun debugger o GOP.

A: – Puoi restituire la diapositiva in cui è implementata la funzione Pubblica? I nomi delle variabili composte da una sola lettera ti confondono?

SM: - NO. Hanno un ambito di visibilità abbastanza “ristretto”. Non vengono utilizzati da nessun'altra parte tranne che qui (tranne che per gli interni di questa classe), ed è molto compatto: richiede solo 7 righe.

A: – In qualche modo non è ancora intuitivo...

SM: - No, no, questo è un vero codice! Non è una questione di stile. È una classe davvero utilitaristica e molto piccola: solo 3 campi all'interno della classe...

Michail Salosin. Incontro Golang. Utilizzando Go nel backend dell'applicazione Look+

SM: – Nel complesso, tutti i dati sincronizzati con i client (partite della stagione, giocatori) non cambiano. In parole povere, se creiamo un altro sport in cui dobbiamo cambiare la partita, terremo semplicemente conto di tutto nella nuova versione del client e le vecchie versioni del client verranno bandite.

A: – Esistono pacchetti di gestione delle dipendenze di terze parti?

SM: – Usavamo Go Dep.

A: – Nell'argomento del rapporto c'era qualcosa che riguardava i video, ma nel rapporto non c'era nulla che riguardasse i video.

SM: – No, non ho nulla nell’argomento riguardante il video. Si chiama "Look+": questo è il nome dell'applicazione.

A: – Hai detto che viene trasmesso in streaming ai clienti?..

SM: – Non eravamo coinvolti nello streaming video. Questo è stato interamente realizzato da Megafon. Sì, non ho detto che l'applicazione fosse MegaFon.

SM: – Go – per inviare tutti i dati – sul punteggio, sugli eventi delle partite, sulle statistiche... Go è l'intero backend dell'applicazione. Il client deve sapere da qualche parte quale collegamento utilizzare per il giocatore in modo che l'utente possa guardare la partita. Abbiamo collegamenti a video e stream che sono stati preparati.

Alcuni annunci 🙂

Grazie per stare con noi. Ti piacciono i nostri articoli? Vuoi vedere contenuti più interessanti? Sostienici effettuando un ordine o raccomandando agli amici, cloud VPS per sviluppatori da $ 4.99, un analogo unico dei server entry-level, che è stato inventato da noi per te: Tutta la verità su VPS (KVM) E5-2697 v3 (6 core) 10 GB DDR4 480 GB SSD 1 Gbps da $ 19 o come condividere un server? (disponibile con RAID1 e RAID10, fino a 24 core e fino a 40 GB DDR4).

Dell R730xd 2 volte più economico nel data center Equinix Tier IV ad Amsterdam? Solo qui 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TV da $199 In Olanda! Dell R420 - 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB - da $99! Leggi Come costruire Infrastructure Corp. classe con l'utilizzo di server Dell R730xd E5-2650 v4 del valore di 9000 euro per un centesimo?

Fonte: habr.com

Aggiungi un commento