Heim  >  Artikel  >  Backend-Entwicklung  >  Die Raft-Implementierung von etcd verstehen: Ein tiefer Einblick in Raft Log

Die Raft-Implementierung von etcd verstehen: Ein tiefer Einblick in Raft Log

Mary-Kate Olsen
Mary-Kate OlsenOriginal
2024-11-23 06:14:17152Durchsuche

Einführung

In diesem Artikel werden das Design und die Implementierung des Raft-Log-Moduls in etcds Raft vorgestellt und analysiert, beginnend mit dem Protokoll im Raft-Konsensalgorithmus. Ziel ist es, den Lesern zu helfen, die Implementierung von etcds Raft besser zu verstehen und einen möglichen Ansatz für die Implementierung ähnlicher Szenarien bereitzustellen.

Übersicht über Floßprotokolle

Der Raft-Konsensalgorithmus ist im Wesentlichen eine replizierte Zustandsmaschine mit dem Ziel, eine Reihe von Protokollen auf die gleiche Weise in einem Servercluster zu replizieren. Diese Protokolle ermöglichen es den Servern im Cluster, einen konsistenten Zustand zu erreichen.

In diesem Zusammenhang beziehen sich die Protokolle auf das Floßprotokoll. Jeder Knoten im Cluster verfügt über ein eigenes Raft-Protokoll, das aus einer Reihe von Protokolleinträgen besteht. Ein Protokolleintrag enthält normalerweise drei Felder:

  • Index: Der Index des Protokolleintrags
  • Amtszeit:Die Amtszeit des Leiters, als der Protokolleintrag erstellt wurde
  • Daten: Die im Protokolleintrag enthaltenen Daten, bei denen es sich um bestimmte Befehle usw. handeln kann.

Es ist wichtig zu beachten, dass der Index des Raft-Protokolls bei 1 beginnt und nur der Führungsknoten das Raft-Protokoll erstellen und auf Folgeknoten replizieren kann.

Wenn ein Protokolleintrag dauerhaft auf den meisten Knoten im Cluster gespeichert ist (z. B. 2/3, 3/5, 4/7), gilt er als festgeschrieben.

Wenn ein Protokolleintrag auf die Zustandsmaschine angewendet wird, gilt er als angewendet.

Understanding etcd

Überblick über die Raft-Implementierung von etcd

etcd Raft ist eine in Go geschriebene Raft-Algorithmusbibliothek, die häufig in Systemen wie etcd, Kubernetes, CockroachDB und anderen verwendet wird.

Das Hauptmerkmal von etcd Raft ist, dass es nur den Kernteil des Raft-Algorithmus implementiert. Benutzer müssen Netzwerkübertragung, Festplattenspeicher und andere am Raft-Prozess beteiligte Komponenten selbst implementieren (obwohl etcd Standardimplementierungen bereitstellt).

Die Interaktion mit der etcd-Raft-Bibliothek ist einigermaßen unkompliziert: Sie sagt Ihnen, welche Daten beibehalten werden müssen und welche Nachrichten an andere Knoten gesendet werden müssen. Es liegt in Ihrer Verantwortung, die Speicher- und Netzwerkübertragungsvorgänge abzuwickeln und diese entsprechend zu informieren. Es geht nicht um die Details, wie Sie diese Vorgänge implementieren; Es verarbeitet einfach die von Ihnen übermittelten Daten und teilt Ihnen basierend auf dem Raft-Algorithmus die nächsten Schritte mit.

Bei der Implementierung des etcd-Raft-Codes wird dieses Interaktionsmodell nahtlos mit der einzigartigen Kanalfunktion von Go kombiniert, was die etcd-Raft-Bibliothek wirklich unverwechselbar macht.

So implementieren Sie Raft Log

log und log_unstable

In etcd Raft befindet sich die Hauptimplementierung von Raft Log in den Dateien log.go und log_unstable.go, wobei die Primärstrukturen RaftLog und Unstable sind. Die instabile Struktur ist auch ein Feld innerhalb von RaftLog.

  • raftLog ist für die Hauptlogik des Raft Log verantwortlich. Es kann über die dem Benutzer bereitgestellte Speicherschnittstelle auf den Protokollspeicherstatus des Knotens zugreifen.
  • unstable enthält, wie der Name schon sagt, Protokolleinträge, die noch nicht dauerhaft gespeichert wurden, also nicht festgeschriebene Protokolle.

