"Si un trabajador quiere hacer bien su trabajo, primero debe afilar sus herramientas." - Confucio, "Las Analectas de Confucio. Lu Linggong"
Página delantera > Programación > Cómo construir su propio sistema de almacenamiento KV distribuido utilizando la biblioteca etcd Raft

Cómo construir su propio sistema de almacenamiento KV distribuido utilizando la biblioteca etcd Raft

Publicado el 2024-07-30
Navegar:541

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

Introducción

raftexample es un ejemplo proporcionado por etcd que demuestra el uso de la biblioteca de algoritmos de consenso etcd raft. En última instancia, raftexample implementa un servicio distribuido de almacenamiento de valores-clave que proporciona una API REST.

Este artículo leerá y analizará el código de raftexample, con la esperanza de ayudar a los lectores a comprender mejor cómo usar la biblioteca etcd raft y la lógica de implementación de la biblioteca raft.

Arquitectura

La arquitectura de raftexample es muy simple, con los archivos principales como sigue:

  • main.go: Responsable de organizar la interacción entre el módulo raft, el módulo httpapi y el módulo kvstore;
  • raft.go: Responsable de interactuar con la biblioteca raft, incluido el envío de propuestas, la recepción de mensajes RPC que deben enviarse y la realización de transmisiones de red, etc.;
  • httpapi.go: Responsable de proporcionar la API REST, sirviendo como punto de entrada para las solicitudes de los usuarios;
  • kvstore.go: Responsable de almacenar persistentemente las entradas de registro confirmadas, equivalente a la máquina de estado en el protocolo raft.

El flujo de procesamiento de una solicitud de escritura

Llega una solicitud de escritura al método ServeHTTP del módulo httpapi a través de una solicitud HTTP PUT.

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

Después de hacer coincidir el método de solicitud HTTP mediante el conmutador, ingresa al flujo de procesamiento del método PUT:

  • Leer el contenido del cuerpo de la solicitud HTTP (es decir, el valor);
  • Construya una propuesta a través del método Propose del módulo kvstore (agregando un par clave-valor con clave como clave y valor como valor);
  • Como no hay datos para devolver, responder al cliente con 204 StatusNoContent;

La propuesta se envía a la biblioteca de algoritmos raft a través del método Propose proporcionado por la biblioteca de algoritmos raft.

El contenido de una propuesta puede ser agregar un nuevo par clave-valor, actualizar un par clave-valor existente, 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)

A continuación, analicemos el método Propose del módulo kvstore para ver cómo se construye y procesa una propuesta.

En el método Propose, primero codificamos el par clave-valor que se escribirá usando gob y luego pasamos el contenido codificado a propuestaC, un canal responsable de transmitir las propuestas construidas por el módulo kvstore al módulo 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 propuesta construida por kvstore y pasada a propuestaC es recibida y procesada por el métodoserveChannels en el módulo raft.

Después de confirmar que la propuestaC no se ha cerrado, el módulo raft envía la propuesta a la biblioteca de algoritmos raft para su procesamiento utilizando el método Propose proporcionado por la biblioteca de algoritmos raft.

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



Después de enviar una propuesta, sigue el proceso del algoritmo de balsa. La propuesta eventualmente se reenviará al nodo líder (si el nodo actual no es el líder y permite que los seguidores reenvíen propuestas, controlado por la configuración DisableProposalForwarding). El líder agregará la propuesta como una entrada de registro a su registro de balsa y la sincronizará con otros nodos seguidores. Después de que se considere comprometido, se aplicará a la máquina de estado y el resultado se devolverá al usuario.

Sin embargo, dado que la biblioteca etcd raft en sí misma no maneja la comunicación entre nodos, no se agrega al registro de la balsa, no se aplica a la máquina de estado, etc., la biblioteca de la balsa solo prepara los datos necesarios para estas operaciones. Las operaciones reales debemos realizarlas nosotros.

Por lo tanto, necesitamos recibir estos datos de la biblioteca de balsa y procesarlos en consecuencia según su tipo. El método Ready devuelve un canal de solo lectura a través del cual podemos recibir los datos que deben procesarse.

Cabe señalar que los datos recibidos incluyen múltiples campos, como instantáneas que se aplicarán, entradas de registro que se agregarán al registro de la balsa, mensajes que se transmitirán a través de la red, etc.

Continuando con nuestro ejemplo de solicitud de escritura (nodo líder), después de recibir los datos correspondientes, necesitamos guardar instantáneas, HardState y Entradas de manera persistente para manejar los problemas causados ​​por fallas del servidor (por ejemplo, un seguidor votando por múltiples candidatos). HardState y Entries juntos comprenden el estado persistente en todos los servidores como se menciona en el documento. Después de guardarlos persistentemente, podemos aplicar la instantánea y agregarla al registro de la balsa.

Dado que actualmente somos el nodo líder, la biblioteca raft nos devolverá mensajes de tipo MsgApp (correspondientes a AppendEntries RPC en el documento). Necesitamos enviar estos mensajes a los nodos seguidores. Aquí, utilizamos el rafthttp proporcionado por etcd para la comunicación con los nodos y enviamos los mensajes a los nodos seguidores utilizando el método Enviar.

// raft.go
case rd := 



A continuación, utilizamos el método PublishEntries para aplicar las entradas del registro de balsa confirmadas a la máquina de estado. Como se mencionó anteriormente, en raftexample, el módulo kvstore actúa como máquina de estados. En el método PublishEntries, pasamos las entradas de registro que deben aplicarse a la máquina de estado a commitC. De manera similar al propuestoC anterior, commitC es responsable de transmitir las entradas de registro que el módulo raft ha considerado comprometidas al módulo kvstore para su aplicación a la máquina de estado.

// raft.go
rc.commitC 



En el método readCommits del módulo kvstore, los mensajes leídos desde commitC se decodifican para recuperar los pares clave-valor originales, que luego se almacenan en una estructura de mapa dentro del módulo 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)
}

Volviendo al módulo raft, utilizamos el método Avanzado para notificar a la biblioteca raft que hemos terminado de procesar los datos leídos del canal Listo y que estamos listos para procesar el siguiente lote de datos.

Anteriormente, en el nodo líder, enviamos mensajes de tipo MsgApp a los nodos seguidores utilizando el método Enviar. El rafthttp del nodo seguidor escucha en el puerto correspondiente para recibir solicitudes y devolver respuestas. Ya sea una solicitud recibida por un nodo seguidor o una respuesta recibida por un nodo líder, se enviará a la biblioteca de la balsa para su procesamiento mediante el método Paso.

raftNode implementa la interfaz Raft en rafthttp, y se llama al método Process de la interfaz Raft para manejar el contenido de la solicitud recibida (como los mensajes MsgApp).

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

Lo anterior describe el flujo de procesamiento completo de una solicitud de escritura en raftexample.

Resumen

Con esto concluye el contenido de este artículo. Al describir la estructura de raftexample y detallar el flujo de procesamiento de una solicitud de escritura, espero ayudarlo a comprender mejor cómo usar la biblioteca etcd raft para crear su propio servicio de almacenamiento KV distribuido.

Si hay algún error o problema, no dudes en comentarme o enviarme un mensaje directamente. Gracias.

Referencias

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

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

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

Declaración de liberación Este artículo se reproduce en: https://dev.to/justlorain/how-to-build-your-own-distributed-kv-storage-system-using-the-etcd-raft-library-2j69?1Si hay alguno infracción, comuníquese con [email protected] para eliminar
Último tutorial Más>

Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.

Copyright© 2022 湘ICP备2022001581号-3