Registro de aprendizagem RPC da linguagem Go

Registro de aprendizagem RPC da linguagem Go

Conceito RPC

RPC (Remote Procedure Call Protocol) , é a abreviatura de remote procedure call, em termos leigos, é chamar uma função remota. Correspondente a ele é a chamada de função local, vamos dar uma olhada na chamada de função local primeiro. Quando escrevemos o seguinte código:
regras

result := Add(1,2)

Sabemos que passamos os dois parâmetros 1, 2 e chamamos uma função Add no código local para obter o valor de retorno do resultado. Neste momento, os parâmetros, valor de retorno e segmento de código estão todos em um espaço de processo, que é uma chamada de função local.

Existe alguma maneira de chamarmos uma função de processo cruzado (chamado de "remoto", um exemplo típico, este processo é implantado em outro servidor)?

Esta também é a função principal do RPC.

Por que os microsserviços precisam de RPC

Uma das vantagens do nosso uso de microsserviços é que não limitamos a seleção da tecnologia usada pelo provedor de serviços e podemos obter o desacoplamento de tecnologia entre as equipes da empresa.

Nesse caso, se não houver uma estrutura de serviço unificada e uma estrutura RPC , os provedores de serviço de cada equipe precisam implementar um conjunto de serialização, desserialização, estrutura de rede, pool de conexão, envio e recebimento de threads, processamento de tempo limite, máquina de estado, etc. A duplicação de mão de obra técnica fora da China causou ineficiência geral. Portanto, a estrutura RPC unificada para lidar com a mão de obra técnica "fora do negócio" mencionada acima é o principal problema a ser resolvido na manutenção.

Versão RPC de "hello world"

O caminho do pacote RPC da linguagem Go é net / rpc, que é colocado no diretório do pacote net. Portanto, podemos adivinhar que o pacote RPC é baseado no pacote da rede. Em seguida, tentamos implementar um exemplo semelhante com base em rpc. Primeiro construímos um tipo HelloService, onde o método Hello é usado para implementar a função de impressão:

type HelloService struct{}
func(p *HelloService)Hello(request string,reply *string)error{
*reply = "hello:" + request
return  nil
}

O método do método Hello deve atender às regras RPC da linguagem Go: o método só pode ter dois parâmetros serializáveis, dos quais o segundo parâmetro é um tipo de ponteiro e retorna um tipo de erro, e deve ser um método público.

Tipos em golang como: canal (canal), complexo (tipo complexo), função (função) não podem ser serializados

Em seguida, você pode registrar um objeto do tipo HelloService como um serviço RPC:

func main(){
    //rpc注册服务  
    //注册rpc服务,维护一个hash表,key值是服务名称,value值是服务的地址
    rpc.RegisterName("HelloService",new(HelloService))

    //设置服务监听
    listener,err := net.Listen("tcp",":1234")
    if err != nil {
        panic(err)
    }

    //接受传输的数据
    conn,err := listener.Accept()
    if err != nil {
        panic(err)
    }

    //rpc调用,并返回执行后的数据
    //1.read,获取服务名称和方法名,获取请求数据
    //2.调用对应服务里面的方法,获取传出数据
    //3.write,把数据返回给client
    rpc.ServeConn(conn)

}

A chamada de função rpc.Register registrará todos os métodos de objeto no tipo de objeto que atendem às regras RPC como funções RPC e todos os métodos registrados serão colocados no espaço de serviço "HelloService". Em seguida, estabelecemos um link TCP exclusivo e fornecemos serviços RPC para a outra parte no link TCP por meio da função rpc.ServeConn.

A seguir está o código para o cliente solicitar o serviço HelloService:

func main(){
    //用rpc连接
    client,err := rpc.Dial("tcp","localhost:1234")
    if err != nil {
        panic(err)
    }

    var reply string
    //调用服务中的函数
    err = client.Call("HelloService.Hello","world",&reply)
    if err != nil {
        panic(err)
    }

    fmt.Println("收到的数据为,",reply)
}