etcd Raft verwaltet die Protokolle innerhalb des Algorithmus, indem es RaftLog und Unstable koordiniert.

Kernfelder von RaftLog und Unstable

Um die Diskussion zu vereinfachen, konzentriert sich dieser Artikel nur auf die Verarbeitungslogik von Protokolleinträgen, ohne auf die Snapshot-Verarbeitung in etcd Raft einzugehen.

type raftLog struct {
    storage Storage
    unstable unstable
    committed uint64
    applying uint64
    applied uint64
}

Kernbereiche von RaftLog:

  • Speicher: Eine vom Benutzer implementierte Speicherschnittstelle, die zum Abrufen bereits gespeicherter Protokolleinträge verwendet wird.
  • unstabil: Speichert nicht persistente Protokolle. Wenn der Leader beispielsweise eine Anfrage von einem Client erhält, erstellt er einen Protokolleintrag mit seinem Begriff und hängt ihn an die instabilen Protokolle an.
  • festgeschrieben: Im Raft-Papier als commitIndex bekannt, stellt es den Index des letzten bekannten festgeschriebenen Protokolleintrags dar.
  • wird angewendet: Der höchste Index eines Protokolleintrags, der derzeit angewendet wird.
  • Angewandt: Im Raft-Artikel als lastApplied bekannt, handelt es sich um den höchsten Index eines Protokolleintrags, der auf die Zustandsmaschine angewendet wurde.
type unstable struct {
    entries []pb.Entry
    offset uint64
    offsetInProgress uint64
}

Kernfelder von instabil:

  • Einträge: Die nicht persistenten Protokolleinträge, die als Slice im Speicher gespeichert werden.
  • Offset: Wird verwendet, um Protokolleinträge in Einträgen dem Raft-Protokoll zuzuordnen, wobei Einträge[i] = Raft-Protokoll[i-Offset] sind.
  • offsetInProgress: Zeigt Einträge an, die derzeit beibehalten werden. Die laufenden Einträge werden durch Einträge[:offsetInProgress-offset] dargestellt.

Die Kernfelder in RaftLog sind unkompliziert und können leicht mit der Implementierung im Raft-Papier in Verbindung gebracht werden. Allerdings könnten die Felder in Unstable abstrakter erscheinen. Das folgende Beispiel soll zur Verdeutlichung dieser Konzepte beitragen.

Angenommen, wir haben bereits 5 Protokolleinträge in unserem Raft-Protokoll gespeichert. Jetzt haben wir 3 Protokolleinträge in Unstable gespeichert und diese 3 Protokolleinträge werden derzeit beibehalten. Die Situation ist wie folgt:

Understanding etcd

offset=6 gibt an, dass die Protokolleinträge an den Positionen 0, 1 und 2 in unstable.entries den Positionen 6 (0 6), 7 (1 6) und 8 (2 6) im tatsächlichen Raft Log entsprechen. Mit offsetInProgress=9 wissen wir, dass unstable.entries[:9-6], das die drei Protokolleinträge an den Positionen 0, 1 und 2 enthält, alle beibehalten werden.

Der Grund dafür, dass offset und offsetInProgress in Unstable verwendet werden, besteht darin, dass Unstable nicht alle Raft-Log-Einträge speichert.

Wann sollte man interagieren?

Da wir uns nur auf die Raft-Log-Verarbeitungslogik konzentrieren, bezieht sich „wann interagiert“ hier darauf, wann etcd Raft die Protokolleinträge übergibt, die vom Benutzer beibehalten werden müssen.

Benutzerseite

etcd Raft interagiert mit dem Benutzer hauptsächlich über Methoden in der Node-Schnittstelle. Die Ready-Methode gibt einen Kanal zurück, der es dem Benutzer ermöglicht, Daten oder Anweisungen von etcd Raft zu empfangen.

type raftLog struct {
    storage Storage
    unstable unstable
    committed uint64
    applying uint64
    applied uint64
}

Die von diesem Kanal empfangene Ready-Struktur enthält Protokolleinträge, die verarbeitet werden müssen, Nachrichten, die an andere Knoten gesendet werden sollen, den aktuellen Status des Knotens und mehr.

