Domande frequenti sull'architettura e sul funzionamento di VKontakte

La storia della creazione di VKontakte è su Wikipedia, raccontata dallo stesso Pavel. Sembra che tutti la conoscano già. Riguardo gli interni, l'architettura e la struttura del sito su HighLoad++ Pavel me lo disse nel 2010. Da allora molti server sono trapelati, quindi aggiorneremo le informazioni: le analizzeremo, ne estrarremo gli interni, le peseremo e esamineremo il dispositivo VK da un punto di vista tecnico.

Domande frequenti sull'architettura e sul funzionamento di VKontakte

Aleksej Akulovich (AterCattus) sviluppatore backend nel team VKontakte. La trascrizione di questo rapporto è una risposta collettiva alle domande più frequenti sul funzionamento della piattaforma, dell'infrastruttura, dei server e sull'interazione tra loro, ma non sullo sviluppo, vale a dire riguardo al ferro. Separatamente, sui database e su ciò che VK ha invece, sulla raccolta dei registri e sul monitoraggio dell'intero progetto nel suo insieme. Dettagli sotto il taglio.



Da più di quattro anni mi occupo di tutti i tipi di compiti legati al backend.

  • Caricamento, archiviazione, elaborazione, distribuzione di media: video, live streaming, audio, foto, documenti.
  • Infrastruttura, piattaforma, monitoraggio degli sviluppatori, log, cache regionali, CDN, protocollo RPC proprietario.
  • Integrazione con servizi esterni: notifiche push, analisi di link esterni, feed RSS.
  • Aiutare i colleghi con varie domande, le cui risposte richiedono l'immersione in un codice sconosciuto.

Durante questo periodo ho contribuito a molti componenti del sito. Voglio condividere questa esperienza.

Architettura generale

Tutto, come al solito, inizia con un server o un gruppo di server che accettano le richieste.

Server anteriore

Il front server accetta richieste tramite HTTPS, RTMP e WSS.

HTTPS - queste sono richieste per le versioni web principali e mobili del sito: vk.com e m.vk.com e altri client ufficiali e non ufficiali della nostra API: client mobili, messenger. Abbiamo un ricevimento RTMP-traffico per trasmissioni in diretta con front server separati e WSS- connessioni per Streaming API.

Per HTTPS e WSS sui server vale la pena nginx. Per le trasmissioni RTMP siamo recentemente passati alla nostra soluzione calcolo, ma va oltre lo scopo della relazione. Per la tolleranza agli errori, questi server pubblicizzano indirizzi IP comuni e agiscono in gruppi in modo che se si verifica un problema su uno dei server, le richieste degli utenti non vanno perse. Per HTTPS e WSS, questi stessi server crittografano il traffico per farsi carico di parte del carico della CPU.

Non parleremo ulteriormente di WSS e RTMP, ma solo delle richieste HTTPS standard, che di solito sono associate a un progetto web.

BACKEND

Dietro la parte anteriore di solito ci sono i server backend. Elaborano le richieste che il front server riceve dai client.

Essa server kPHP, su cui è in esecuzione il demone HTTP, perché HTTPS è già decrittografato. kPHP è un server su cui gira modelli con preforca: avvia un processo principale, un gruppo di processi secondari, passa loro i socket di ascolto e loro elaborano le loro richieste. In questo caso, i processi non vengono riavviati tra ogni richiesta dell'utente, ma semplicemente ripristinano il loro stato allo stato originale di valore zero, richiesta dopo richiesta, invece di riavviarsi.

Distribuzione del carico

Tutti i nostri backend non sono un enorme pool di macchine in grado di elaborare qualsiasi richiesta. Noi loro divisi in gruppi separati: generale, mobile, API, video, staging... Il problema su un gruppo separato di macchine non influenzerà tutti gli altri. In caso di problemi con il video, l'utente che ascolta la musica non verrà nemmeno a conoscenza del problema. A quale backend inviare la richiesta viene deciso da nginx in base alla configurazione.

Raccolta e riequilibrio delle metriche

Per capire quante auto dobbiamo avere in ogni gruppo, noi non fare affidamento su QPS. I backend sono diversi, hanno richieste diverse, ogni richiesta ha una diversa complessità di calcolo del QPS. Ecco perché noi operiamo con il concetto di carico sul server nel suo complesso: sulla CPU e sulle prestazioni.

Abbiamo migliaia di server di questo tipo. Ogni server fisico esegue un gruppo kPHP per riciclare tutti i core (perché kPHP è a thread singolo).

Server dei contenuti

CS o Content Server è un archivio. CS è un server che archivia file ed elabora anche file caricati e tutti i tipi di attività sincrone in background assegnategli dal frontend web principale.

