
Die Entwicklung hochgeladener Projekte in jeder Sprache erfordert einen speziellen Ansatz und die Verwendung spezieller Tools. Bei Anwendungen in PHP kann sich die Situation jedoch so sehr verschlechtern, dass Sie beispielsweise
Ihren eigenen Anwendungsserver entwickeln müssen . In diesem Artikel werden wir über die Probleme sprechen, die jeder mit der verteilten Speicherung von Sitzungen und dem Zwischenspeichern von Daten in Memcached kennt, und darüber, wie wir diese Probleme in einem „Ward“ -Projekt gelöst haben.
Schuld an der Feier ist eine PHP-Anwendung, die auf dem Symfony 2.3-Framework basiert und in den Geschäftsplänen überhaupt nicht enthalten ist. Zusätzlich zum vollständig standardmäßigen Sitzungsspeicher verwendete das Projekt
das Caching aller Richtlinien in Memcached mit
aller Kraft: Antworten auf Anfragen an die Datenbank- und API-Server, verschiedene Flags, Sperren für die Synchronisierung der Codeausführung und vieles mehr. In dieser Situation wird ein zwischengespeicherter Fehler schwerwiegend, damit die Anwendung funktioniert. Darüber hinaus führt der Verlust des Caches zu schwerwiegenden Konsequenzen: Das DBMS beginnt aus allen Nähten zu knacken, API-Dienste - Verbotsanforderungen usw. Die Stabilisierung der Situation kann einige zehn Minuten dauern, und zu diesem Zeitpunkt wird der Dienst erheblich langsamer oder wird vollständig unzugänglich.
Wir mussten die
Möglichkeit einer horizontalen Skalierung der Anwendung mit kleinem Blut bereitstellen , d.h. mit minimalen Änderungen am Quellcode und vollständiger Beibehaltung der Funktionalität. Machen Sie den Cache nicht nur fehlertolerant, sondern versuchen Sie auch, Datenverluste zu minimieren.
Was ist los mit memcached selbst?
Im Allgemeinen unterstützt die sofort einsatzbereite Speichererweiterung für PHP die verteilte Speicherung von Daten und Sitzungen. Der konsistente Schlüssel-Hashing-Mechanismus ermöglicht es Ihnen, Daten gleichmäßig auf vielen Servern zu platzieren und jeden bestimmten Schlüssel eindeutig an einen bestimmten Server in der Gruppe zu adressieren. Die integrierten Tools des Failovers sorgen für eine hohe Verfügbarkeit des Caching-Dienstes (leider jedoch
nicht für Daten ).
Beim Speichern von Sitzungen sieht es etwas besser aus: Sie können
memcached.sess_number_of_replicas
konfigurieren, wodurch Daten auf mehreren Servern gleichzeitig gespeichert werden und bei Ausfall einer zwischengespeicherten Instanz Daten von anderen übertragen werden. Wenn der Server jedoch ohne Daten zum Dienst zurückkehrt (wie dies normalerweise nach einem Neustart der Fall ist), wird ein Teil der Schlüssel zu seinen Gunsten neu verteilt. Tatsächlich bedeutet dies den
Verlust von Sitzungsdaten , da es im Falle eines Fehlers keine Möglichkeit gibt, zu einem anderen Replikat zu "wechseln".
Die Standard-Bibliothekstools zielen hauptsächlich auf die
horizontale Skalierung ab: Sie ermöglichen es Ihnen, den Cache auf gigantische Größen zu vergrößern und über Code auf verschiedenen Servern darauf zuzugreifen. In unserer Situation überschreitet die Menge der gespeicherten Daten jedoch nicht mehrere Gigabyte, und die Leistung von einem oder zwei Knoten ist völlig ausreichend. Dementsprechend konnten sie mit einem nützlichen regulären Mittel nur die Verfügbarkeit von Memcached sicherstellen, während mindestens eine Cache-Instanz in einem funktionsfähigen Zustand gehalten wurde. Es ist mir jedoch nicht gelungen, diese Gelegenheit überhaupt zu nutzen ... Hier sollten wir uns an die Antike des im Projekt verwendeten Frameworks erinnern, das es unmöglich machte, die Anwendung für den Serverpool zum Laufen zu bringen. Wir werden auch den Verlust von Sitzungsdaten nicht vergessen: Das Auge zuckte bei der Massenabmeldung von Benutzern beim Kunden.
Im Idealfall war die
Replikation eines Datensatzes in zwischengespeicherten und crawlenden Replikaten im Falle eines Fehlers oder Fehlers erforderlich.
Mcrouter hat uns bei der Umsetzung dieser Strategie geholfen.
mcrouter
Dies ist ein Memcached-Router, der von Facebook entwickelt wurde, um seine Probleme zu lösen. Es unterstützt das Memcached-Text-Protokoll, mit dem Sie
Memcached-Installationen auf verrückte Größen
skalieren können. Eine ausführliche Beschreibung von mcrouter finden Sie in
dieser Ankündigung . Unter anderem
kann es das, was wir brauchen:
- Replizieren Sie den Datensatz.
- Im Fehlerfall auf andere Server der Gruppe zurückgreifen.
Zur Sache!
Mcrouter-Konfiguration
Ich werde direkt zur Konfiguration gehen:
{ "pools": { "pool00": { "servers": [ "mc-0.mc:11211", "mc-1.mc:11211", "mc-2.mc:11211" }, "pool01": { "servers": [ "mc-1.mc:11211", "mc-2.mc:11211", "mc-0.mc:11211" }, "pool02": { "servers": [ "mc-2.mc:11211", "mc-0.mc:11211", "mc-1.mc:11211" }, "route": { "type": "OperationSelectorRoute", "default_policy": "AllMajorityRoute|Pool|pool00", "operation_policies": { "get": { "type": "RandomRoute", "children": [ "MissFailoverRoute|Pool|pool02", "MissFailoverRoute|Pool|pool00", "MissFailoverRoute|Pool|pool01" ] } } } }
Warum drei Pools? Warum werden die Server wiederholt? Mal sehen, wie es funktioniert.
- In dieser Konfiguration wählt mcrouter den Pfad aus, an den die Anforderung gesendet wird, basierend auf dem Anforderungsbefehl. Der Typ
OperationSelectorRoute
informiert ihn darüber. - GET-Anforderungen fallen in den
RandomRoute
Handler, der zufällig einen Pool oder eine Route zwischen den Objekten im RandomRoute
Array auswählt. Jedes Element dieses Arrays ist wiederum ein MissFailoverRoute
Handler, der jeden Server im Pool MissFailoverRoute
, bis er eine Antwort mit Daten erhält, die an den Client zurückgegeben werden. - Wenn wir ausschließlich
MissFailoverRoute
mit einem Pool von drei Servern verwenden würden, würden alle Anforderungen zuerst an die erste zwischengespeicherte Instanz gesendet, und der Rest würde Anforderungen nach dem Restprinzip erhalten, wenn keine Daten vorhanden sind. Ein solcher Ansatz würde zu einer Überlastung des ersten Servers in der Liste führen . Daher wurde beschlossen, drei Pools mit Adressen in einer anderen Reihenfolge zu generieren und diese zufällig auszuwählen. - Alle anderen Anforderungen (und dieser Datensatz) werden mit
AllMajorityRoute
verarbeitet. Dieser Handler sendet Anforderungen an alle Server im Pool und wartet auf Antworten von mindestens N / 2 + 1 von ihnen. Ich musste die Verwendung von AllSyncRoute
für Schreibvorgänge aufgeben, da diese Methode eine positive Antwort von allen Servern in der Gruppe erfordert - andernfalls wird SERVER_ERROR
. Obwohl mcrouter die Daten in zugänglichen Caches ablegt, gibt die aufrufende PHP-Funktion einen Fehler zurück und generiert einen Hinweis. AllMajorityRoute
nicht so streng und ermöglicht die Außerbetriebnahme von bis zur Hälfte der Knoten ohne die oben genannten Probleme.
Der Hauptnachteil dieses Schemas besteht darin, dass für jede Anforderung vom Client N Anforderungen an memcached ausgeführt werden, wenn sich wirklich keine Daten im Cache befinden - an
alle Server im Pool. Sie können die Anzahl der Server in Pools beispielsweise auf zwei reduzieren: Wenn Sie die Speicherzuverlässigkeit opfern, erhalten wir mehr Geschwindigkeit und weniger Last von Anforderungen an fehlende Schlüssel.
NB : Die Dokumentation im Wiki und die Ausgaben des Projekts (einschließlich der geschlossenen), die ein ganzes Lagerhaus mit verschiedenen Konfigurationen darstellen, können auch nützliche Links zum Erlernen von mcrouter sein.Mcrouter erstellen und ausführen
Die Anwendung (und selbst zwischengespeichert) funktioniert für uns in Kubernetes - jeweils am selben Ort und am selben Computer. Um
den Container zu
erstellen, verwenden wir
werf , dessen Konfiguration folgendermaßen aussehen wird:
NB : Die Auflistungen in diesem Artikel werden im flant / mcrouter-Repository veröffentlicht . configVersion: 1 project: mcrouter deploy: namespace: '[[ env ]]' helmRelease: '[[ project ]]-[[ env ]]' --- image: mcrouter from: ubuntu:16.04 mount: - from: tmp_dir to: /var/lib/apt/lists - from: build_dir to: /var/cache/apt ansible: beforeInstall: - name: Install prerequisites apt: name: [ 'apt-transport-https', 'tzdata', 'locales' ] update_cache: yes - name: Add mcrouter APT key apt_key: url: https://facebook.imtqy.com/mcrouter/debrepo/xenial/PUBLIC.KEY - name: Add mcrouter Repo apt_repository: repo: deb https://facebook.imtqy.com/mcrouter/debrepo/xenial xenial contrib filename: mcrouter update_cache: yes - name: Set timezone timezone: name: "Europe/Moscow" - name: Ensure a locale exists locale_gen: name: en_US.UTF-8 state: present install: - name: Install mcrouter apt: name: [ 'mcrouter' ]
( werf.yaml )... und wirf eine
Helmkarte . Von der interessanten - es gibt nur einen Konfigurationsgenerator für die Anzahl der Replikate
(wenn jemand eine prägnantere und elegantere Option hat - teilen Sie in den Kommentaren) :
{{- $count := (pluck .Values.global.env .Values.memcached.replicas | first | default .Values.memcached.replicas._default | int) -}} {{- $pools := dict -}} {{- $servers := list -}} {{- /* : "0 1 2 0 1 2" */ -}} {{- range until 2 -}} {{- range $i, $_ := until $count -}} {{- $servers = append $servers (printf "mc-%d.mc:11211" $i) -}} {{- end -}} {{- end -}} {{- /* , N : "[0 1 2] [1 2 0] [2 0 1]" */ -}} {{- range $i, $_ := until $count -}} {{- $pool := dict "servers" (slice $servers $i (add $i $count)) -}} {{- $_ := set $pools (printf "MissFailoverRoute|Pool|pool%02d" $i) $pool -}} {{- end -}} --- apiVersion: v1 kind: ConfigMap metadata: name: mcrouter data: config.json: | { "pools": {{- $pools | toJson | replace "MissFailoverRoute|Pool|" "" -}}, "route": { "type": "OperationSelectorRoute", "default_policy": "AllMajorityRoute|Pool|pool00", "operation_policies": { "get": { "type": "RandomRoute", "children": {{- keys $pools | toJson }} } } } }
( 10-mcrouter.yaml )Wir rollen in die Testumgebung und überprüfen:
Die Suche im Text ergab keinen Fehler, aber auf Anfrage von "
mcrouter php " stand das älteste nicht geschlossene Projektproblem im Vordergrund - die
mangelnde Unterstützung für das zwischengespeicherte Binärprotokoll.
NB : Das ASCII-Protokoll in Memcached ist langsamer als das Binärprotokoll und bietet regelmäßige Methoden für konsistentes Schlüssel-Hashing, die nur mit dem Binärprotokoll funktionieren. Dies schafft jedoch keine Probleme für einen bestimmten Fall.Die Sache ist im Hut: Es bleibt nur, zum ASCII-Protokoll zu wechseln und es wird funktionieren .... In diesem Fall war die Gewohnheit, in der
Dokumentation auf php.net nach Antworten
zu suchen, jedoch ein grausamer Witz. Sie werden dort nicht die richtige Antwort finden ... es sei denn, Sie blättern natürlich bis zum Ende durch, wo im Abschnitt
"Vom Benutzer beigesteuerte Notizen" eine korrekte und
unverdient bombardierte Antwort angezeigt wird .
Ja, der richtige Optionsname lautet
memcached.sess_binary_protocol
. Es muss deaktiviert sein, danach beginnen die Sitzungen zu arbeiten. Es bleibt nur, den Container mit mcrouter mit PHP in den Pod zu stellen!
Fazit
So konnten wir allein mit Hilfe von Infrastrukturänderungen das aufgeworfene Problem lösen: Das Problem mit der zwischengespeicherten Fehlertoleranz wurde behoben, die Zuverlässigkeit des Cache-Speichers wurde erhöht. Zusätzlich zu den offensichtlichen Vorteilen für die Anwendung gab dies Spielraum bei der Arbeit an der Plattform: Wenn alle Komponenten über eine Reserve verfügen, wird die Lebensdauer des Administrators erheblich vereinfacht. Ja, diese Methode hat auch ihre Nachteile, sie mag wie eine „Krücke“ aussehen, aber wenn sie Geld spart, das Problem begräbt und keine neuen verursacht - warum nicht?
PS
Lesen Sie auch in unserem Blog: