FAQ zur Architektur und Arbeit von VKontakte

Die Entstehungsgeschichte von VKontakte ist auf Wikipedia zu finden; sie wurde von Pavel selbst erzählt. Es scheint, dass jeder sie bereits kennt. Über die Interna, Architektur und Struktur der Website auf HighLoad++ Pavel erzählte es mir im Jahr 2010. Seitdem sind viele Server durchgesickert, daher werden wir die Informationen aktualisieren: Wir werden sie sezieren, das Innere herausnehmen, es wiegen und uns das VK-Gerät aus technischer Sicht ansehen.

FAQ zur Architektur und Arbeit von VKontakte

Alexey Akulovich (AterCattus) Backend-Entwickler im VKontakte-Team. Das Transkript dieses Berichts ist eine kollektive Antwort auf häufig gestellte Fragen zum Betrieb der Plattform, der Infrastruktur, den Servern und der Interaktion zwischen ihnen, jedoch nicht zur Entwicklung über Eisen. Separat über Datenbanken und was VK stattdessen hat, über das Sammeln von Protokollen und die Überwachung des gesamten Projekts als Ganzes. Details unter dem Schnitt.



Seit mehr als vier Jahren beschäftige ich mich mit allen möglichen Aufgaben rund um das Backend.

  • Hochladen, Speichern, Bearbeiten, Verteilen von Medien: Video, Live-Streaming, Audio, Fotos, Dokumente.
  • Infrastruktur, Plattform, Entwicklerüberwachung, Protokolle, regionale Caches, CDN, proprietäres RPC-Protokoll.
  • Integration mit externen Diensten: Push-Benachrichtigungen, Analyse externer Links, RSS-Feed.
  • Helfen Sie Kollegen bei verschiedenen Fragen, deren Beantwortung das Eintauchen in unbekannten Code erfordert.

Während dieser Zeit war ich an vielen Komponenten der Website beteiligt. Ich möchte diese Erfahrung teilen.

Allgemeine Architektur

Alles beginnt wie üblich mit einem Server oder einer Gruppe von Servern, die Anfragen entgegennehmen.

Frontserver

Der Frontserver akzeptiert Anfragen über HTTPS, RTMP und WSS.

HTTPS - Dies sind Anfragen für die Haupt- und mobilen Webversionen der Website: vk.com und m.vk.com sowie für andere offizielle und inoffizielle Clients unserer API: mobile Clients, Messenger. Wir haben einen Empfang RTMP-Verkehr für Live-Übertragungen mit separaten Frontservern und WSS- Verbindungen für die Streaming-API.

Für HTTPS und WSS auf Servern lohnt es sich . Für RTMP-Übertragungen sind wir kürzlich auf unsere eigene Lösung umgestiegen Stein, aber es geht über den Rahmen des Berichts hinaus. Aus Gründen der Fehlertoleranz geben diese Server gemeinsame IP-Adressen bekannt und agieren in Gruppen, sodass Benutzeranfragen nicht verloren gehen, wenn auf einem der Server ein Problem auftritt. Bei HTTPS und WSS verschlüsseln dieselben Server den Datenverkehr, um einen Teil der CPU-Last auf sich zu nehmen.

Wir werden nicht weiter auf WSS und RTMP eingehen, sondern nur auf Standard-HTTPS-Anfragen, die normalerweise mit einem Webprojekt verbunden sind.

Backend

Hinter der Front verbergen sich meist Backend-Server. Sie verarbeiten Anfragen, die der Frontserver von Clients erhält.

Es kPHP-Server, auf dem der HTTP-Daemon läuft, da HTTPS bereits entschlüsselt ist. kPHP ist ein Server, der darauf läuft Prefork-Modelle: Startet einen Master-Prozess, eine Reihe von untergeordneten Prozessen, übergibt ihnen Listening-Sockets und sie verarbeiten ihre Anforderungen. In diesem Fall werden Prozesse nicht zwischen jeder Anfrage des Benutzers neu gestartet, sondern ihr Zustand wird einfach auf den ursprünglichen Nullwertzustand zurückgesetzt – Anfrage für Anfrage, anstatt neu zu starten.

Lastverteilung

Alle unsere Backends sind kein riesiger Maschinenpool, der jede Anfrage bearbeiten kann. Wir sie in separate Gruppen aufgeteilt: allgemein, mobil, API, Video, Staging ... Das Problem auf einer separaten Gruppe von Computern wirkt sich nicht auf alle anderen aus. Bei Problemen mit dem Video weiß der Benutzer, der Musik hört, nicht einmal von den Problemen. An welches Backend die Anfrage gesendet werden soll, entscheidet Nginx auf der Vorderseite entsprechend der Konfiguration.

Erfassung und Neuausrichtung von Metriken

Um zu verstehen, wie viele Autos wir in jeder Gruppe haben müssen, müssen wir Verlassen Sie sich nicht auf QPS. Die Backends sind unterschiedlich, sie haben unterschiedliche Anfragen, jede Anfrage hat eine andere Komplexität bei der Berechnung von QPS. Deshalb sind wir Wir arbeiten mit dem Konzept der Belastung des gesamten Servers – der CPU und der Leistung.

Wir haben Tausende solcher Server. Jeder physische Server führt eine kPHP-Gruppe aus, um alle Kerne zu recyceln (da kPHP Single-Threaded ist).

Inhaltsserver

CS oder Content Server ist ein Speicher. CS ist ein Server, der Dateien speichert und auch hochgeladene Dateien sowie alle möglichen synchronen Hintergrundaufgaben verarbeitet, die ihm vom Haupt-Web-Frontend zugewiesen werden.

