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.
La arquitectura de raftexample es muy simple, con los archivos principales como sigue:
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:
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.proposeCLa 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.commitCEn 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
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