Bioyino — un aggregatore di metriche distribuito e scalabile

Quindi, state raccogliendo metriche. Proprio come noi. Anche noi raccogliamo metriche. Certamente quelle utili per il business. Oggi parleremo del primo anello del nostro sistema di monitoraggio: un server di aggregazione compatibile con statsd. bioyino, il motivo per cui l'abbiamo scritto e perché abbiamo abbandonato brubeck.

Bioyino — un aggregatore di metriche distribuito e scalabile

Dai nostri articoli precedenti (1, 2) puoi scoprire che fino a un certo punto abbiamo raccolto i nostri dati tramite brubeck. È scritto in C. Dal punto di vista del codice - semplice come un tappo (questo è importante quando vuoi contribuire) e, cosa più importante, gestisce senza particolari problemi i nostri volumi di 2 milioni di metriche al secondo (MPS) al picco. La documentazione afferma di supportare 4 milioni di MPS con asterisco. Ciò significa che otterrai il numero dichiarato se configuri correttamente la rete su Linux. (Non sappiamo quante MPS si possono ottenere lasciando la rete com'è). Nonostante questi vantaggi, avevamo alcune serie lamentele su brubeck.

Lamentela 1. Github — lo sviluppatore del progetto — ha smesso di supportarlo: pubblicare patch e fix, accettare le nostre e (non solo le nostre) PR. Negli ultimi mesi (circa da febbraio-marzo 2018) l'attività è ripresa, ma prima di ciò c'è stata quasi 2 anni di completo silenzio. Inoltre, il progetto è sviluppato per esigenze interne di Gihub, il che può rappresentare un serio ostacolo all'implementazione di nuove funzionalità.

Reclamo 2. Precisione dei calcoli. Brubeck raccoglie solo 65536 valori per l'aggregazione. Nel nostro caso, per alcune metriche durante il periodo di aggregazione (30 secondi) possono arrivare molte più valore (1.527.392 al picco). Di conseguenza, i valori massimi e minimi sembrano inutili. Ad esempio, ecco come dovrebbe apparire:

Bioyino — un aggregatore di metriche distribuito e scalabile
Come era

Bioyino — un aggregatore di metriche distribuito e scalabile
Come dovrebbe essere

Per lo stesso motivo, le somme sono considerate del tutto scorrette. Aggiungi a ciò il bug del overflow del float a 32 bit, che manda in segfault il server all'arrivo di una metrica apparentemente innocua, e diventa davvero eccellente. Il bug, per inciso, non è stato ancora corretto.

E, infine, Reclamo X. Al momento della stesura di questo articolo, siamo pronti a presentarlo a tutte e 14 le implementazioni di statsd che siamo riusciti a trovare. Immaginiamo che un'infrastruttura specifica sia cresciuta a tal punto da rendere insufficienti 4 milioni di MPS. Oppure, magari non è ancora cresciuta, ma le metriche sono già così importanti per voi che anche brevi cali, di 2-3 minuti, nei grafici possono risultare critici e provocare episodi di depressione negli manager. Poiché curare la depressione è un compito ingrato, sono necessarie soluzioni tecniche.

In primo luogo, la tolleranza ai guasti, affinché un problema imprevisto su un server non scateni un'apocalisse zombie psichiatrica in ufficio. In secondo luogo, la scalabilità, per poter gestire più di 4 milioni di MPS, senza dover scavare a fondo nello stack di rete di Linux e crescere tranquillamente "in larghezza" fino alle dimensioni necessarie.

Poiché avevamo margine per la scalabilità, abbiamo deciso di iniziare con la resilienza. "Oh! Resilienza! È semplice, lo sappiamo fare", abbiamo pensato e abbiamo avviato 2 server, sollevando su ciascuno una copia di brubeck. Per questo, abbiamo dovuto copiare il traffico con le metriche su entrambi i server e persino scrivere per questo una piccola utilità. Abbiamo risolto il problema della tolleranza ai guasti, ma... non molto bene. All'inizio sembrava andare tutto alla grande: ogni brubeck raccoglie la propria variante di aggregazione, scrivendo dati in Graphite ogni 30 secondi, sovrascrivendo l'intervallo precedente (questo avviene sul lato di Graphite). Se un server dovesse guastarsi, abbiamo sempre un secondo con una copia dei dati aggregati. Ma ecco il problema: se un server si guasta, sui grafici compare una «sega». Questo è dovuto al fatto che gli intervalli di 30 secondi dei brubeck non sono sincronizzati e, nel momento in cui uno di essi si guasta, non viene sovrascritto. Quando il secondo server viene attivato, succede la stessa cosa. È tollerabile, ma si può fare di meglio! Anche il problema della scalabilità non è scomparso. Tutte le metriche continuano a «volare» su un singolo server e perciò siamo limitati agli stessi 2-4 milioni MPS a seconda delle prestazioni della rete.

Se ci si ferma un attimo a riflettere sul problema e si scava un po' nella neve, può venirci in mente un'idea piuttosto ovvia: abbiamo bisogno di un statsd in grado di operare in modalità distribuita. In altre parole, uno che implementi la sincronizzazione tra le nodi in base al tempo e alle metriche. "Certo, una soluzione del genere deve già esistere", abbiamo detto e ci siamo messi a cercare su Google... ma non abbiamo trovato nulla. Esaminando la documentazione di vari statsd (https://github.com/etsy/statsd/wiki#server-implementations a partire dal 11.12.2017), non abbiamo trovato davvero niente. Evidentemente, né gli sviluppatori né gli utenti di queste soluzioni si sono ancora imbattuti in UNA COSÌ GRANDE quantità di metriche, altrimenti avrebbero sicuramente trovato qualche soluzione.

Ed è qui che ci siamo ricordati del statsd "giocattolo" - bioyino, che abbiamo creato durante un hackathon solo per divertirci (il nome del progetto è stato generato da uno script prima dell'inizio dell'hackathon) e abbiamo capito che avevamo urgentemente bisogno di un nostro statsd. Perché?

  • perché nel mondo ci sono troppo pochi cloni di statsd,
  • perché possiamo garantire la resilienza e la scalabilità desiderate o almeno vicine (inclusa la sincronizzazione delle metriche aggregate tra i server e risolvere il problema dei conflitti durante l'invio),
  • perché è possibile monitorare le metriche con maggiore precisione rispetto a brubeck,
  • perché possiamo raccogliere statistiche più dettagliate che brubeck praticamente non ci ha fornito,
  • perché abbiamo avuto l'opportunità di programmare la nostra applicazione hyper-performance distribuita, che non réplica completamente l'architettura di un'altra simile.

Con cosa scrivere? Naturalmente, con Rust. Perché?

  • perché esisteva già un prototipo della soluzione,
  • perché l'autore dell'articolo a quel tempo conosceva già Rust e voleva scrivere qualcosa per la produzione da rendere open-source,
  • perché i linguaggi con GC non sono adatti alla natura del traffico che gestiamo (praticamente in tempo reale) e le pause del GC sono praticamente inaccettabili,
  • perché necessitiamo della massima prestazione, paragonabile a C
  • perché Rust ci offre una concorrenza senza paura, e iniziando a scrivere in C/C++, avremmo accumulato ancor più vulnerabilità, overflow di buffer, condizioni di race e altre brutte parole rispetto a brubeck.

C'era anche un argomento contro Rust. L'azienda non aveva esperienza nella creazione di progetti in Rust, e attualmente non prevediamo di utilizzarlo nel progetto principale. Pertanto, c'erano serie preoccupazioni che nulla potesse funzionare, ma abbiamo deciso di rischiare e abbiamo provato.

Passava il tempo…

Finalmente, dopo diversi tentativi falliti, la prima versione funzionante era pronta. Che cosa abbiamo ottenuto? È venuto fuori qualcosa di simile a questo.

Bioyino — un aggregatore di metriche distribuito e scalabile

Ogni nodo riceve il proprio insieme di metriche e le accumula, senza aggregare le metriche per quei tipi che richiederebbero il loro insieme completo per l'aggregazione finale. I nodi sono connessi tra loro attraverso un protocollo di blocco distribuito, che consente di selezionare l'unico nodo (qui piangevamo) che è degno di inviare le metriche al Grande. Attualmente, questo problema viene affrontato con Consul, ma in futuro le ambizioni dell'autore si estendono a propria implementazioni Raft, dove il nodo principale è certamente il nodo leader del consenso. Oltre al consenso, i nodi inviano abbastanza frequentemente (di default una volta al secondo) ai loro vicini le parti pre-aggregate delle metriche che sono riusciti a raccogliere in quel secondo. In questo modo, la scalabilità e la tolleranza ai guasti sono garantite: ogni nodo mantiene ancora un set completo di metriche, ma le metriche vengono inviate già aggregate, tramite TCP e codificate in un protocollo binario, riducendo così i costi di duplicazione rispetto all'UDP. Nonostante l'elevato numero di metriche in entrata, l'accumulo richiede davvero poca memoria e ancora meno CPU. Per le nostre metriche ben comprimibili, si tratta solo di poche decine di megabyte di dati. Un ulteriore vantaggio è l'assenza di inutili sovrascritture di dati in Graphite, come accadeva nel caso di burbeck.

I pacchetti UDP con metriche sono distribuiti tra i nodi sull'hardware di rete tramite un semplice Round Robin. Naturalmente, l'hardware di rete non analizza il contenuto dei pacchetti e quindi può gestire molto più di 4 milioni di pacchetti al secondo, per non parlare delle metriche di cui non sa nulla. Considerando che le metriche non arrivano una alla volta in ogni pacchetto, non prevediamo problemi di prestazioni in questo contesto. In caso di caduta del server, il dispositivo di rete rileverà rapidamente (nell'arco di 1-2 secondi) questo fatto e rimuoverà il server non funzionante dalla rotazione. Di conseguenza, i nodi passivi (ovvero non leader) possono essere accesi e spenti praticamente senza provocare cali nei grafici. Il massimo che perdiamo è una parte delle metriche arrivate nell'ultimo secondo. Una perdita/improvviso spegnimento/cambiamento del leader disegnerà comunque un'anomalia minima (l'intervallo di 30 secondi sarà ancora disallineato), ma mantenendo una connessione tra i nodi, è possibile ridurre al minimo anche questi problemi, ad esempio, inviando pacchetti di sincronizzazione.

Un po' sul funzionamento interno. L'applicazione è ovviamente multithread, ma l'architettura dei thread è diversa da quella utilizzata in brubeck. I thread in brubeck sono uniformi: ognuno di essi è responsabile sia della raccolta delle informazioni che dell'aggregazione. In bioyino, i thread di lavoro (workers) sono divisi in due gruppi: quelli responsabili della rete e quelli responsabili dell'aggregazione. Questa divisione consente di gestire l'applicazione in modo più flessibile in base al tipo di metriche: dove è necessaria un'aggregazione intensa, è possibile aumentare il numero di aggregatori, mentre dove c'è molto traffico di rete, si può aumentare il numero di thread di rete. Al momento, sui nostri server stiamo operando con 8 thread di rete e 4 thread di aggregazione.

La parte di conteggio (responsabile dell'aggregazione) è piuttosto noiosa. I buffer riempiti con i thread di rete vengono distribuiti tra i thread di conteggio, dove vengono quindi analizzati e aggregati. Su richiesta, le metriche vengono restituite per l'invio ad altre nodi. Tutto questo, inclusa la trasmissione dei dati tra nodi e il lavoro con Consul, avviene in modo asincrono, funziona su un framework tokio.

La parte della rete responsabile della raccolta dei metodi ha presentato molte più difficoltà durante lo sviluppo. L'obiettivo principale di isolare i flussi di rete in entità distinte era quello di ridurre il tempo impiegato dai flussi non nella lettura dei dati dal socket. Le opzioni che prevedevano l'uso di UDP asincrono e il normale recvmsg sono state rapidamente scartate: il primo consuma troppa CPU in user-space per la gestione degli eventi, il secondo richiede troppe commutazioni di contesto. Pertanto, attualmente si utilizza recvmmsg con buffer grandi (e i buffer, signori ufficiali, non sono qualcosa da prendere alla leggera!). Il supporto per UDP standard è stato mantenuto per casi non critici, dove non è necessaria la recvmmsg. In modalità multimessage si riesce a raggiungere l’obiettivo principale: la maggior parte del tempo il flusso di rete gestisce la coda del sistema operativo — legge i dati dal socket e li trasferisce nel buffer user-space, passando solo occasionalmente per restituire il buffer pieno agli aggregatori. La coda nel socket praticamente non si accumula e il numero di pacchetti scartati cresce di poco.

Nota

Nelle impostazioni predefinite, la dimensione del buffer è impostata piuttosto grande. Se decidi di provare il server da solo, potresti riscontrare che, dopo l'invio di un piccolo numero di metriche, esse non arrivano a Graphite, rimanendo nel buffer del flusso di rete. Per lavorare con un numero ridotto di metriche, è necessario impostare nel file di configurazione valori più piccoli per bufsize e task-queue-size.

Infine, ecco alcuni grafici per gli amanti dei grafici.

Statistiche sul numero di metriche in ingresso per ogni server: oltre 2 milioni di MPS.

Bioyino — un aggregatore di metriche distribuito e scalabile

Disattivazione di uno dei nodi e ridistribuzione delle metriche in ingresso.

Bioyino — un aggregatore di metriche distribuito e scalabile

Statistiche sulle metriche in uscita: solo un nodo invia sempre — il raidboss.

Bioyino — un aggregatore di metriche distribuito e scalabile

Statistiche sul funzionamento di ciascun nodo tenendo conto degli errori in vari moduli del sistema.

Bioyino — un aggregatore di metriche distribuito e scalabile

Dettagli delle metriche in ingresso (i nomi delle metriche sono nascosti).

Bioyino — un aggregatore di metriche distribuito e scalabile

Cosa intendiamo fare con tutto questo in futuro? Certamente scrivere codice, bl…! Il progetto è stato concepito come open-source e rimarrà tale per tutta la sua vita. Nei nostri piani futuri c'è la transizione a una versione proprietaria di Raft, il passaggio a un protocollo peer più portabile, l'inserimento di statistiche interne aggiuntive, nuovi tipi di metriche, la correzione di bug e altri miglioramenti.

Naturalmente, sono tutti benvenuti a contribuire allo sviluppo del progetto: create PR, Issues e, se possibile, risponderemo, perfezioneremo, ecc.

E con questo, come si suol dire, that’s all folks, comprate i nostri elefanti!

Riproduci video


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