„Wenn ein Arbeiter seine Arbeit gut machen will, muss er zuerst seine Werkzeuge schärfen.“ – Konfuzius, „Die Gespräche des Konfuzius. Lu Linggong“
Titelseite > Programmierung > So erstellen Sie Ihr eigenes verteiltes KV-Speichersystem mithilfe der etcd Raft-Bibliothek

So erstellen Sie Ihr eigenes verteiltes KV-Speichersystem mithilfe der etcd Raft-Bibliothek

Veröffentlicht am 30.07.2024
Durchsuche:967

How to Build Your Own Distributed KV Storage System Using the etcd Raft Library

Einführung

raftexample ist ein von etcd bereitgestelltes Beispiel, das die Verwendung der etcd-Raft-Konsensalgorithmus-Bibliothek demonstriert. Raftexample implementiert letztendlich einen verteilten Schlüsselwert-Speicherdienst, der eine REST-API bereitstellt.

In diesem Artikel wird der Code von „raftexample“ gelesen und analysiert, in der Hoffnung, den Lesern ein besseres Verständnis für die Verwendung der etcd-Raft-Bibliothek und die Implementierungslogik der Raft-Bibliothek zu vermitteln.

Die Architektur

Die Architektur von RaftExample ist sehr einfach, mit den Hauptdateien wie folgt:

  • main.go: Verantwortlich für die Organisation der Interaktion zwischen dem Raft-Modul, dem httpapi-Modul und dem kvstore-Modul;
  • raft.go: Verantwortlich für die Interaktion mit der Raft-Bibliothek, einschließlich der Einreichung von Vorschlägen, dem Empfang von RPC-Nachrichten, die gesendet werden müssen, und der Durchführung von Netzwerkübertragungen usw.;
  • httpapi.go: Verantwortlich für die Bereitstellung der REST-API, die als Einstiegspunkt für Benutzeranfragen dient;
  • kvstore.go: Verantwortlich für die dauerhafte Speicherung festgeschriebener Protokolleinträge, äquivalent zur Zustandsmaschine im Raft-Protokoll.

Der Verarbeitungsablauf einer Schreibanforderung

Eine Schreibanforderung kommt über eine HTTP-PUT-Anfrage in der ServeHTTP-Methode des httpapi-Moduls an.

curl -L http://127.0.0.1:12380/key -XPUT -d value

Nachdem die HTTP-Anforderungsmethode über den Schalter abgeglichen wurde, gelangt sie in den Verarbeitungsablauf der PUT-Methode:

  • Lesen Sie den Inhalt aus dem HTTP-Anfragetext (d. h. den Wert);
  • Erstellen Sie einen Vorschlag mithilfe der Propose-Methode des kvstore-Moduls (Hinzufügen eines Schlüssel-Wert-Paares mit Schlüssel als Schlüssel und Wert als Wert);
  • Da keine Daten zurückgegeben werden müssen, antworten Sie dem Client mit 204 StatusNoContent;

Der Vorschlag wird über die von der Raft-Algorithmusbibliothek bereitgestellte Propose-Methode an die Raft-Algorithmusbibliothek übermittelt.

Der Inhalt eines Vorschlags kann das Hinzufügen eines neuen Schlüssel-Wert-Paares, das Aktualisieren eines vorhandenen Schlüssel-Wert-Paares usw. sein.

// httpapi.go
v, err := io.ReadAll(r.Body)
if err != nil {
    log.Printf("Failed to read on PUT (%v)\n", err)
    http.Error(w, "Failed on PUT", http.StatusBadRequest)
    return
}
h.store.Propose(key, string(v))
w.WriteHeader(http.StatusNoContent)

Als nächstes schauen wir uns die Propose-Methode des kvstore-Moduls an, um zu sehen, wie ein Vorschlag erstellt und verarbeitet wird.

In der Propose-Methode codieren wir zuerst das zu schreibende Schlüssel-Wert-Paar mit gob und übergeben dann den codierten Inhalt an ProposeC, einen Kanal, der für die Übertragung der vom KVStore-Modul erstellten Vorschläge an das Raft-Modul verantwortlich ist.