A primeira opção é discar para o serviço RPC por meio de rpc.Dial e, em seguida, chamar o método RPC específico por meio de client.Call. Ao chamar client.Call, o primeiro parâmetro é o nome do serviço RPC e o nome do método vinculados por pontos, e o segundo e o terceiro parâmetros, respectivamente, definem os dois parâmetros do método RPC.

RPC entre idiomas

O RPC da biblioteca padrão usa codificação gob exclusiva para a linguagem Go por padrão. Portanto, será mais difícil para outras linguagens chamar os serviços RPC implementados na linguagem Go. Linguagem cruzada é um pré-requisito para RPC na era da Internet. Aqui, implementaremos uma RPC de linguagem cruzada. Graças ao design da estrutura RPC, o RPC na linguagem Go é realmente muito fácil de implementar o suporte a várias linguagens.

Aqui, tentaremos implementar um RPC entre linguagens por meio da extensão oficial net / rpc / jsonrpc.

A primeira é reimplementar o serviço RPC com base na codificação json:

func main(){
    //注册rpc服务
    rpc.RegisterName("HelloService",new(HelloService))
    //设置监听
    listener,err := net.Listen("tcp",":1234")
    if err != nil {
        panic(err)
    }

    for{
        //接收连接
        conn,err := listener.Accept()
        if err != nil {
            panic(err)
        }
        //给当前连接提供针对json格式的rpc服务
        go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
    }
}

A maior alteração no código é substituir a função rpc.ServeConn pela função rpc.ServeCodec. Os parâmetros passados ​​são para o codec json no lado do servidor.

Depois, há o cliente que implementa a versão json:

func main(){
    //简历tcp连接
    conn,err := net.Dial("tcp","localhost:1234")
    if err !=nil{
        panic(err)
    }
    //简历基于json编解码的rpc服务
    client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))

    var reply string
    //调用rpc服务方法
    err = client.Call("HelloService.Hello"," world",&reply)
    if err != nil {
        panic(err)
    }

    fmt.Println("收到的数据为:",reply)
}

Primeiro chame manualmente a função net.Dial para estabelecer um link TCP e, em seguida, estabeleça um codec json para o cliente com base no link.

Depois de garantir que o cliente pode normalmente chamar o método de serviço RPC, podemos usar comandos para verificar quais dados o cliente envia ao servidor. Aqui usamos == nc -l 1234 == Este comando simula o servidor para monitorar os dados recebidos na porta 1234 e, em seguida, execute novamente o cliente, você verá que nc produziu as seguintes informações:

{"method":"HelloService.Hello","params":["hello"],"id":0}

Existem dois nc comumente usados, um é para se conectar ao ip e porta especificados

porta do nome do host nc

A outra é escutar na porta e esperar a conexão

porta nc -l

Este é um dado codificado em json, onde a parte do método corresponde ao nome do serviço rpc e método a ser chamado, o primeiro elemento da parte params é um parâmetro e o id é um número de chamada exclusivo mantido pelo chamador.

O objeto de dados json solicitado corresponde a duas estruturas internas: o cliente é clientRequest e o servidor é serverRequest. O conteúdo das estruturas clientRequest e serverRequest é basicamente o mesmo:

type clientRequest struct {
    Method string         `json:"method"`
    Params []interface{}  `json:"params"`
    Id     uint64         `json:"id"`
}
type serverRequest struct {
    Method string           `json:"method"`
    Params *json.RawMessage `json:"params"`
    Id     *json.RawMessage `json:"id"`
}

Depois de entender quais dados o cliente precisa enviar, podemos ver quais dados o servidor retornará após receber os dados transmitidos pelo cliente, ou usar nosso comando nc. A operação é a seguinte:

echo -e '{"method":"HelloService.Hello","params":["hello"],"id":1}'| nc localhost 1234

Os dados retornados são os seguintes:

Registro de aprendizagem RPC da linguagem Go

O id corresponde ao parâmetro de id de entrada, o resultado é o resultado retornado e a parte do erro indica informações de erro quando algo dá errado. Para chamadas sequenciais, a identificação não é necessária. No entanto, a estrutura RPC da linguagem Go suporta chamadas assíncronas. Quando a ordem dos resultados retornados é inconsistente com a ordem das chamadas, a chamada correspondente pode ser identificada por id.

Os dados json retornados também correspondem às duas estruturas internas: o cliente é clientResponse e o servidor é serverResponse. O conteúdo das duas estruturas também é semelhante:

type clientResponse struct {
    Id     uint64           `json:"id"`
    Result *json.RawMessage `json:"result"`
    Error  interface{}      `json:"error"`
}
type serverResponse struct {
    Id     *json.RawMessage `json:"id"`
    Result interface{}      `json:"result"`
    Error  interface{}      `json:"error"`
}

Portanto, independentemente da linguagem usada, desde que siga a mesma estrutura json e o mesmo processo, ela pode se comunicar com o serviço RPC escrito na linguagem Go. Dessa forma, podemos simplesmente implementar RPC entre linguagens com json.

No entanto, além de usar json para serviços RPC entre linguagens durante o desenvolvimento, muitas empresas agora escolhem protobuf para serviços RPC entre linguagens. Então, o que é ProtoBuf ? A seguir, vamos dar uma olhada mais de perto .

Pacote de protocolo RPC

O nome do serviço do código acima é codificado permanentemente e não é flexível o suficiente (é fácil de escrever errado). Aqui encapsulamos o servidor e o cliente do RPC novamente para bloquear o nome do serviço. O código específico é o seguinte

Encapsulamento do lado do servidor
//抽离服务名称
var serverName = "LoginService"

//定义一个父类
type RPCDesign interface {
    Hello(string,*string)error
}

//实现工厂函数
func RegisterRPCServer(srv RPCDesign)error{
    return rpc.RegisterName(serverName,srv)
}

O servidor encapsulado é implementado da seguinte forma:

type RpcServer struct{}

//5 + 3i    chan   func    complex
func (this *RpcServer) Hello(req string, resp *string) error {
    *resp += req + "你好"
    return nil
}

func main() {
    //设置监听
    listener, err := net.Listen("tcp", ":8899")
    if err != nil {
        fmt.Println("设置监听错误")
        return
    }
    defer listener.Close()

    fmt.Println("开始监听....")
    for {
        //接收链接
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("获取连接失败")
            return
        }
        defer conn.Close()

        fmt.Println(conn.RemoteAddr().String() + "连接成功")

        //rpc表  注册rpc服务

        if err = RegisterRPCServer(new(RpcServer)); err != nil {
            fmt.Println("注册rpc服务失败")
            return
        }

        //把rpc服务和套接字绑定
        //rpc.ServeConn(conn)
        rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
    }

}
Pacote de cliente
type RPCClient struct {
    rpcClient *rpc.Client
}

func NewRpcClient(addr string)(RPCClient){
    conn,err := net.Dial("tcp",addr)
    if err != nil {
        fmt.Println("链接服务器失败")
        return RPCClient{}
    }
    defer conn.Close()

    //套接字和rpc服务绑定
    client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
    return RPCClient{rpcClient:client}
}

func (this*RPCClient)CallFunc(req string,resp*string)error{
    return this.rpcClient.Call(serverName+".Hello",req,resp)
}

Implementação do cliente após o encapsulamento

func main()  {
    //初始化对象  与服务名有关的内容完全封装起来了
    client := NewRpcClient("127.0.0.1:8899")

    //调用成员函数
    var temp string
    client.CallFunc("xiaoming",&temp)

    fmt.Println(temp)
}

Acho que você gosta

Origin blog.51cto.com/13380838/2674839
Recomendado
Clasificación