Wir verfügen über Zehntausende physische Server, auf denen Dateien gespeichert sind. Benutzer lieben es, Dateien hochzuladen, und wir lieben es, sie zu speichern und zu teilen. Einige dieser Server werden durch spezielle PU/PP-Server geschlossen.

pu/pp

Wenn Sie die Registerkarte „Netzwerk“ in VK geöffnet haben, wurde pu/pp angezeigt.

FAQ zur Architektur und Arbeit von VKontakte

Was ist pu/pp? Wenn wir einen Server nach dem anderen schließen, gibt es zwei Möglichkeiten, eine Datei auf den geschlossenen Server hoch- und herunterzuladen: direkt durch http://cs100500.userapi.com/path oder über Zwischenserver - http://pu.vk.com/c100500/path.

Pu ist der historische Name für das Hochladen von Fotos und pp ist ein Foto-Proxy. Das heißt, ein Server dient zum Hochladen von Fotos und ein anderer zum Hochladen. Jetzt werden nicht nur Fotos geladen, sondern der Name bleibt erhalten.

Diese Server HTTPS-Sitzungen beendenum die Prozessorlast aus dem Speicher zu entfernen. Da Benutzerdateien auf diesen Servern verarbeitet werden, ist es außerdem umso besser, je weniger vertrauliche Informationen auf diesen Computern gespeichert sind. Zum Beispiel HTTPS-Verschlüsselungsschlüssel.

Da die Maschinen durch unsere anderen Maschinen geschlossen sind, können wir es uns leisten, ihnen keine „weißen“ externen IPs zu geben gib "grau". Auf diese Weise haben wir den IP-Pool eingespart und die Maschinen garantiert vor Zugriffen von außen geschützt – es gibt einfach keine IP, die hineingelangen könnte.

Ausfallsicherheit über gemeinsam genutzte IPs. In Bezug auf die Fehlertoleranz funktioniert das Schema gleich – mehrere physische Server haben eine gemeinsame physische IP und die Hardware vor ihnen wählt, wohin die Anfrage gesendet werden soll. Ich werde später über andere Optionen sprechen.

Der umstrittene Punkt ist, dass in diesem Fall Der Client hält weniger Verbindungen. Wenn für mehrere Maschinen dieselbe IP vorhanden ist – mit demselben Host: pu.vk.com oder pp.vk.com – ist die Anzahl der gleichzeitigen Anfragen an einen Host für den Client-Browser begrenzt. Aber im Zeitalter des allgegenwärtigen HTTP/2 glaube ich, dass dies nicht mehr so ​​relevant ist.

Der offensichtliche Nachteil des Systems besteht darin, dass es so sein muss Pumpen Sie den gesamten Verkehr, die über einen anderen Server in den Speicher gelangt. Da wir Datenverkehr durch Maschinen pumpen, können wir mit demselben Schema noch keinen starken Datenverkehr, beispielsweise Video, pumpen. Wir übertragen es direkt – eine separate Direktverbindung für separate Speicher speziell für Video. Wir übertragen leichtere Inhalte über einen Proxy.

Vor nicht allzu langer Zeit haben wir eine verbesserte Version von Proxy bekommen. Jetzt erzähle ich Ihnen, wie sie sich von gewöhnlichen unterscheiden und warum dies notwendig ist.

Sun

Im September 2017 hat Oracle, das zuvor Sun gekauft hatte, hat eine große Anzahl von Sun-Mitarbeitern entlassen. Wir können sagen, dass das Unternehmen zu diesem Zeitpunkt aufgehört hat zu existieren. Bei der Namenswahl für das neue System beschlossen unsere Administratoren, der Erinnerung an dieses Unternehmen Tribut zu zollen, und nannten das neue System Sun. Unter uns nennen wir sie einfach „Sonnen“.

FAQ zur Architektur und Arbeit von VKontakte

pp hatte ein paar Probleme. Eine IP pro Gruppe – ineffektiver Cache. Mehrere physische Server teilen sich eine gemeinsame IP-Adresse und es gibt keine Möglichkeit zu steuern, an welchen Server die Anfrage gesendet wird. Wenn also verschiedene Benutzer für dieselbe Datei kommen und auf diesen Servern ein Cache vorhanden ist, landet die Datei im Cache jedes Servers. Dies ist ein sehr ineffizientes Schema, aber es konnte nichts getan werden.

Als Konsequenz - Wir können Inhalte nicht teilen, da wir für diese Gruppe keinen bestimmten Server auswählen können – sie haben eine gemeinsame IP. Auch aus einigen internen Gründen haben wir Es war nicht möglich, solche Server in Regionen zu installieren. Sie standen nur in St. Petersburg.

Bei den Sonnen haben wir das Auswahlsystem geändert. Jetzt haben wir Anycast-Routing: dynamisches Routing, Anycast, Selbstprüfungs-Daemon. Jeder Server hat seine eigene individuelle IP, aber ein gemeinsames Subnetz. Alles ist so konfiguriert, dass bei Ausfall eines Servers der Datenverkehr automatisch auf die anderen Server derselben Gruppe verteilt wird. Jetzt ist es möglich, einen bestimmten Server auszuwählen, kein redundantes Caching, und die Zuverlässigkeit wurde nicht beeinträchtigt.