// kvstore.go
func (s *kvstore) Propose(k string, v string) {
    var buf strings.Builder
    if err := gob.NewEncoder(&buf).Encode(kv{k, v}); err != nil {
        log.Fatal(err)
    }
    s.proposeC 



Der von kvstore erstellte und an ProposC übergebene Vorschlag wird von der ServeChannels-Methode im Raft-Modul empfangen und verarbeitet.

Nachdem bestätigt wurde, dass „proposC“ nicht geschlossen wurde, übermittelt das Raft-Modul den Vorschlag an die Raft-Algorithmusbibliothek zur Verarbeitung mithilfe der von der Raft-Algorithmusbibliothek bereitgestellten Propose-Methode.

// raft.go
select {
    case prop, ok := 



Nachdem ein Vorschlag eingereicht wurde, folgt er dem Raft-Algorithmus-Prozess. Der Vorschlag wird schließlich an den führenden Knoten weitergeleitet (wenn der aktuelle Knoten nicht der führende Knoten ist und Sie Followern erlauben, Vorschläge weiterzuleiten, gesteuert durch die DisableProposalForwarding-Konfiguration). Der Anführer fügt den Vorschlag als Protokolleintrag zu seinem Raft-Protokoll hinzu und synchronisiert ihn mit anderen Folgeknoten. Nachdem es als festgeschrieben gilt, wird es auf die Zustandsmaschine angewendet und das Ergebnis wird an den Benutzer zurückgegeben.

Da die etcd-Raft-Bibliothek selbst jedoch nicht die Kommunikation zwischen Knoten, das Anhängen an das Raft-Protokoll, die Anwendung auf die Zustandsmaschine usw. übernimmt, bereitet die Raft-Bibliothek nur die für diese Vorgänge erforderlichen Daten vor. Die eigentlichen Operationen müssen von uns durchgeführt werden.

Daher müssen wir diese Daten von der Raft-Bibliothek erhalten und sie je nach Typ entsprechend verarbeiten. Die Ready-Methode gibt einen schreibgeschützten Kanal zurück, über den wir die zu verarbeitenden Daten empfangen können.

Es ist zu beachten, dass die empfangenen Daten mehrere Felder umfassen, z. B. anzuwendende Snapshots, an das Raft-Protokoll anzuhängende Protokolleinträge, über das Netzwerk zu übertragende Nachrichten usw.

Um mit unserem Schreibanforderungsbeispiel (Leader-Knoten) fortzufahren, müssen wir nach Erhalt der entsprechenden Daten Snapshots, HardState und Einträge dauerhaft speichern, um Probleme zu bewältigen, die durch Serverabstürze verursacht werden (z. B. wenn ein Follower für mehrere Kandidaten stimmt). HardState und Entries bilden zusammen den Persistent-Status auf allen Servern, wie im Dokument erwähnt. Nachdem wir sie dauerhaft gespeichert haben, können wir den Snapshot anwenden und an das Raft-Protokoll anhängen.

Da wir derzeit der führende Knoten sind, sendet die Raft-Bibliothek Nachrichten vom Typ MsgApp an uns zurück (entsprechend AppendEntries RPC im Artikel). Wir müssen diese Nachrichten an die Follower-Knoten senden. Hier verwenden wir das von etcd bereitgestellte Rafthttp für die Knotenkommunikation und senden die Nachrichten mithilfe der Send-Methode an Follower-Knoten.

// raft.go
case rd := 



Als nächstes verwenden wir die Methode „publishEntries“, um die festgeschriebenen Raft-Protokolleinträge auf die Zustandsmaschine anzuwenden. Wie bereits erwähnt, fungiert das kvstore-Modul im Raft-Beispiel als Zustandsmaschine. In der Methode „publishEntries“ übergeben wir die Protokolleinträge, die auf die Zustandsmaschine angewendet werden müssen, an commitC. Ähnlich wie das frühere ProposC ist CommitC für die Übertragung der Protokolleinträge, die das Raft-Modul als festgeschrieben erachtet hat, an das KVstore-Modul zur Anwendung auf die Zustandsmaschine verantwortlich.

// raft.go
rc.commitC 



In der readCommits-Methode des kvstore-Moduls werden von commitC gelesene Nachrichten gob-dekodiert, um die ursprünglichen Schlüssel-Wert-Paare abzurufen, die dann in einer Kartenstruktur innerhalb des kvstore-Moduls gespeichert werden.

// kvstore.go
for commit := range commitC {
    ...
    for _, data := range commit.data {
        var dataKv kv
        dec := gob.NewDecoder(bytes.NewBufferString(data))
        if err := dec.Decode(&dataKv); err != nil {
            log.Fatalf("raftexample: could not decode message (%v)", err)
        }
        s.mu.Lock()
        s.kvStore[dataKv.Key] = dataKv.Val
        s.mu.Unlock()
    }
    close(commit.applyDoneC)
}

Zurück zum Raft-Modul: Wir verwenden die Advance-Methode, um der Raft-Bibliothek mitzuteilen, dass wir die Verarbeitung der aus dem Ready-Kanal gelesenen Daten abgeschlossen haben und bereit sind, den nächsten Datenstapel zu verarbeiten.

Zuvor haben wir auf dem Leader-Knoten mithilfe der Send-Methode Nachrichten vom Typ MsgApp an die Follower-Knoten gesendet. Der Rafthttp des Follower-Knotens lauscht am entsprechenden Port, um Anfragen zu empfangen und Antworten zurückzugeben. Unabhängig davon, ob es sich um eine von einem Follower-Knoten empfangene Anfrage oder eine von einem Leader-Knoten empfangene Antwort handelt, wird sie zur Verarbeitung durch die Step-Methode an die Raft-Bibliothek übermittelt.

raftNode implementiert die Raft-Schnittstelle in Rafthttp, und die Process-Methode der Raft-Schnittstelle wird aufgerufen, um den empfangenen Anforderungsinhalt (z. B. MsgApp-Nachrichten) zu verarbeiten.

// raft.go
func (rc *raftNode) Process(ctx context.Context, m raftpb.Message) error {
    return rc.node.Step(ctx, m)
}

Oben wird der vollständige Verarbeitungsablauf einer Schreibanforderung im Raft-Beispiel beschrieben.

Zusammenfassung

Damit ist der Inhalt dieses Artikels abgeschlossen. Indem ich die Struktur von „raftexample“ skizziere und den Verarbeitungsablauf einer Schreibanforderung detailliert beschreibe, hoffe ich, Ihnen dabei zu helfen, besser zu verstehen, wie Sie die etcd-Raft-Bibliothek verwenden, um Ihren eigenen verteilten KV-Speicherdienst aufzubauen.

Wenn es Fehler oder Probleme gibt, können Sie mir gerne einen Kommentar hinterlassen oder mir direkt eine Nachricht senden. Danke schön.

Verweise

  • https://github.com/etcd-io/etcd/tree/main/contrib/raftexample

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

  • https://raft.github.io/raft.pdf

Freigabeerklärung Dieser Artikel ist abgedruckt unter: https://dev.to/justlorain/how-to-build-your-own-distributed-kv-storage-system-using-the-etcd-raft-library-2j69?1Falls vorhanden Verstoß, wenden Sie sich zum Löschen bitte an [email protected]
Neuestes Tutorial Mehr>

Haftungsausschluss: Alle bereitgestellten Ressourcen stammen teilweise aus dem Internet. Wenn eine Verletzung Ihres Urheberrechts oder anderer Rechte und Interessen vorliegt, erläutern Sie bitte die detaillierten Gründe und legen Sie einen Nachweis des Urheberrechts oder Ihrer Rechte und Interessen vor und senden Sie ihn dann an die E-Mail-Adresse: [email protected] Wir werden die Angelegenheit so schnell wie möglich für Sie erledigen.

Copyright© 2022 湘ICP备2022001581号-3