"Si un ouvrier veut bien faire son travail, il doit d'abord affûter ses outils." - Confucius, "Les Entretiens de Confucius. Lu Linggong"
Page de garde > La programmation > Comment créer votre propre système de stockage KV distribué à l'aide de la bibliothèque Raft etcd

Comment créer votre propre système de stockage KV distribué à l'aide de la bibliothèque Raft etcd

Publié le 2024-07-30
Parcourir:875

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

Introduction

raftexample est un exemple fourni par etcd qui démontre l'utilisation de la bibliothèque d'algorithmes de consensus raft etcd. raftexample implémente finalement un service de stockage clé-valeur distribué qui fournit une API REST.

Cet article lira et analysera le code de raftexample, dans l'espoir d'aider les lecteurs à mieux comprendre comment utiliser la bibliothèque raft etcd et la logique d'implémentation de la bibliothèque raft.

Architecture

L'architecture de raftexample est très simple, avec les fichiers principaux comme suit :

  • main.go : Responsable de l'organisation de l'interaction entre le module raft, le module httpapi et le module kvstore ;
  • raft.go : Responsable de l'interaction avec la bibliothèque raft, y compris la soumission de propositions, la réception des messages RPC qui doivent être envoyés et la transmission réseau, etc. ;
  • httpapi.go : Responsable de la fourniture de l'API REST, servant de point d'entrée pour les demandes des utilisateurs ;
  • kvstore.go : Responsable du stockage persistant des entrées de journal validées, équivalentes à la machine à états dans le protocole raft.

Le flux de traitement d'une demande d'écriture

Une requête d'écriture arrive dans la méthode ServeHTTP du module httpapi via une requête HTTP PUT.

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

Après avoir fait correspondre la méthode de requête HTTP via le commutateur, il entre dans le flux de traitement de la méthode PUT :

  • Lire le contenu du corps de la requête HTTP (c'est-à-dire la valeur) ;
  • Construire une proposition via la méthode Propose du module kvstore (en ajoutant une paire clé-valeur avec clé comme clé et valeur comme valeur) ;
  • Comme il n'y a aucune donnée à renvoyer, répondez au client avec 204 StatusNoContent;

La proposition est soumise à la bibliothèque d'algorithmes raft via la méthode Propose fournie par la bibliothèque d'algorithmes raft.

Le contenu d'une proposition peut consister à ajouter une nouvelle paire clé-valeur, à mettre à jour une paire clé-valeur existante, etc.

// 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)

Ensuite, examinons la méthode Propose du module kvstore pour voir comment une proposition est construite et traitée.

Dans la méthode Propose, nous codons d'abord la paire clé-valeur à écrire à l'aide de gob, puis transmettons le contenu codé à proposerC, un canal chargé de transmettre les propositions construites par le module kvstore au module raft.

// 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 



La proposition construite par kvstore et transmise à proposeC est reçue et traitée par la méthode serveChannels dans le module raft.

Après avoir confirmé que proposeC n'a pas été fermé, le module raft soumet la proposition à la bibliothèque d'algorithmes raft pour traitement à l'aide de la méthode Propose fournie par la bibliothèque d'algorithmes raft.

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



Une fois qu'une proposition est soumise, elle suit le processus de l'algorithme du radeau. La proposition sera finalement transmise au nœud leader (si le nœud actuel n'est pas le leader et que vous autorisez les abonnés à transmettre des propositions, contrôlé par la configuration DisableProposalForwarding). Le leader ajoutera la proposition en tant qu'entrée de journal à son journal de radeau et la synchronisera avec d'autres nœuds suiveurs. Après avoir été considéré comme validé, il sera appliqué à la machine à états et le résultat sera renvoyé à l'utilisateur.

Cependant, étant donné que la bibliothèque raft etcd elle-même ne gère pas la communication entre les nœuds, l'ajout au journal du radeau, l'application à la machine d'état, etc., la bibliothèque raft ne prépare que les données requises pour ces opérations. Les opérations proprement dites doivent être effectuées par nos soins.

Par conséquent, nous devons recevoir ces données de la bibliothèque raft et les traiter en conséquence en fonction de leur type. La méthode Ready renvoie un canal en lecture seule par lequel nous pouvons recevoir les données à traiter.

Il convient de noter que les données reçues comprennent plusieurs champs, tels que les instantanés à appliquer, les entrées de journal à ajouter au journal du radeau, les messages à transmettre sur le réseau, etc.

En continuant avec notre exemple de demande d'écriture (nœud leader), après avoir reçu les données correspondantes, nous devons enregistrer de manière persistante les instantanés, HardState et les entrées pour gérer les problèmes causés par des pannes de serveur (par exemple, un suiveur votant pour plusieurs candidats). HardState et Entries constituent ensemble l’état persistant sur tous les serveurs, comme mentionné dans le document. Après les avoir sauvegardés de manière persistante, nous pouvons appliquer l'instantané et l'ajouter au journal du radeau.

Puisque nous sommes actuellement le nœud leader, la bibliothèque raft nous renverra des messages de type MsgApp (correspondant à AppendEntries RPC dans l'article). Nous devons envoyer ces messages aux nœuds suiveurs. Ici, nous utilisons le rafthttp fourni par etcd pour la communication des nœuds et envoyons les messages aux nœuds suiveurs à l'aide de la méthode Send.

// raft.go
case rd := 



Ensuite, nous utilisons la méthode publiEntries pour appliquer les entrées du journal du radeau validées à la machine à états. Comme mentionné précédemment, dans l'exemple raft, le module kvstore fait office de machine à états. Dans la méthode submitEntries, nous transmettons les entrées de journal qui doivent être appliquées à la machine à états à commitC. Semblable au proposeC précédent, commitC est responsable de la transmission des entrées de journal que le module raft a considérées comme validées au module kvstore pour application à la machine à états.

// raft.go
rc.commitC 



Dans la méthode readCommits du module kvstore, les messages lus depuis commitC sont décodés par gob pour récupérer les paires clé-valeur d'origine, qui sont ensuite stockées dans une structure de carte au sein du module kvstore.

// 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)
}

De retour au module raft, nous utilisons la méthode Advance pour informer la bibliothèque raft que nous avons terminé le traitement des données lues à partir du canal Ready et que nous sommes prêts à traiter le prochain lot de données.

Plus tôt, sur le nœud leader, nous envoyions des messages de type MsgApp aux nœuds suiveurs à l'aide de la méthode Send. Le rafthttp du nœud suiveur écoute sur le port correspondant pour recevoir des requêtes et renvoyer des réponses. Qu'il s'agisse d'une requête reçue par un nœud suiveur ou d'une réponse reçue par un nœud leader, elle sera soumise à la bibliothèque raft pour traitement via la méthode Step.

raftNode implémente l'interface Raft dans rafthttp, et la méthode Process de l'interface Raft est appelée pour gérer le contenu de la demande reçue (tel que les messages MsgApp).

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

Ce qui précède décrit le flux de traitement complet d'une demande d'écriture dans l'exemple raft.

Résumé

Ceci conclut le contenu de cet article. En décrivant la structure de raftexample et en détaillant le flux de traitement d'une demande d'écriture, j'espère vous aider à mieux comprendre comment utiliser la bibliothèque raft etcd pour créer votre propre service de stockage KV distribué.

S'il y a des erreurs ou des problèmes, n'hésitez pas à commenter ou à m'envoyer un message directement. Merci.

Les références

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

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

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

Déclaration de sortie Cet article est reproduit sur : https://dev.to/justlorain/how-to-build-your-own-distributed-kv-storage-system-using-the-etcd-raft-library-2j69?1S'il y en a infraction, veuillez contacter [email protected] pour supprimer
Dernier tutoriel Plus>

Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.

Copyright© 2022 湘ICP备2022001581号-3