Für unsere Diskussion über Raft Log müssen wir uns nur auf die Felder Entries und CommittedEntries konzentrieren:

  • Einträge: Protokolleinträge, die beibehalten werden müssen. Sobald diese Einträge gespeichert sind, können sie über die Speicherschnittstelle abgerufen werden.
  • CommittedEntries: Protokolleinträge, die auf die Zustandsmaschine angewendet werden müssen.
type unstable struct {
    entries []pb.Entry
    offset uint64
    offsetInProgress uint64
}

Nach der Verarbeitung der Protokolle, Nachrichten und anderen über Ready weitergeleiteten Daten können wir die Advance-Methode in der Node-Schnittstelle aufrufen, um etcd Raft darüber zu informieren, dass wir seine Anweisungen abgeschlossen haben, sodass es das nächste Ready empfangen und verarbeiten kann.

etcd Raft bietet eine AsyncStorageWrites-Option, die die Knotenleistung bis zu einem gewissen Grad verbessern kann. Allerdings ziehen wir diese Option hier nicht in Betracht.

etcd Floßseite

Auf der Benutzerseite liegt der Fokus auf der Verarbeitung der Daten in der empfangenen Ready-Struktur. Auf der etcd-Raft-Seite liegt der Schwerpunkt darauf, zu bestimmen, wann eine Ready-Struktur an den Benutzer übergeben werden soll und welche Aktionen danach zu ergreifen sind.

Ich habe die wichtigsten an diesem Prozess beteiligten Methoden im folgenden Diagramm zusammengefasst, das die allgemeine Reihenfolge der Methodenaufrufe zeigt (beachten Sie, dass dies nur die ungefähre Reihenfolge der Aufrufe darstellt):

Understanding etcd

Sie können sehen, dass der gesamte Prozess eine Schleife ist. Hier skizzieren wir die allgemeine Funktion dieser Methoden und befassen uns in der anschließenden Schreibflussanalyse mit der Funktionsweise dieser Methoden in den Kernfeldern „raftLog“ und „unstable“.

  • HasReady: Wie der Name schon sagt, prüft es, ob eine Ready-Struktur vorhanden ist, die an den Benutzer übergeben werden muss. Wenn es beispielsweise nicht persistente Protokolleinträge in Unstable gibt, die derzeit nicht persistiert werden, gibt HasReady „true“ zurück.
  • readyWithoutAccept: Diese Methode wird aufgerufen, nachdem HasReady „true“ zurückgegeben hat. Sie erstellt die Ready-Struktur, die an den Benutzer zurückgegeben werden soll, einschließlich der Protokolleinträge, die beibehalten werden müssen, und derjenigen, die als festgeschrieben markiert sind.
  • acceptReady: Wird aufgerufen, nachdem etcd Raft die von readyWithoutAccept erstellte Ready-Struktur an den Benutzer übergeben hat. Es markiert die in „Bereit“ zurückgegebenen Protokolleinträge als im Prozess der Beibehaltung und Anwendung befindliche Protokolleinträge und erstellt einen „Rückruf“, der aufgerufen wird, wenn der Benutzer Node.Advance aufruft, und markiert die Protokolleinträge als beibehalten und angewendet.
  • Advance: Führt den in „acceptReady“ erstellten „Rückruf“ aus, nachdem der Benutzer Node.Advance aufruft.

So definieren Sie „engagiert“ und „angewandt“.

Hier sind zwei wichtige Punkte zu beachten:

1. Beharrlich ≠ engagiert

Wie ursprünglich definiert, gilt ein Protokolleintrag nur dann als festgeschrieben, wenn er von der Mehrheit der Knoten im Raft-Cluster beibehalten wurde. Selbst wenn wir also die von etcd Raft zurückgegebenen Einträge über „Ready“ beibehalten, können diese Einträge noch nicht als festgeschrieben markiert werden.

Wenn wir jedoch die Advance-Methode aufrufen, um etcd Raft darüber zu informieren, dass wir den Persistenzschritt abgeschlossen haben, wertet etcd Raft den Persistenzstatus über andere Knoten im Cluster aus und markiert einige Protokolleinträge als festgeschrieben. Diese Einträge werden uns dann über das CommittedEntries-Feld der Ready-Struktur bereitgestellt, damit wir sie auf die Zustandsmaschine anwenden können.

