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 von RaftExample ist sehr einfach, mit den Hauptdateien wie folgt:
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:
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.proposeCDer 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.commitCIn 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
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