Abbiamo decine di migliaia di server fisici che archiviano file. Gli utenti adorano caricare file e noi amiamo archiviarli e condividerli. Alcuni di questi server sono chiusi da speciali server pu/pp.

confezione/pp

Se hai aperto la scheda rete in VK, hai visto pu/pp.

Domande frequenti sull'architettura e sul funzionamento di VKontakte

Cos'è il pu/pp? Se chiudiamo un server dopo l'altro, ci sono due opzioni per caricare e scaricare un file sul server che è stato chiuso: direttamente attraverso http://cs100500.userapi.com/path o tramite server intermedio - http://pu.vk.com/c100500/path.

Pu è il nome storico per il caricamento delle foto e pp è il proxy delle foto. Cioè, un server serve per caricare le foto e un altro per caricare. Ora non solo vengono caricate le foto, ma il nome è stato preservato.

Questi server terminare le sessioni HTTPSper rimuovere il carico del processore dallo storage. Inoltre, poiché i file degli utenti vengono elaborati su questi server, meno informazioni sensibili sono archiviate su queste macchine, meglio è. Ad esempio, le chiavi di crittografia HTTPS.

Dato che le macchine sono chiuse dalle nostre altre macchine, possiamo permetterci di non dare loro IP esterni “bianchi” e dare "grigio". In questo modo abbiamo risparmiato sul pool IP e abbiamo garantito la protezione delle macchine dall'accesso esterno: semplicemente non c'è alcun IP per accedervi.

Resilienza sugli IP condivisi. In termini di tolleranza agli errori, lo schema funziona allo stesso modo: diversi server fisici hanno un IP fisico comune e l'hardware di fronte a loro sceglie dove inviare la richiesta. Parlerò di altre opzioni più tardi.

Il punto controverso è che in questo caso il client mantiene meno connessioni. Se più macchine hanno lo stesso IP - con lo stesso host: pu.vk.com o pp.vk.com, il browser client ha un limite al numero di richieste simultanee a un host. Ma nell’era dell’onnipresente HTTP/2, credo che questo non sia più così rilevante.

L’ovvio svantaggio del sistema è che deve farlo pompare tutto il traffico, che va allo storage, attraverso un altro server. Poiché pompiamo il traffico attraverso le macchine, non possiamo ancora pompare traffico pesante, ad esempio video, utilizzando lo stesso schema. Lo trasmettiamo direttamente: una connessione diretta separata per archivi separati specifici per i video. Trasmettiamo contenuti più leggeri tramite un proxy.

Non molto tempo fa abbiamo ottenuto una versione migliorata del proxy. Ora ti dirò in cosa differiscono da quelli ordinari e perché è necessario.

Dom.

Nel settembre 2017 Oracle, che in precedenza aveva acquistato Sun, licenziato un numero enorme di dipendenti Sun. Possiamo dire che in questo momento l'azienda ha cessato di esistere. Scegliendo un nome per il nuovo sistema, i nostri amministratori hanno deciso di rendere omaggio alla memoria di questa azienda e hanno chiamato il nuovo sistema Sole. Tra di noi la chiamiamo semplicemente “soli”.

Domande frequenti sull'architettura e sul funzionamento di VKontakte

la pp ha avuto qualche problema. Un IP per gruppo: cache inefficace. Diversi server fisici condividono un indirizzo IP comune e non è possibile controllare a quale server verrà indirizzata la richiesta. Pertanto, se diversi utenti vengono per lo stesso file, se su questi server è presente una cache, il file finisce nella cache di ciascun server. Questo è uno schema molto inefficiente, ma non si può fare nulla.

Di conseguenza - non possiamo condividere il contenuto, perché non possiamo selezionare un server specifico per questo gruppo: hanno un IP comune. Anche per alcuni motivi interni che abbiamo non era possibile installare tali server nelle regioni. Stavano solo a San Pietroburgo.

Con i soli abbiamo cambiato il sistema di selezione. Ora abbiamo instradamento anycast: routing dinamico, anycast, demone di autocontrollo. Ogni server ha il proprio IP individuale, ma una sottorete comune. Tutto è configurato in modo tale che se un server si guasta, il traffico viene distribuito automaticamente sugli altri server dello stesso gruppo. Ora è possibile selezionare un server specifico, nessuna memorizzazione nella cache ridondantee l'affidabilità non è stata influenzata.

Supporto per il peso. Ora possiamo permetterci di installare macchine di diversa potenza a seconda delle necessità e anche, in caso di problemi temporanei, di modificare i pesi dei “soli” funzionanti per ridurre il carico su di essi, in modo che “riposino” e riprendano a lavorare.

Sharding per ID contenuto. Una cosa divertente dello sharding: di solito partizioniamo il contenuto in modo che utenti diversi accedano allo stesso file attraverso lo stesso "sole" in modo che abbiano una cache comune.

Recentemente abbiamo lanciato l'applicazione "Clover". Si tratta di un quiz online in una trasmissione dal vivo, in cui l'ospite pone domande e gli utenti rispondono in tempo reale, scegliendo le opzioni. L'app dispone di una chat in cui gli utenti possono chattare. Può connettersi contemporaneamente alla trasmissione più di 100mila persone. Tutti scrivono messaggi che vengono inviati a tutti i partecipanti e un avatar accompagna il messaggio. Se 100mila persone vengono per un avatar in un "sole", a volte può rotolare dietro una nuvola.

Per resistere alle esplosioni di richieste per lo stesso file, è per un certo tipo di contenuto che attiviamo uno stupido schema che distribuisce i file su tutti i "soli" disponibili nella regione.

Sole dall'interno

Proxy inverso su nginx, cache nella RAM o su dischi veloci Optane/NVMe. Esempio: http://sun4-2.userapi.com/c100500/path — un collegamento al “sole”, che si trova nella quarta regione, il secondo gruppo di server. Chiude il file di percorso, che si trova fisicamente sul server 100500.

Cache

Aggiungiamo un ulteriore nodo al nostro schema architettonico: l'ambiente di memorizzazione nella cache.

Domande frequenti sull'architettura e sul funzionamento di VKontakte

Di seguito è riportato lo schema di layout cache regionali, ce ne sono circa 20. Questi sono i luoghi in cui si trovano cache e "soli", che possono memorizzare nella cache il traffico attraverso se stessi.

Domande frequenti sull'architettura e sul funzionamento di VKontakte

Si tratta di memorizzazione nella cache di contenuti multimediali; qui non vengono archiviati dati utente: solo musica, video, foto.

Per determinare la regione dell'utente, noi raccogliamo i prefissi di rete BGP annunciati nelle regioni. In caso di fallback, dobbiamo anche analizzare il database geoip se non riusciamo a trovare l'IP tramite prefissi. Determiniamo la regione in base all'IP dell'utente. Nel codice possiamo osservare una o più regioni dell'utente, ovvero i punti a cui è geograficamente più vicino.

Come funziona?

Contiamo la popolarità dei file in base alla regione. C'è un numero della cache regionale in cui si trova l'utente e un identificatore di file: prendiamo questa coppia e incrementiamo la valutazione con ogni download.

Allo stesso tempo, i demoni - servizi nelle regioni - di tanto in tanto arrivano all'API e dicono: “Sono una tale cache, dammi un elenco dei file più popolari nella mia regione che non sono ancora su di me. " L'API fornisce una serie di file ordinati per classificazione, il demone li scarica, li porta nelle regioni e da lì consegna i file. Questa è la differenza fondamentale tra pu/pp e Sun dalle cache: danno immediatamente il file a se stessi, anche se questo file non è nella cache, e la cache prima scarica il file su se stessa e poi inizia a restituirlo.

In questo caso otteniamo contenuti più vicini agli utenti e distribuire il carico di rete. Ad esempio, solo dalla cache di Mosca distribuiamo più di 1 Tbit/s nelle ore di punta.

Ma ci sono problemi - i server cache non sono di gomma. Per i contenuti molto popolari, a volte non c'è abbastanza rete per un server separato. I nostri server cache sono da 40-50 Gbit/s, ma ci sono contenuti che intasano completamente tale canale. Ci stiamo muovendo verso l'implementazione dell'archiviazione di più di una copia dei file più diffusi nella regione. Spero che lo implementeremo entro la fine dell’anno.

Abbiamo esaminato l'architettura generale.

  • Front server che accettano richieste.
  • Backend che elaborano le richieste.
  • Archivi chiusi da due tipi di proxy.
  • Cache regionali.

Cosa manca in questo diagramma? Naturalmente, i database in cui memorizziamo i dati.

Database o motori

Non li chiamiamo database, ma motori: motori, perché praticamente non disponiamo di database nel senso generalmente accettato.

Domande frequenti sull'architettura e sul funzionamento di VKontakte

Questa è una misura necessaria.. Ciò è accaduto perché nel 2008-2009, quando VK ebbe una crescita esplosiva in popolarità, il progetto lavorava interamente su MySQL e Memcache e ci furono problemi. MySQL amava andare in crash e corrompere i file, dopodiché non si riprendeva più, e Memcache gradualmente peggiorava in termini di prestazioni e doveva essere riavviato.

Si scopre che il progetto sempre più popolare aveva una memoria persistente, che corrompe i dati, e una cache, che rallenta. In tali condizioni è difficile sviluppare un progetto in crescita. Si è deciso di provare a riscrivere sulle nostre biciclette gli aspetti critici su cui si è concentrato il progetto.

La soluzione ha avuto successo. C'era l'opportunità di farlo, ma anche un'estrema necessità, perché a quel tempo non esistevano altri modi di ridimensionamento. Non c'erano molti database, NoSQL non esisteva ancora, c'erano solo MySQL, Memcache, PostrgreSQL - e basta.

Funzionamento universale. Lo sviluppo è stato guidato dal nostro team di sviluppatori C e tutto è stato fatto in modo coerente. Indipendentemente dal motore, avevano tutti più o meno lo stesso formato di file scritto su disco, gli stessi parametri di lancio, elaboravano i segnali allo stesso modo e si comportavano più o meno allo stesso modo in caso di situazioni limite e problemi. Con la crescita dei motori, è conveniente per gli amministratori gestire il sistema: non c'è nessuno zoo che debba essere mantenuto e devono imparare di nuovo come gestire ogni nuovo database di terze parti, il che ha permesso di aumentare rapidamente e comodamente il loro numero.

Tipi di motori

Il team ha scritto parecchi motori. Eccone solo alcuni: amico, suggerimenti, immagine, ipdb, lettere, elenchi, registri, memcached, meowdb, notizie, nostradamus, foto, playlist, pmemcached, sandbox, ricerca, archiviazione, Mi piace, attività,...

Per ogni attività che richiede una struttura dati specifica o elabora richieste atipiche, il team C scrive un nuovo motore. Perché no.

Abbiamo un motore separato memcached, che è simile a quello normale, ma con un sacco di chicche e che non rallenta. Non ClickHouse, ma funziona ugualmente. Disponibile separatamente pmemcached - E ' memcached persistente, che può anche memorizzare i dati su disco, inoltre, che si inserisce nella RAM, in modo da non perdere i dati al riavvio. Esistono vari motori per le singole attività: code, elenchi, set: tutto ciò che richiede il nostro progetto.

Cluster

Dal punto di vista del codice, non è necessario pensare ai motori o ai database come processi, entità o istanze. Il codice funziona specificatamente con i cluster, con i gruppi di motori - un tipo per cluster. Diciamo che esiste un cluster memcached: è solo un gruppo di macchine.

Non è necessario che il codice conosca la posizione fisica, le dimensioni o il numero di server. Va al cluster utilizzando un determinato identificatore.

Affinché funzioni, è necessario aggiungere un'altra entità che si trova tra il codice e i motori: delega.

proxy RPC

Procura autobus di collegamento, su cui gira quasi l'intero sito. Allo stesso tempo abbiamo nessuna individuazione del servizio — esiste invece una configurazione per questo proxy, che conosce la posizione di tutti i cluster e di tutti i frammenti di questo cluster. Questo è ciò che fanno gli amministratori.

Ai programmatori non interessa affatto quanto, dove e quanto costa: vanno semplicemente al cluster. Questo ci permette molto. Quando riceve una richiesta, il proxy reindirizza la richiesta, sapendo dove lo determina da solo.

Domande frequenti sull'architettura e sul funzionamento di VKontakte

In questo caso, il proxy è un punto di protezione contro il fallimento del servizio. Se qualche motore rallenta o si blocca, il proxy lo capisce e risponde di conseguenza al lato client. Ciò consente di rimuovere il timeout: il codice non attende la risposta del motore, ma capisce che non funziona e deve comportarsi in qualche modo diversamente. Il codice deve essere preparato al fatto che i database non sempre funzionano.

Implementazioni specifiche

A volte vogliamo ancora avere una sorta di soluzione non standard come motore. Allo stesso tempo, si è deciso di non utilizzare il nostro proxy rpc già pronto, creato appositamente per i nostri motori, ma di creare un proxy separato per l'attività.

Per MySQL, che abbiamo ancora qua e là, utilizziamo db-proxy e per ClickHouse - Gattino.

Funziona generalmente così. C'è un certo server, esegue kPHP, Go, Python - in generale, qualsiasi codice che possa utilizzare il nostro protocollo RPC. Il codice viene eseguito localmente su un proxy RPC: ciascun server in cui si trova il codice esegue il proprio proxy locale. Su richiesta, il proxy capisce dove rivolgersi.

Domande frequenti sull'architettura e sul funzionamento di VKontakte

Se un motore vuole passare a un altro, anche se è vicino, passa attraverso un proxy, perché il vicino potrebbe trovarsi in un altro data center. Il motore non dovrebbe fare affidamento sulla conoscenza della posizione di qualcosa di diverso da se stesso: questa è la nostra soluzione standard. Ma ovviamente ci sono delle eccezioni :)

Un esempio di uno schema TL in base al quale funzionano tutti i motori.

memcache.not_found                                = memcache.Value;
memcache.strvalue	value:string flags:int = memcache.Value;
memcache.addOrIncr key:string flags:int delay:int value:long = memcache.Value;

tasks.task
    fields_mask:#
    flags:int
    tag:%(Vector int)
    data:string
    id:fields_mask.0?long
    retries:fields_mask.1?int
    scheduled_time:fields_mask.2?int
    deadline:fields_mask.3?int
    = tasks.Task;
 
tasks.addTask type_name:string queue_id:%(Vector int) task:%tasks.Task = Long;

Questo è un protocollo binario, il cui analogo più vicino è protobuff. Lo schema prescrive campi facoltativi, tipi complessi: estensioni di scalari incorporati e query. Tutto funziona secondo questo protocollo.

RPC su TL su TCP/UDP... UDP?

Disponiamo di un protocollo RPC per l'esecuzione delle richieste del motore che viene eseguito sullo schema TL. Tutto funziona tramite una connessione TCP/UDP. TCP è comprensibile, ma perché abbiamo spesso bisogno di UDP?

L'UDP aiuta evitare il problema di un numero enorme di connessioni tra server. Se ogni server ha un proxy RPC e, in generale, può accedere a qualsiasi motore, ci sono decine di migliaia di connessioni TCP per server. C'è un carico, ma è inutile. Nel caso dell'UDP questo problema non esiste.

Nessun handshake TCP ridondante. Questo è un problema tipico: quando viene avviato un nuovo motore o un nuovo server, vengono stabilite molte connessioni TCP contemporaneamente. Per richieste leggere e di piccole dimensioni, ad esempio il payload UDP, tutta la comunicazione tra il codice e il motore è due pacchetti UDP: uno vola in una direzione, il secondo nell'altra. Un viaggio di andata e ritorno e il codice ha ricevuto una risposta dal motore senza stretta di mano.

Sì, funziona tutto e basta con una percentuale molto piccola di perdita di pacchetti. Il protocollo supporta ritrasmissioni e timeout, ma se perdiamo molto, otterremo quasi TCP, il che non è redditizio. Non portiamo l’UDP attraverso gli oceani.

Abbiamo migliaia di server di questo tipo e lo schema è lo stesso: un pacchetto di motori è installato su ciascun server fisico. Sono per lo più a thread singolo per essere eseguiti il ​​più rapidamente possibile senza blocchi e sono suddivisi come soluzioni a thread singolo. Allo stesso tempo, non abbiamo nulla di più affidabile di questi motori e viene prestata molta attenzione all'archiviazione persistente dei dati.

Archiviazione dati persistente

I motori scrivono binlog. Un binlog è un file alla fine del quale viene aggiunto un evento per un cambiamento di stato o di dati. In diverse soluzioni viene chiamato diversamente: log binario, WAL, AOF, ma il principio è lo stesso.

Per evitare che il motore rilegga l'intero binlog per molti anni al riavvio, i motori scrivono istantanee: stato attuale. Se necessario, leggono prima da esso e poi finiscono di leggere dal binlog. Tutti i binlog sono scritti nello stesso formato binario, secondo lo schema TL, in modo che gli amministratori possano amministrarli allo stesso modo con i loro strumenti. Non c'è bisogno di istantanee. C'è un'intestazione generale che indica quale istantanea è int, magia del motore e quale corpo non è importante per nessuno. Si tratta di un problema con il motore che ha registrato l'istantanea.

Descriverò rapidamente il principio di funzionamento. C'è un server su cui gira il motore. Apre un nuovo binlog vuoto per la scrittura e scrive un evento per modificarlo.

Domande frequenti sull'architettura e sul funzionamento di VKontakte

Ad un certo punto, decide di scattare lui stesso una foto oppure riceve un segnale. Il server crea un nuovo file, scrive il suo intero stato al suo interno, aggiunge la dimensione corrente del binlog - offset - alla fine del file e continua a scrivere ulteriormente. Non viene creato un nuovo binlog.

Domande frequenti sull'architettura e sul funzionamento di VKontakte

Ad un certo punto, al riavvio del motore, sul disco saranno presenti sia un binlog che uno snapshot. Il motore legge l'intera istantanea e ne aumenta lo stato ad un certo punto.

Domande frequenti sull'architettura e sul funzionamento di VKontakte

Legge la posizione al momento della creazione dello snapshot e la dimensione del binlog.

Domande frequenti sull'architettura e sul funzionamento di VKontakte

Legge la fine del binlog per ottenere lo stato corrente e continua a scrivere ulteriori eventi. Questo è uno schema semplice; tutti i nostri motori funzionano secondo esso.

Replica dei dati

Di conseguenza, la replica dei dati nel nostro basato su dichiarazioni — nel binlog non scriviamo alcun cambiamento di pagina, ma proprio questo cambia richieste. Molto simile a quello che arriva in rete, solo leggermente modificato.

Lo stesso schema viene utilizzato non solo per la replica, ma anche per creare backup. Abbiamo un motore: un maestro della scrittura che scrive nel binlog. In qualsiasi altro posto in cui gli amministratori lo hanno configurato, questo binlog viene copiato e basta: abbiamo un backup.

Domande frequenti sull'architettura e sul funzionamento di VKontakte

Se avete bisogno replica di letturaPer ridurre il carico di lettura della CPU viene semplicemente lanciato il motore di lettura che legge la fine del binlog ed esegue questi comandi localmente.

Il ritardo qui è molto piccolo ed è possibile scoprire quanto la replica resta indietro rispetto al master.

Sharding dei dati nel proxy RPC

Come funziona lo sharding? Come fa il proxy a capire a quale frammento del cluster inviare? Il codice non dice: "Invia per 15 frammenti!" - no, questo viene fatto dal proxy.

Lo schema più semplice è firstint — il primo numero della richiesta.

get(photo100_500) => 100 % N.

Questo è un esempio di un semplice protocollo di testo memcached ma, ovviamente, le query possono essere complesse e strutturate. L'esempio prende il primo numero nella query e il resto quando viene diviso per la dimensione del cluster.

Ciò è utile quando vogliamo avere la località dei dati di una singola entità. Supponiamo che 100 sia un ID utente o gruppo e vogliamo che tutti i dati di un'entità si trovino su un unico shard per query complesse.

Se non ci interessa il modo in cui le richieste vengono distribuite nel cluster, c'è un'altra opzione: hashing dell'intero shard.

hash(photo100_500) => 3539886280 % N

Otteniamo anche l'hash, il resto della divisione e il numero del frammento.

Entrambe queste opzioni funzionano solo se siamo preparati al fatto che quando aumentiamo la dimensione del cluster, lo divideremo o lo aumenteremo più volte. Ad esempio, avevamo 16 frammenti, non ne abbiamo abbastanza, ne vogliamo di più: possiamo tranquillamente ottenerne 32 senza tempi di inattività. Se non vogliamo aumentare i multipli, ci saranno tempi di inattività, perché non saremo in grado di dividere tutto con precisione senza perdite. Queste opzioni sono utili, ma non sempre.

Se dobbiamo aggiungere o rimuovere un numero arbitrario di server, utilizziamo Hashing coerente sull'anello alla Ketama. Ma allo stesso tempo, perdiamo completamente la località dei dati; dobbiamo unire la richiesta al cluster in modo che ogni pezzo restituisca la propria piccola risposta, e quindi unire le risposte al proxy.

Ci sono richieste super specifiche. Appare così: il proxy RPC riceve la richiesta, determina a quale cluster andare e determina lo shard. Quindi ci sono master di scrittura oppure, se il cluster dispone del supporto di replica, invia a una replica su richiesta. La delega fa tutto questo.

Domande frequenti sull'architettura e sul funzionamento di VKontakte

logs

Scriviamo i log in diversi modi. Quello più ovvio e semplice è scrivere i log su memcache.

ring-buffer: prefix.idx = line

C'è un prefisso chiave - il nome del registro, una riga, e c'è la dimensione di questo registro - il numero di righe. Prendiamo un numero casuale da 0 al numero di righe meno 1. La chiave in memcache è un prefisso concatenato con questo numero casuale. Salviamo la riga di registro e l'ora corrente nel valore.

Quando è necessario leggere i log, eseguiamo Ottieni più tutte le chiavi, ordinate per ora, e ottenere così un registro della produzione in tempo reale. Lo schema viene utilizzato quando è necessario eseguire il debug di qualcosa in produzione in tempo reale, senza interrompere nulla, senza interrompere o consentire il traffico verso altre macchine, ma questo registro non dura a lungo.

Per l'archiviazione affidabile dei registri disponiamo di un motore logs-motore. Questo è esattamente il motivo per cui è stato creato ed è ampiamente utilizzato in un gran numero di cluster. Il cluster più grande che conosco memorizza 600 TB di log compressi.

Il motore è molto vecchio, ci sono grappoli che hanno già 6-7 anni. Ci sono problemi con esso che stiamo cercando di risolvere, ad esempio, abbiamo iniziato a utilizzare attivamente ClickHouse per archiviare i registri.

Raccolta dei log in ClickHouse

Questo diagramma mostra come entriamo nei nostri motori.

Domande frequenti sull'architettura e sul funzionamento di VKontakte

Esiste un codice che viene inviato localmente tramite RPC al proxy RPC e capisce dove andare al motore. Se vogliamo scrivere log in ClickHouse, dobbiamo modificare due parti in questo schema:

  • sostituire alcuni motori con ClickHouse;
  • sostituire il proxy RPC, che non può accedere a ClickHouse, con qualche soluzione che possa farlo e tramite RPC.

Il motore è semplice: lo sostituiamo con un server o un cluster di server con ClickHouse.

E per andare su ClickHouse, l'abbiamo fatto KittenHouse. Se passiamo direttamente da KittenHouse a ClickHouse, non ce la farà. Anche senza richieste, si accumula dalle connessioni HTTP di un numero enorme di macchine. Affinché lo schema funzioni, su un server con ClickHouse viene generato il proxy inverso locale, che è scritto in modo tale da poter sopportare i volumi di connessioni richiesti. Può anche bufferizzare i dati al suo interno in modo relativamente affidabile.

Domande frequenti sull'architettura e sul funzionamento di VKontakte

A volte non vogliamo implementare lo schema RPC in soluzioni non standard, ad esempio in nginx. Pertanto, KittenHouse ha la capacità di ricevere registri tramite UDP.

Domande frequenti sull'architettura e sul funzionamento di VKontakte

Se il mittente e il destinatario dei log lavorano sulla stessa macchina, la probabilità di perdere un pacchetto UDP all'interno dell'host locale è piuttosto bassa. Come compromesso tra la necessità di implementare RPC in una soluzione di terze parti e l'affidabilità, utilizziamo semplicemente l'invio UDP. Torneremo più avanti su questo schema.

Monitoraggio

Abbiamo due tipi di log: quelli raccolti dagli amministratori sui loro server e quelli scritti dagli sviluppatori dal codice. Corrispondono a due tipi di metriche: sistema e prodotto.

Metriche di sistema

Funziona su tutti i nostri server Dati di rete, che raccoglie le statistiche e le invia a Grafite Carbon. Pertanto, come sistema di archiviazione viene utilizzato ClickHouse e non Whisper, ad esempio. Se necessario, puoi leggere direttamente da ClickHouse o utilizzare graminacee per metriche, grafici e report. Come sviluppatori, abbiamo abbastanza accesso a Netdata e Grafana.

Metriche del prodotto

Per comodità, abbiamo scritto molte cose. Ad esempio, esiste una serie di funzioni ordinarie che consentono di scrivere conteggi, valori UniqueCounts nelle statistiche, che vengono inviati da qualche altra parte.

statlogsCountEvent   ( ‘stat_name’,            $key1, $key2, …)
statlogsUniqueCount ( ‘stat_name’, $uid,    $key1, $key2, …)
statlogsValuetEvent  ( ‘stat_name’, $value, $key1, $key2, …)

$stats = statlogsStatData($params)

Successivamente, possiamo utilizzare filtri di ordinamento e raggruppamento e fare tutto ciò che vogliamo dalle statistiche: creare grafici, configurare Watchdog.

Scriviamo molto molte metriche il numero di eventi varia da 600 miliardi a 1 trilione al giorno. Vogliamo però mantenerli almeno un paio d'anniper comprendere le tendenze delle metriche. Mettere tutto insieme è un grosso problema che non abbiamo ancora risolto. Ti dirò come ha funzionato negli ultimi anni.

Abbiamo funzioni che scrivono queste metriche alla memcache localeper ridurre il numero di iscrizioni. Lanciato localmente una volta in un breve periodo di tempo demone delle statistiche raccoglie tutti i record. Successivamente, il demone unisce le metriche in due livelli di server raccoglitori di log, che aggrega le statistiche di un gruppo delle nostre macchine in modo che lo strato dietro di esse non muoia.

Domande frequenti sull'architettura e sul funzionamento di VKontakte

Se necessario, possiamo scrivere direttamente ai raccoglitori di log.

Domande frequenti sull'architettura e sul funzionamento di VKontakte

Ma scrivere dal codice direttamente ai collector, bypassando stas-daemom, è una soluzione scarsamente scalabile perché aumenta il carico sul collector. La soluzione è adatta solo se per qualche motivo non possiamo avviare il demone memcache stats sulla macchina, o si è bloccato e siamo andati direttamente.

Successivamente, i raccoglitori di log uniscono le statistiche in meowDB - questo è il nostro database, che può anche memorizzare metriche.

Domande frequenti sull'architettura e sul funzionamento di VKontakte

Quindi possiamo effettuare selezioni binarie "quasi SQL" dal codice.

Domande frequenti sull'architettura e sul funzionamento di VKontakte

Esperimento

Nell'estate del 2018, abbiamo organizzato un hackathon interno ed è nata l'idea di provare a sostituire la parte rossa del diagramma con qualcosa che potesse memorizzare le metriche in ClickHouse. Abbiamo log su ClickHouse: perché non provarlo?

Domande frequenti sull'architettura e sul funzionamento di VKontakte

Avevamo uno schema che scriveva i log tramite KittenHouse.

Domande frequenti sull'architettura e sul funzionamento di VKontakte

Abbiamo deciso aggiungi un'altra "*Casa" al diagramma, che riceverà esattamente le metriche nel formato in cui le scrive il nostro codice tramite UDP. Quindi questa *House li trasforma in inserti, come tronchi, cosa che KittenHouse capisce. Può consegnare perfettamente questi registri a ClickHouse, che dovrebbe essere in grado di leggerli.

Domande frequenti sull'architettura e sul funzionamento di VKontakte

Lo schema con il database memcache, stats-daemon e logs-collectors è sostituito con questo.

Domande frequenti sull'architettura e sul funzionamento di VKontakte

Lo schema con il database memcache, stats-daemon e logs-collectors è sostituito con questo.

  • C'è un invio dal codice qui, che è scritto localmente in StatsHouse.
  • StatsHouse scrive i parametri UDP, già convertiti in inserti SQL, su KittenHouse in batch.
  • KittenHouse li invia a ClickHouse.
  • Se vogliamo leggerli, lo leggiamo bypassando StatsHouse, direttamente da ClickHouse utilizzando il normale SQL.

è ancora? esperimento, ma ci piace come va a finire. Se risolviamo i problemi con lo schema, forse lo utilizzeremo completamente. Personalmente lo spero.

Guida non risparmia ferro. Sono necessari meno server, non sono necessari demoni di statistiche locali e raccoglitori di log, ma ClickHouse richiede un server più grande di quelli nello schema attuale. Sono necessari meno server, ma devono essere più costosi e più potenti.

Distribuire

Innanzitutto, diamo un'occhiata alla distribuzione di PHP. Ci stiamo sviluppando in git: utilizzo GitLab и TeamCity per la distribuzione. I rami di sviluppo vengono accorpati nel ramo master, dal master per il testing vengono fusi nello staging e dallo staging alla produzione.

Prima della distribuzione, vengono presi il ramo di produzione corrente e quello precedente e al loro interno vengono considerati i file diff: modifiche: creato, eliminato, modificato. Questa modifica viene registrata nel binlog di uno speciale motore copyfast, che può replicare rapidamente le modifiche sull'intero parco server. Ciò che viene utilizzato qui non è copiare direttamente, ma replica di pettegolezzi, quando un server invia modifiche ai suoi vicini più vicini, questi ai loro vicini e così via. Ciò consente di aggiornare il codice in decine e unità di secondi su tutta la flotta. Quando la modifica raggiunge la replica locale, applica queste patch alla sua file system locale. Anche il rollback viene eseguito secondo lo stesso schema.

Utilizziamo molto anche kPHP e ha anche il proprio sviluppo git secondo lo schema qui sopra. Da questo Binario del server HTTP, allora non possiamo produrre diff: il file binario di rilascio pesa centinaia di MB. Pertanto, qui c'è un'altra opzione: la versione su cui viene scritta binlog copyfast. Con ogni build aumenta e aumenta anche durante il rollback. Versione replicato sui server. I copyfast locali vedono che una nuova versione è entrata nel binlog e, con la stessa replica dei pettegolezzi, prendono per sé l'ultima versione del binario, senza stancare il nostro server principale, ma distribuendo attentamente il carico sulla rete. Ciò che segue rilancio grazioso per la nuova versione.

Per i nostri motori, anch’essi essenzialmente binari, lo schema è molto simile:

  • ramo principale di git;
  • ingresso binario . Deb;
  • la versione è scritta su binlog copyfast;
  • replicato sui server;
  • il server estrae un nuovo .dep;
  • dpkg -i;
  • grazioso rilancio alla nuova versione.

La differenza è che il nostro binario è confezionato in archivi . Deb, e quando li pompano fuori dpkg -i vengono inseriti nel sistema. Perché kPHP viene distribuito come binario e i motori vengono distribuiti come dpkg? È andata così. Funziona: non toccarlo.

Link utili:

Alexey Akulovich è uno di quelli che, come parte del comitato del programma, aiutano PHPRussia il 17 maggio diventerà il più grande evento per gli sviluppatori PHP degli ultimi tempi. Guarda che bel PC abbiamo, cosa Altoparlanti (due di loro stanno sviluppando il core PHP!) - sembra qualcosa da non perdere se scrivi PHP.

Fonte: habr.com

Aggiungi un commento