Bei Verwendung von etcd Raft wird daher der Zeitpunkt für die Markierung von Einträgen als festgeschrieben intern verwaltet und Benutzer müssen nur die Persistenzvoraussetzungen erfüllen.

Intern wird die Verpflichtung durch Aufrufen der Methode „raftLog.commitTo“ erreicht, die „raftLog.committed“ entsprechend dem commitIndex im Raft-Papier aktualisiert.

2. Engagiert ≠ Angewendet

Nachdem die Methode „raftLog.commitTo“ in etcd „raft“ aufgerufen wurde, gelten die Protokolleinträge bis zum Index „raft.committed“ als festgeschrieben. Einträge mit Indizes im Bereich lastApplied < index <= commitedIndex wurden noch nicht auf die Zustandsmaschine angewendet. etcd Raft gibt diese festgeschriebenen, aber nicht angewendeten Einträge im CommittedEntries-Feld von Ready zurück, sodass wir sie auf die Zustandsmaschine anwenden können. Sobald wir Advance anrufen, markiert etcd Raft diese Einträge als angewendet.

Der Zeitpunkt zum Markieren von Einträgen als angewendet wird auch intern in etcd Raft verwaltet; Benutzer müssen nur die festgeschriebenen Einträge von „Bereit“ auf die Zustandsmaschine anwenden.

Ein weiterer subtiler Punkt ist, dass in Raft nur der Leader Einträge festschreiben kann, alle Knoten sie jedoch anwenden können.

Verarbeitungsablauf einer Schreibanforderung

Hier verbinden wir alle zuvor besprochenen Konzepte, indem wir den Fluss von etcd Raft analysieren, während es eine Schreibanfrage verarbeitet.

Ausgangszustand

Um ein allgemeineres Szenario zu besprechen, beginnen wir mit einem Raft-Protokoll, das bereits drei Protokolleinträge festgeschrieben und angewendet hat.

Understanding etcd

In der Abbildung steht grün für RaftLog-Felder und die im Speicher gespeicherten Protokolleinträge, während rot für instabile Felder und die in Einträgen gespeicherten nicht persistenten Protokolleinträge steht.

Da wir drei Protokolleinträge festgeschrieben und angewendet haben, sind sowohl „festgeschrieben“ als auch „angewandt“ auf 3 gesetzt. Das Anwendungsfeld enthält den Index des höchsten Protokolleintrags aus der vorherigen Anwendung, der in diesem Fall ebenfalls 3 ist.

Zu diesem Zeitpunkt wurden keine Anfragen initiiert, daher ist unstable.entries leer. Der nächste Protokollindex im Raft-Protokoll ist 4, wodurch Offset 4 entsteht. Da derzeit keine Protokolle beibehalten werden, ist offsetInProgress ebenfalls auf 4 gesetzt.

Stellen Sie eine Anfrage

Jetzt initiieren wir eine Anfrage zum Anhängen von zwei Protokolleinträgen an das Raft-Protokoll.

Understanding etcd

Wie in der Abbildung gezeigt, werden die angehängten Protokolleinträge in unstable.entries gespeichert. Zu diesem Zeitpunkt werden keine Änderungen an den in den Kernfeldern erfassten Indexwerten vorgenommen.

HasReady

Erinnern Sie sich an die HasReady-Methode? HasReady prüft, ob nicht persistente Protokolleinträge vorhanden sind, und gibt in diesem Fall „true“ zurück.

Die Logik zur Bestimmung des Vorhandenseins nicht persistenter Protokolleinträge basiert darauf, ob die Länge von unstable.entries[offsetInProgress-offset:] größer als 0 ist. In unserem Fall gilt eindeutig:

type raftLog struct {
    storage Storage
    unstable unstable
    committed uint64
    applying uint64
    applied uint64
}

zeigt an, dass es zwei nicht persistente Protokolleinträge gibt, sodass HasReady „true“ zurückgibt.

Understanding etcd

readyWithoutAccept

Der Zweck von readyWithoutAccept besteht darin, die Ready-Struktur zu erstellen, die an den Benutzer zurückgegeben werden soll. Da wir zwei nicht persistente Protokolleinträge haben, fügt readyWithoutAccept diese beiden Protokolleinträge in das Feld „Einträge“ des zurückgegebenen „Ready“ ein.

