¡Recomendar colección! Enséñele a usar la biblioteca de Consul Raft para construir un sistema distribuido completo, ¡suficiente para escribir su currículum!

Los sistemas de PC comunes incluyen etcd y consul. Lo opuesto al común es un sistema dedicado, por lo que en algunas ocasiones existe tal demanda.

Sin embargo, la usabilidad de etcd embed es extremadamente pobre. Habrá varios problemas cuando se ejecuta en Windows, y el protocolo no se puede personalizar. Debe usar el protocolo definido por etcd y el cliente para comunicarse con el clúster etcd. Así que la elección en esta vez:

1. aguantar

2. Implemente una biblioteca de algoritmos de balsa usted mismo y aplíquela

Existe una cierta posibilidad, se puede hacer al menos MIT 6.824, pero todavía hay una gran brecha con las aplicaciones industriales

3. Busque una biblioteca de balsa de calidad industrial y luego aplíquela

En este momento, vaya al algoritmo de consenso de Raft para encontrar varias bibliotecas de algoritmos de Raft opcionales, como braft, hashicorp / raft, lni / dragonboat.

Sin embargo, el código C ++ es más difícil de escribir, por lo que se pasa el braft, solo quedan la balsa del cónsul y el barco dragón.

Este artículo utiliza la balsa de cónsul para realizar un servicio KeyValue simple.

En primer lugar, el gin utilizado en el front-end proporciona interfaces put / get / inc / delete, y las tres interfaces usan la máquina de estado de balsa. Debido a que admite múltiples nodos, los nodos internos no líderes deben enviar la solicitud a el nodo líder.

El código de front-end se ve así:

func (this *ApiService) Start() error {
        //转发请求给leader节点
    this.router.Use(this.proxyHandler())

    this.router.POST("/get", this.Get)
    this.router.POST("/put", this.Put)
    this.router.POST("/delete", this.Delete)
    this.router.POST("/inc", this.Inc)

    address := fmt.Sprintf(":%d", this.port)
    return this.router.Run(address)
}

La solicitud es muy simple, es decir, conecte directamente el comando o la primitiva proporcionada por el servicio en la máquina de estado de Raft y espere a que se aplique el estado de Raft, y luego obtenga el resultado (modo futuro / promesa), como el put mando:

func (this *ApiService) Put(ctx *gin.Context) {
    req := &Request{}
    if err := ctx.ShouldBindJSON(req); err != nil {
        ctx.JSON(http.StatusBadRequest, Response{
            Error: err.Error(),
        })
        return
    }
    result, err := this.raft.ApplyCommand(raft.CommandPut, req.Key, req.Value)
    if err != nil {
        ctx.JSON(http.StatusInternalServerError, Response{
            Error: err.Error(),
        })
        return
    }
    ctx.JSON(http.StatusOK, Response{
        Value: result.Value,
    })
}

También hay un interceptor en el front-end que reenvía la solicitud al nodo líder (? Debería llamarse así, de hecho, es una especie de modo de canalización)

func (this *ApiService) proxyHandler() gin.HandlerFunc {
    return func(context *gin.Context) {
        if this.raft.IsLeader() {
            context.Next()
        } else {
            leaderServiceAddress := this.raft.GetLeaderServiceAddress()
            if this.leaderServiceAddress != leaderServiceAddress {
                Director := func(req *http.Request) {
                    req.URL.Scheme = "http"
                    req.URL.Host = leaderServiceAddress
                }
                this.leaderProxy = &httputil.ReverseProxy{
                    Director: Director,
                }
                this.leaderServiceAddress = leaderServiceAddress
            }
            this.leaderProxy.ServeHTTP(context.Writer, context.Request)
            context.Abort()
        }
    }
}

El siguiente es el tratamiento del acuerdo :

func (this *FSM) Apply(log *raft.Log) interface{} {
    result := &FSMApplyResult{
        Success: false,
    }
    t, cmd, err := raftLogToCommand(log)
    if err != nil {
        result.Error = err
        return result
    }
    binary.LittleEndian.PutUint64(keyCache, uint64(cmd.Key))
    binary.LittleEndian.PutUint64(valueCache, uint64(cmd.Value))
    switch t {
    case CommandPut:
        result.Success, result.Error = this.add(keyCache, valueCache)
    case CommandDelete:
        result.Success, result.Error = this.delete(keyCache)
    case CommandGet:
        result.Value, result.Error = this.get(keyCache)
    case CommandInc:
        result.Value, result.Error = this.inc(keyCache, cmd.Value)
    }
    return result
}

Los comandos que ingresan al estado de la balsa son realmente serializados. La máquina de estado de la balsa guardará los comandos en el almacenamiento (memoria, o disco / DB, etc.). Por lo tanto, cuando aplique el comando, primero realice la decodificación del registro de la balsa y luego cambie a proceso.

Echemos un vistazo al procesamiento de, por ejemplo, inc:

func (this *FSM) inc(key []byte, add int64) (int64, error) {
    var value int64 = 0
    err := this.db.Update(func(tx *bbolt.Tx) error {
        b, err := tx.CreateBucketIfNotExists(BBoltBucket)
        if err != nil {
            return err
        }
        valueBytes := b.Get(key)
        if len(valueBytes) != 8 {
            logging.Errorf("FSM.inc, key:%d, value length:%d, Reset",
                int64(binary.LittleEndian.Uint64(key)), len(valueBytes))
            valueBytes = make([]byte, 8)
        }
        value = int64(binary.LittleEndian.Uint64(valueBytes))
        value += add
        binary.LittleEndian.PutUint64(valueBytes, uint64(value))
        err = b.Put(key, valueBytes)
        return err
    })
    if err != nil {
        return -1, err
    }
    return value, err
}

Esta instrucción es un poco más complicada. Primero debe buscarla en la base de datos. Si la encuentra, agregue una N, guárdela y devuelva el nuevo valor. Debido a que la máquina de estado de balsa aplica el registro secuencialmente, no es necesario para bloquearlo. Sí, inc en sí mismo es atómico.

Hasta ahora, se ha implementado un servicio KeyValue distribuido simple, y también es un sistema CP.

Por supuesto, esto es solo una demostración, la aplicación real es mucho más complicada que esto, este artículo solo proporciona una idea.

No tiene que atarse a Etcd, todos los caminos conducen a Roma . Si su sistema solo necesita proporcionar principios operativos limitados, entonces puede considerar Consul Raft o DragonBoat para crear un servicio de CP de protocolo personalizado. Ant's SOFARaft también puede hacer este tipo de cosas.
Finalmente, preparé algunos materiales de aprendizaje de arquitectura Java para todos. El contenido de tecnología de aprendizaje incluye: Spring, Dubbo, MyBatis, RPC, análisis de código fuente, alta concurrencia, alto rendimiento, distribuido, optimización del rendimiento, microservicios avanzados Desarrollo de arquitectura , etc., haga clic aquí para recibir directamente.

Supongo que te gusta

Origin blog.csdn.net/jiagouwgm/article/details/112358217
Recomendado
Clasificación