Gewichtsunterstützung. Jetzt können wir es uns leisten, je nach Bedarf Maschinen mit unterschiedlicher Leistung zu installieren und bei vorübergehenden Problemen auch das Gewicht der Arbeits-„Sonnen“ zu ändern, um die Belastung auf ihnen zu verringern, damit sie „ruhen“ und wieder arbeiten können.

Sharding nach Inhalts-ID. Eine lustige Sache beim Sharding: Normalerweise teilen wir Inhalte so, dass verschiedene Benutzer über dieselbe „Sonne“ auf dieselbe Datei zugreifen, sodass sie einen gemeinsamen Cache haben.

Wir haben kürzlich die Anwendung „Clover“ gestartet. Hierbei handelt es sich um ein Online-Quiz in einer Live-Übertragung, bei dem der Moderator Fragen stellt und die Benutzer in Echtzeit antworten und dabei Optionen auswählen. Die App verfügt über einen Chat, in dem Benutzer chatten können. Kann gleichzeitig eine Verbindung zur Übertragung herstellen mehr als 100 Menschen. Sie alle schreiben Nachrichten, die an alle Teilnehmer gesendet werden, und ein Avatar kommt mit der Nachricht. Wenn 100 Menschen für einen Avatar in einer „Sonne“ kommen, kann dieser manchmal hinter einer Wolke rollen.

Um einer Flut von Anfragen nach derselben Datei standzuhalten, aktivieren wir für eine bestimmte Art von Inhalten ein dummes Schema, das Dateien auf alle verfügbaren „Sonnen“ in der Region verteilt.

Sonne von innen

Reverse-Proxy auf Nginx, Cache entweder im RAM oder auf schnellen Optane/NVMe-Festplatten. Beispiel: http://sun4-2.userapi.com/c100500/path — ein Link zur „Sonne“, die sich in der vierten Region, der zweiten Servergruppe, befindet. Es schließt die Pfaddatei, die physikalisch auf Server 100500 liegt.

Cache-Speicher

Wir fügen unserem Architekturschema einen weiteren Knoten hinzu – die Caching-Umgebung.

FAQ zur Architektur und Arbeit von VKontakte

Unten finden Sie das Layoutdiagramm regionale Caches, es sind etwa 20 davon. Dies sind die Orte, an denen sich Caches und „Sonnen“ befinden, die den Datenverkehr durch sich selbst zwischenspeichern können.

FAQ zur Architektur und Arbeit von VKontakte

Dabei handelt es sich um das Zwischenspeichern von Multimedia-Inhalten; hier werden keine Benutzerdaten gespeichert – nur Musik, Videos, Fotos.

Um die Region des Benutzers zu bestimmen, verwenden wir Wir sammeln die in den Regionen angekündigten BGP-Netzwerkpräfixe. Im Fallback müssen wir auch die GeoIP-Datenbank analysieren, wenn wir die IP nicht anhand der Präfixe finden konnten. Wir bestimmen die Region anhand der IP des Benutzers. Im Code können wir uns eine oder mehrere Regionen des Benutzers ansehen – die Punkte, denen er geografisch am nächsten ist.

Wie funktioniert es?

Wir zählen die Beliebtheit von Dateien nach Region. Es gibt eine Nummer des regionalen Caches, in dem sich der Benutzer befindet, und eine Dateikennung – wir nehmen dieses Paar und erhöhen die Bewertung mit jedem Download.

Gleichzeitig kommen Dämonen – Dienste in Regionen – von Zeit zu Zeit zur API und sagen: „Ich bin so und so ein Cache, gib mir eine Liste der beliebtesten Dateien in meiner Region, die noch nicht auf mir sind.“ ” Die API liefert eine Reihe von Dateien, sortiert nach Bewertung, der Daemon lädt sie herunter, bringt sie in die Regionen und liefert die Dateien von dort aus. Dies ist der grundlegende Unterschied zwischen pu/pp und Sun gegenüber Caches: Sie geben die Datei sofort durch sich selbst weiter, auch wenn sich diese Datei nicht im Cache befindet, und der Cache lädt die Datei zuerst in sich selbst herunter und beginnt dann mit der Rückgabe.

In diesem Fall erhalten wir Inhalte näher an die Benutzer heranbringen und die Netzwerklast verteilen. Beispielsweise verteilen wir nur aus dem Moskauer Cache zu Spitzenzeiten mehr als 1 Tbit/s.

Aber es gibt Probleme - Cache-Server sind nicht aus Gummi. Für besonders beliebte Inhalte reicht das Netzwerk manchmal nicht für einen separaten Server aus. Unsere Cache-Server haben 40-50 Gbit/s, aber es gibt Inhalte, die einen solchen Kanal komplett verstopfen. Wir sind dabei, die Speicherung von mehr als einer Kopie beliebter Dateien in der Region zu implementieren. Ich hoffe, dass wir es bis Ende des Jahres umsetzen können.

Wir haben uns die allgemeine Architektur angeschaut.

  • Frontserver, die Anfragen entgegennehmen.
  • Backends, die Anfragen verarbeiten.
  • Speicher, die durch zwei Arten von Proxys geschlossen werden.
  • Regionale Caches.

Was fehlt in diesem Diagramm? Natürlich die Datenbanken, in denen wir Daten speichern.

Datenbanken oder Engines

Wir nennen sie nicht Datenbanken, sondern Engines – Engines, weil wir praktisch keine Datenbanken im allgemein akzeptierten Sinne haben.

FAQ zur Architektur und Arbeit von VKontakte

Dies ist eine notwendige Maßnahme.. Dies geschah, weil das Projekt in den Jahren 2008 und 2009, als VK einen explosionsartigen Anstieg der Popularität verzeichnete, ausschließlich auf MySQL und Memcache lief und es Probleme gab. MySQL stürzte gern ab und beschädigte Dateien. Danach konnte es nicht mehr wiederhergestellt werden, und die Leistung von Memcache ließ allmählich nach, sodass ein Neustart erforderlich war.

Es stellte sich heraus, dass das immer beliebter werdende Projekt über persistenten Speicher verfügte, der Daten beschädigte, und über einen Cache, der langsamer wurde. Unter solchen Bedingungen ist es schwierig, ein wachsendes Projekt zu entwickeln. Es wurde beschlossen, zu versuchen, die kritischen Dinge, auf die sich das Projekt konzentrierte, auf unsere eigenen Fahrräder umzuschreiben.

Die Lösung war erfolgreich. Dazu bestand die Möglichkeit, aber auch eine extreme Notwendigkeit, da es zu diesem Zeitpunkt noch keine anderen Möglichkeiten der Skalierung gab. Es gab nicht viele Datenbanken, NoSQL existierte noch nicht, es gab nur MySQL, Memcache, PostgreSQL – und das war's.

Universeller Betrieb. Die Entwicklung wurde von unserem C-Entwicklerteam geleitet und alles wurde auf konsistente Weise durchgeführt. Unabhängig von der Engine hatten sie alle ungefähr dasselbe Dateiformat auf der Festplatte, dieselben Startparameter, verarbeiteten Signale auf dieselbe Weise und verhielten sich bei Randsituationen und Problemen ungefähr gleich. Mit dem Wachstum der Engines ist es für Administratoren bequemer, das System zu bedienen – es gibt keinen Zoo, der gewartet werden muss, und sie müssen den Betrieb jeder neuen Datenbank von Drittanbietern neu erlernen, was eine schnelle und bequeme Erweiterung ermöglicht ihre Zahl.

Arten von Motoren

Das Team hat eine ganze Reihe von Motoren geschrieben. Hier sind nur einige davon: Freund, Hinweise, Bild, IPDB, Briefe, Listen, Protokolle, Memcached, MeowDB, Nachrichten, Nostradamus, Foto, Wiedergabelisten, PMEMcached, Sandbox, Suche, Speicher, Likes, Aufgaben, …

Für jede Aufgabe, die eine bestimmte Datenstruktur erfordert oder atypische Anfragen verarbeitet, schreibt das C-Team eine neue Engine. Warum nicht.

Wir haben einen separaten Motor memcached, das einem normalen ähnelt, aber viele Extras bietet und nicht langsamer wird. Nicht ClickHouse, aber es funktioniert auch. Separat erhältlich pmemcached - Das dauerhaft zwischengespeichert, das darüber hinaus auch Daten auf der Festplatte speichern kann, als in den RAM passt, um beim Neustart keine Daten zu verlieren. Für einzelne Aufgaben gibt es verschiedene Engines: Warteschlangen, Listen, Sets – alles, was unser Projekt benötigt.

Cluster

Aus Code-Perspektive besteht keine Notwendigkeit, Engines oder Datenbanken als Prozesse, Entitäten oder Instanzen zu betrachten. Der Code funktioniert speziell mit Clustern, mit Gruppen von Engines – ein Typ pro Cluster. Nehmen wir an, es gibt einen Memcached-Cluster – es handelt sich lediglich um eine Gruppe von Maschinen.

Der Code muss den physischen Standort, die Größe oder die Anzahl der Server überhaupt nicht kennen. Er geht mit einer bestimmten Kennung zum Cluster.

Damit dies funktioniert, müssen Sie eine weitere Entität hinzufügen, die sich zwischen dem Code und den Engines befindet – Stellvertreter.

RPC-Proxy

Stellvertreter Verbindungsbus, auf dem fast die gesamte Website läuft. Gleichzeitig haben wir Keine Diensterkennung — Stattdessen gibt es eine Konfiguration für diesen Proxy, die den Standort aller Cluster und aller Shards dieses Clusters kennt. Das ist es, was Administratoren tun.

Den Programmierern ist es völlig egal, wie viel, wo und was es kostet – sie gehen einfach zum Cluster. Das ermöglicht uns viel. Beim Empfang einer Anfrage leitet der Proxy die Anfrage weiter, da er weiß, wohin – er bestimmt dies selbst.

FAQ zur Architektur und Arbeit von VKontakte

In diesem Fall ist der Proxy ein Schutzpunkt gegen Dienstausfälle. Wenn eine Engine langsamer wird oder abstürzt, erkennt der Proxy dies und reagiert entsprechend auf die Client-Seite. Dadurch können Sie die Zeitüberschreitung beseitigen – der Code wartet nicht auf die Antwort der Engine, sondern versteht, dass sie nicht funktioniert und sich irgendwie anders verhalten muss. Der Code muss darauf vorbereitet sein, dass die Datenbanken nicht immer funktionieren.

Spezifische Implementierungen

Manchmal möchten wir immer noch eine nicht standardmäßige Lösung als Engine haben. Gleichzeitig wurde beschlossen, nicht unseren vorgefertigten RPC-Proxy zu verwenden, der speziell für unsere Engines erstellt wurde, sondern einen separaten Proxy für die Aufgabe zu erstellen.

Für MySQL, das wir hier und da noch haben, verwenden wir db-proxy und für ClickHouse - Kätzchenhaus.

Im Allgemeinen funktioniert es so. Es gibt einen bestimmten Server, auf dem kPHP, Go, Python ausgeführt wird – im Allgemeinen jeder Code, der unser RPC-Protokoll verwenden kann. Der Code läuft lokal auf einem RPC-Proxy – jeder Server, auf dem sich der Code befindet, betreibt seinen eigenen lokalen Proxy. Auf Anfrage versteht der Bevollmächtigte, wohin er gehen muss.

FAQ zur Architektur und Arbeit von VKontakte

Wenn eine Engine zu einer anderen gehen möchte, auch wenn es sich um einen Nachbarn handelt, erfolgt dies über einen Proxy, da sich der Nachbar möglicherweise in einem anderen Rechenzentrum befindet. Der Motor sollte sich nicht darauf verlassen, den Standort von etwas anderem als sich selbst zu kennen – das ist unsere Standardlösung. Aber natürlich gibt es Ausnahmen :)

Ein Beispiel für ein TL-Schema, nach dem alle Motoren arbeiten.

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;

Dies ist ein Binärprotokoll, dessen nächstgelegenes Analogon ist protobuf. Das Schema beschreibt optionale Felder, komplexe Typen – Erweiterungen integrierter Skalare und Abfragen. Alles funktioniert nach diesem Protokoll.

RPC über TL über TCP/UDP… UDP?

Wir verfügen über ein RPC-Protokoll zum Ausführen von Engine-Anfragen, das auf dem TL-Schema basiert. Dies alles funktioniert über eine TCP/UDP-Verbindung. TCP ist verständlich, aber warum brauchen wir oft UDP?

UDP hilft Vermeiden Sie das Problem einer großen Anzahl von Verbindungen zwischen Servern. Wenn jeder Server über einen RPC-Proxy verfügt und dieser im Allgemeinen zu jeder Engine gehen kann, dann gibt es Zehntausende TCP-Verbindungen pro Server. Es gibt eine Ladung, aber sie ist nutzlos. Bei UDP besteht dieses Problem nicht.

Kein redundanter TCP-Handshake. Dies ist ein typisches Problem: Wenn eine neue Engine oder ein neuer Server gestartet wird, werden viele TCP-Verbindungen gleichzeitig aufgebaut. Bei kleinen, leichtgewichtigen Anfragen, beispielsweise UDP-Nutzdaten, erfolgt die gesamte Kommunikation zwischen dem Code und der Engine zwei UDP-Pakete: Einer fliegt in die eine Richtung, der zweite in die andere. Ein Hin- und Rücklauf – und der Code erhielt ohne Handschlag eine Antwort von der Engine.

Ja, es funktioniert einfach alles mit einem sehr geringen Prozentsatz an Paketverlusten. Das Protokoll unterstützt erneute Übertragungen und Zeitüberschreitungen, aber wenn wir viel verlieren, erhalten wir fast TCP, was nicht vorteilhaft ist. Wir treiben UDP nicht über Ozeane.

Wir haben Tausende solcher Server und das Schema ist das gleiche: Auf jedem physischen Server wird ein Paket von Engines installiert. Sie sind meist Single-Threaded, um so schnell wie möglich ohne Blockierung zu laufen, und werden als Single-Threaded-Lösungen fragmentiert. Gleichzeitig verfügen wir über nichts Zuverlässigeres als diese Engines und legen großen Wert auf die dauerhafte Datenspeicherung.

Permanente Datenspeicherung

Engines schreiben Binlogs. Ein Binlog ist eine Datei, an deren Ende ein Ereignis für eine Zustands- oder Datenänderung hinzugefügt wird. In verschiedenen Lösungen wird es unterschiedlich genannt: Binärlog, WAL, AOF, aber das Prinzip ist das gleiche.

Um zu verhindern, dass die Engine beim Neustart viele Jahre lang das gesamte Binlog erneut liest, schreiben die Engines Schnappschüsse – aktueller Stand. Bei Bedarf lesen sie zuerst daraus und lesen dann das Binlog zu Ende. Alle Binlogs werden im gleichen Binärformat geschrieben – nach dem TL-Schema, sodass Administratoren sie mit ihren Tools gleichermaßen verwalten können. Es besteht kein Bedarf an Schnappschüssen. Es gibt einen allgemeinen Header, der angibt, wessen Snapshot int ist, die Magie der Engine und welcher Body für niemanden wichtig ist. Dies ist ein Problem mit der Engine, die den Snapshot aufgezeichnet hat.

Ich werde kurz das Funktionsprinzip beschreiben. Es gibt einen Server, auf dem die Engine läuft. Er öffnet ein neues leeres Binlog zum Schreiben und schreibt ein Ereignis zur Änderung daran.

FAQ zur Architektur und Arbeit von VKontakte

Irgendwann beschließt er entweder, selbst einen Schnappschuss zu machen, oder er erhält ein Signal. Der Server erstellt eine neue Datei, schreibt ihren gesamten Status hinein, hängt die aktuelle Binlog-Größe (Offset) an das Ende der Datei an und schreibt weiter. Es wird kein neues Binlog erstellt.

FAQ zur Architektur und Arbeit von VKontakte

Irgendwann, wenn die Engine neu gestartet wird, befinden sich sowohl ein Binlog als auch ein Snapshot auf der Festplatte. Die Engine liest den gesamten Snapshot und erhöht seinen Status an einem bestimmten Punkt.

FAQ zur Architektur und Arbeit von VKontakte

Liest die Position zum Zeitpunkt der Erstellung des Snapshots und die Größe des Binlogs.

FAQ zur Architektur und Arbeit von VKontakte

Liest das Ende des Binlogs, um den aktuellen Status abzurufen, und schreibt weitere Ereignisse weiter. Das ist ein einfaches Schema; alle unsere Motoren arbeiten danach.

Datenreplikation

Infolgedessen ist die Datenreplikation in unserem aussagebasiert — Wir schreiben im Binlog keine Seitenänderungen, sondern nämlich Änderungswünsche. Sehr ähnlich zu dem, was über das Netzwerk kommt, nur leicht modifiziert.

Das gleiche Schema wird nicht nur für die Replikation verwendet, sondern auch um Backups zu erstellen. Wir haben eine Engine – einen Schreibmaster, der in das Binlog schreibt. An jedem anderen Ort, an dem die Administratoren es eingerichtet haben, wird dieses Binlog kopiert, und das war’s – wir haben ein Backup.

FAQ zur Architektur und Arbeit von VKontakte

Wenn benötigt LesereplikUm die CPU-Leselast zu reduzieren, wird einfach die Lese-Engine gestartet, die das Ende des Binlogs liest und diese Befehle lokal ausführt.

Die Verzögerung ist hier sehr gering und es ist möglich herauszufinden, wie stark die Replik hinter dem Master zurückbleibt.

Daten-Sharding im RPC-Proxy

Wie funktioniert Sharding? Wie erkennt der Proxy, an welchen Cluster-Shard er senden soll? Der Code sagt nicht: „Für 15 Shards senden!“ - Nein, das erledigt der Proxy.

Das einfachste Schema ist firstint – die erste Nummer in der Anfrage.

get(photo100_500) => 100 % N.

Dies ist ein Beispiel für ein einfaches zwischengespeichertes Textprotokoll, aber natürlich können Abfragen komplex und strukturiert sein. Das Beispiel verwendet die erste Zahl in der Abfrage und den Rest, wenn sie durch die Clustergröße dividiert wird.

Dies ist nützlich, wenn wir die Datenlokalität einer einzelnen Entität haben möchten. Nehmen wir an, 100 ist eine Benutzer- oder Gruppen-ID und wir möchten, dass sich alle Daten einer Entität für komplexe Abfragen auf einem Shard befinden.

Wenn uns die Verteilung der Anfragen im Cluster egal ist, gibt es eine andere Option: Hashing des gesamten Shards.

hash(photo100_500) => 3539886280 % N

Außerdem erhalten wir den Hash, den Rest der Division und die Shard-Nummer.

Beide Optionen funktionieren nur, wenn wir darauf vorbereitet sind, dass wir den Cluster bei einer Vergrößerung aufteilen oder um ein Vielfaches vergrößern. Wir hatten zum Beispiel 16 Shards, wir haben nicht genug, wir wollen mehr – wir können ohne Ausfallzeiten sicher 32 erhalten. Wenn wir nicht um ein Vielfaches erhöhen wollen, kommt es zu Ausfallzeiten, da wir nicht in der Lage sind, alles ohne Verluste genau aufzuteilen. Diese Optionen sind nützlich, aber nicht immer.

Wenn wir eine beliebige Anzahl von Servern hinzufügen oder entfernen müssen, verwenden wir Konsistentes Hashing im Ring a la Ketama. Aber gleichzeitig verlieren wir völlig die Lokalität der Daten; wir müssen die Anfrage an den Cluster zusammenführen, sodass jedes Stück seine eigene kleine Antwort zurückgibt, und dann die Antworten an den Proxy zusammenführen.

Es gibt superspezifische Anfragen. Es sieht so aus: Der RPC-Proxy empfängt die Anfrage, bestimmt, zu welchem ​​Cluster er gehen soll, und bestimmt den Shard. Dann gibt es entweder schreibende Master oder, wenn der Cluster über Replikatunterstützung verfügt, sendet er bei Bedarf an ein Replikat. Der Proxy erledigt das alles.

FAQ zur Architektur und Arbeit von VKontakte

Protokolle

Wir schreiben Protokolle auf verschiedene Arten. Das offensichtlichste und einfachste ist Schreiben Sie Protokolle in den Memcache.

ring-buffer: prefix.idx = line

Es gibt ein Schlüsselpräfix – den Namen des Protokolls, eine Zeile, und es gibt die Größe dieses Protokolls – die Anzahl der Zeilen. Wir nehmen eine Zufallszahl von 0 bis zur Anzahl der Zeilen minus 1. Der Schlüssel im Memcache ist ein Präfix, das mit dieser Zufallszahl verkettet ist. Wir speichern die Protokollzeile und die aktuelle Uhrzeit zum Wert.

Wenn es notwendig ist, Protokolle zu lesen, führen wir dies durch Multi-Get alle Schlüssel, sortiert nach Zeit, und erhalten so ein Produktionsprotokoll in Echtzeit. Das Schema wird verwendet, wenn Sie in der Produktion etwas in Echtzeit debuggen müssen, ohne etwas zu beschädigen, ohne den Datenverkehr zu anderen Computern zu stoppen oder zuzulassen, dieses Protokoll jedoch nicht lange anhält.

Für die zuverlässige Speicherung von Protokollen verfügen wir über eine Engine Protokoll-Engine. Genau aus diesem Grund wurde es entwickelt und wird in einer Vielzahl von Clustern häufig eingesetzt. Der größte mir bekannte Cluster speichert 600 TB gepackte Protokolle.

Der Motor ist sehr alt, es gibt Cluster, die schon 6-7 Jahre alt sind. Es gibt Probleme damit, die wir zu lösen versuchen. Beispielsweise haben wir damit begonnen, ClickHouse aktiv zum Speichern von Protokollen zu nutzen.

Sammeln von Protokollen in ClickHouse

Dieses Diagramm zeigt, wie wir in unsere Motoren hineingehen.

FAQ zur Architektur und Arbeit von VKontakte

Es gibt Code, der lokal über RPC an den RPC-Proxy gesendet wird und versteht, wohin er zur Engine gehen muss. Wenn wir Protokolle in ClickHouse schreiben möchten, müssen wir zwei Teile in diesem Schema ändern:

  • Ersetzen Sie eine Engine durch ClickHouse.
  • Ersetzen Sie den RPC-Proxy, der nicht auf ClickHouse zugreifen kann, durch eine Lösung, die dies kann, und zwar über RPC.

Die Engine ist einfach: Wir ersetzen sie durch einen Server oder einen Servercluster mit ClickHouse.

Und um zu ClickHouse zu gehen, haben wir es getan Kätzchenhaus. Wenn wir direkt von KittenHouse zu ClickHouse wechseln, wird es nicht zurechtkommen. Auch ohne Anfragen summiert es sich aus HTTP-Verbindungen einer riesigen Anzahl von Maschinen. Damit das Schema funktioniert, auf einem Server mit ClickHouse Der lokale Reverse-Proxy wird aktiviert, das so geschrieben ist, dass es den erforderlichen Verbindungsmengen standhält. Es kann auch relativ zuverlässig Daten in sich selbst puffern.

FAQ zur Architektur und Arbeit von VKontakte

Manchmal möchten wir das RPC-Schema nicht in nicht standardmäßigen Lösungen implementieren, beispielsweise in Nginx. Daher hat KittenHouse die Möglichkeit, Protokolle über UDP zu empfangen.

FAQ zur Architektur und Arbeit von VKontakte

Wenn Absender und Empfänger der Protokolle auf demselben Rechner arbeiten, ist die Wahrscheinlichkeit, dass ein UDP-Paket innerhalb des lokalen Hosts verloren geht, recht gering. Als Kompromiss zwischen der Notwendigkeit, RPC in einer Drittanbieterlösung zu implementieren, und der Zuverlässigkeit verwenden wir einfach UDP-Versand. Wir werden später auf dieses Schema zurückkommen.

Überwachung

Wir haben zwei Arten von Protokollen: diejenigen, die von Administratoren auf ihren Servern gesammelt werden, und solche, die von Entwicklern aus Code geschrieben werden. Sie entsprechen zwei Arten von Metriken: System und Produkt.

Systemmetriken

Es funktioniert auf allen unseren Servern Nettodaten, das Statistiken sammelt und an sendet Graphit-Kohlenstoff. Daher wird ClickHouse als Speichersystem verwendet und nicht beispielsweise Whisper. Bei Bedarf können Sie ClickHouse direkt auslesen oder verwenden Grafana für Metriken, Grafiken und Berichte. Als Entwickler haben wir ausreichend Zugriff auf Netdata und Grafana.

Produktkennzahlen

Der Einfachheit halber haben wir viele Dinge geschrieben. Beispielsweise gibt es eine Reihe gewöhnlicher Funktionen, mit denen Sie Counts- und UniqueCounts-Werte in Statistiken schreiben können, die an einen weiteren Ort gesendet werden.

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

$stats = statlogsStatData($params)

Anschließend können wir Sortier- und Gruppierungsfilter verwenden und aus der Statistik alles machen, was wir wollen – Diagramme erstellen, Watchdogs konfigurieren.

Wir schreiben sehr viele Kennzahlen die Zahl der Ereignisse liegt zwischen 600 Milliarden und 1 Billion pro Tag. Wir wollen sie jedoch behalten mindestens ein paar Jahreum Trends in Metriken zu verstehen. Alles zusammenzufassen ist ein großes Problem, das wir noch nicht gelöst haben. Ich erzähle Ihnen, wie es in den letzten Jahren funktioniert hat.

Wir haben Funktionen, die diese Metriken schreiben zum lokalen Memcacheum die Anzahl der Einträge zu reduzieren. Einmal in kurzer Zeit lokal eingeführt Statistik-Daemon sammelt alle Datensätze. Als nächstes führt der Dämon die Metriken in zwei Serverschichten zusammen Protokollsammler, das Statistiken von einer Reihe unserer Maschinen zusammenfasst, damit die Schicht dahinter nicht stirbt.

FAQ zur Architektur und Arbeit von VKontakte

Bei Bedarf können wir direkt an Protokollsammler schreiben.

FAQ zur Architektur und Arbeit von VKontakte

Das direkte Schreiben von Code in Collectors unter Umgehung von stas-daemom ist jedoch eine schlecht skalierbare Lösung, da dadurch die Belastung des Collectors erhöht wird. Die Lösung ist nur geeignet, wenn wir aus irgendeinem Grund den Memcache-Statistik-Daemon auf dem Computer nicht starten können oder er abgestürzt ist und wir direkt losgelegt haben.

Als nächstes führen Protokollsammler Statistiken zusammen meowDB - das ist unsere Datenbank, die auch Metriken speichern kann.

FAQ zur Architektur und Arbeit von VKontakte

Dann können wir aus dem Code binäre „SQL-nahe“ Auswahlen treffen.

FAQ zur Architektur und Arbeit von VKontakte

Experiment

Im Sommer 2018 veranstalteten wir einen internen Hackathon und dabei kam die Idee auf, den roten Teil des Diagramms durch etwas zu ersetzen, das Metriken in ClickHouse speichern kann. Wir haben Protokolle auf ClickHouse – warum probieren Sie es nicht aus?

FAQ zur Architektur und Arbeit von VKontakte

Wir hatten ein Schema, das Protokolle über KittenHouse schrieb.

FAQ zur Architektur und Arbeit von VKontakte

Wir beschlossen Fügen Sie dem Diagramm ein weiteres „*Haus“ hinzu, das genau die Metriken in dem Format erhält, wie unser Code sie über UDP schreibt. Dann wandelt dieses *House sie in Einfügungen wie Protokolle um, was KittenHouse versteht. Er kann diese Protokolle perfekt an ClickHouse übermitteln, das sie lesen kann.

FAQ zur Architektur und Arbeit von VKontakte

Das Schema mit Memcache, Stats-Daemon und Logs-Collectors-Datenbank wird durch dieses ersetzt.

FAQ zur Architektur und Arbeit von VKontakte

Das Schema mit Memcache, Stats-Daemon und Logs-Collectors-Datenbank wird durch dieses ersetzt.

  • Hier gibt es einen Versandcode, der lokal in StatsHouse geschrieben wird.
  • StatsHouse schreibt UDP-Metriken, die bereits in SQL-Einfügungen konvertiert wurden, stapelweise in KittenHouse.
  • KittenHouse sendet sie an ClickHouse.
  • Wenn wir sie lesen wollen, dann lesen wir sie unter Umgehung von StatsHouse – direkt aus ClickHouse unter Verwendung von regulärem SQL.

Ist es noch Experiment, aber uns gefällt, wie es ausgeht. Wenn wir die Probleme mit dem Schema beheben, werden wir vielleicht ganz darauf umsteigen. Ich persönlich hoffe es.

Fahren spart kein Eisen. Es werden weniger Server benötigt, lokale Statistik-Daemons und Protokoll-Sammler werden nicht benötigt, aber ClickHouse erfordert einen größeren Server als die im aktuellen Schema. Es werden weniger Server benötigt, diese müssen jedoch teurer und leistungsfähiger sein.

Einsetzen

Schauen wir uns zunächst die PHP-Bereitstellung an. Wir entwickeln uns in git: verwenden Gitlab и TeamCity für den Einsatz. Entwicklungszweige werden in den Masterzweig zusammengeführt, vom Masterzweig zum Testen in den Stagingzweig und vom Staging in den Produktionszweig.

Vor der Bereitstellung werden der aktuelle und der vorherige Produktionszweig übernommen und die darin enthaltenen Diff-Dateien berücksichtigt – Änderungen: erstellt, gelöscht, geändert. Diese Änderung wird im Binlog einer speziellen Copyfast-Engine aufgezeichnet, die Änderungen schnell auf unsere gesamte Serverflotte replizieren kann. Hier wird nicht direkt kopiert, sondern Klatschreplikation, wenn ein Server Änderungen an seine nächsten Nachbarn sendet, diese an deren Nachbarn usw. Auf diese Weise können Sie den Code für die gesamte Flotte in Zehner- und Sekundeneinheiten aktualisieren. Wenn die Änderung das lokale Replikat erreicht, werden diese Patches darauf angewendet lokales Dateisystem. Auch das Rollback erfolgt nach dem gleichen Schema.

Wir setzen kPHP auch häufig ein und es gibt auch eine eigene Entwicklung git gemäß dem Diagramm oben. Seit dem HTTP-Server-Binärdatei, dann können wir kein Diff erzeugen – die Release-Binärdatei wiegt Hunderte von MB. Daher gibt es hier eine weitere Möglichkeit – die Version wird beschrieben binlog copyfast. Mit jedem Build steigt er und beim Rollback erhöht er sich auch. Ausführung auf Server repliziert. Lokale Copyfasts sehen, dass eine neue Version in das Binlog gelangt ist, und übernehmen durch die gleiche Gossip-Replikation die neueste Version der Binärdatei, ohne unseren Master-Server zu ermüden, sondern die Last sorgfältig über das Netzwerk zu verteilen. Was folgt eleganter Relaunch für die neue Version.

Für unsere Engines, die ebenfalls im Wesentlichen Binärdateien sind, ist das Schema sehr ähnlich:

  • Git-Master-Zweig;
  • binär in . Deb;
  • die Version wird in binlog copyfast geschrieben;
  • auf Server repliziert;
  • Der Server ruft eine neue .dep-Datei ab.
  • dpkg -i;
  • Anmutiger Relaunch zur neuen Version.

Der Unterschied besteht darin, dass unsere Binärdatei in Archiven verpackt ist . Deb, und beim Abpumpen sie dpkg -i werden auf dem System platziert. Warum wird kPHP als Binärdatei und Engines als Dpkg bereitgestellt? Es ist so passiert. Es funktioniert – nicht anfassen.

Nützliche Links:

Alexey Akulovich ist einer derjenigen, die im Programmkomitee helfen PHP Russland am 17. Mai wird die größte Veranstaltung für PHP-Entwickler der letzten Zeit sein. Schauen Sie, was für einen coolen PC wir haben, was Sprecher (zwei von ihnen entwickeln den PHP-Kern!) – scheint etwas zu sein, das Sie nicht verpassen dürfen, wenn Sie PHP schreiben.

Source: habr.com

Kommentar hinzufügen