Understanding etcd

akzeptierenBereit

acceptReady wird aufgerufen, nachdem die Ready-Struktur an den Benutzer übergeben wurde.

Understanding etcd

acceptReady aktualisiert den Index der Protokolleinträge, die gerade beibehalten werden, auf 6, was bedeutet, dass Protokolleinträge im Bereich [4, 6) jetzt als dauerhaft gespeichert werden.

Vorauszahlung

Nachdem der Benutzer die Einträge im Status „Bereit“ gespeichert hat, ruft er Node.Advance auf, um etcd Raft zu benachrichtigen. Dann kann etcd Raft den in AcceptReady erstellten „Callback“ ausführen.

Understanding etcd

Dieser „Rückruf“ löscht die bereits persistenten Protokolleinträge in unstable.entries und setzt dann den Offset auf Storage.LastIndex 1, also 6.

Protokolleinträge festschreiben

Wir gehen davon aus, dass diese beiden Protokolleinträge bereits von der Mehrheit der Knoten im Raft-Cluster gespeichert wurden, sodass wir diese beiden Protokolleinträge als festgeschrieben markieren können.

Understanding etcd

HasReady

Im weiteren Verlauf unserer Schleife erkennt HasReady das Vorhandensein von Protokolleinträgen, die festgeschrieben, aber noch nicht angewendet wurden, und gibt daher „true“ zurück.

Understanding etcd

readyWithoutAccept

readyWithoutAccept gibt ein Bereit zurück, das Protokolleinträge (4, 5) enthält, die festgeschrieben, aber nicht auf die Zustandsmaschine angewendet wurden.

Diese Einträge werden als niedrig, hoch := berechnet, indem 1 angewendet, 1 festgeschrieben wird, in einem links-offenen, rechts-geschlossenen Intervall.

Understanding etcd

akzeptierenBereit

acceptReady markiert dann die in „Ready“ zurückgegebenen Protokolleinträge [4, 5] als auf die Zustandsmaschine angewendet.

Understanding etcd

Vorauszahlung

Nachdem der Benutzer Node.Advance aufgerufen hat, führt etcd Raft den „Callback“ aus und aktualisiert Aktualisierungen auf 5, was anzeigt, dass die Protokolleinträge bei Index 5 und früher alle auf die Zustandsmaschine angewendet wurden.

Understanding etcd

Endgültiger Zustand

Damit ist der Verarbeitungsablauf für eine Schreibanforderung abgeschlossen. Der Endzustand ist wie unten dargestellt und kann mit dem Anfangszustand verglichen werden.

Understanding etcd

Zusammenfassung

Wir begannen mit einem Überblick über das Raft Log, um ein Verständnis seiner Grundkonzepte zu erlangen, gefolgt von einem ersten Blick auf die etcd-Raft-Implementierung. Anschließend haben wir uns eingehender mit den Kernmodulen von Raft Log innerhalb von etcd Raft befasst und wichtige Fragen berücksichtigt. Schließlich haben wir alles durch eine vollständige Analyse des Ablaufs einer Schreibanfrage zusammengefügt.

Ich hoffe, dieser Ansatz hilft Ihnen dabei, ein klares Verständnis der etcd-Raft-Implementierung zu erlangen und Ihre eigenen Erkenntnisse zum Raft-Log zu entwickeln.

Damit ist dieser Artikel abgeschlossen. Wenn es Fehler oder Fragen gibt, können Sie uns gerne per Privatnachricht kontaktieren oder einen Kommentar hinterlassen.

Übrigens ist Raft-Foiver eine vereinfachte Version von etcd Raft, die ich implementiert habe, wobei die gesamte Kernlogik von Raft beibehalten und entsprechend dem Prozess im Raft-Papier optimiert wurde. Ich werde in Zukunft einen separaten Beitrag veröffentlichen, in dem ich diese Bibliothek vorstelle. Bei Interesse melden Sie sich gerne bei Star, Fork oder PR!

Referenz

  • https://github.com/B1NARY-GR0UP/raft
  • https://github.com/etcd-io/raft

Das obige ist der detaillierte Inhalt vonDie Raft-Implementierung von etcd verstehen: Ein tiefer Einblick in Raft Log. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn