"Se um trabalhador quiser fazer bem o seu trabalho, ele deve primeiro afiar suas ferramentas." - Confúcio, "Os Analectos de Confúcio. Lu Linggong"
Primeira página > Programação > RPC Action EPUsando Protobuf e Criando um Plugin Personalizado

RPC Action EPUsando Protobuf e Criando um Plugin Personalizado

Publicado em 12/09/2024
Navegar:927

RPC Action EPUsing Protobuf and Creating a Custom Plugin

No artigo anterior, implementei uma interface RPC simples usando o pacote net/rpc e experimentei a codificação Gob que vem com net/rpc e codificação JSON para aprender alguns conceitos básicos de Golang RPC. Neste post, combinarei net/rpc com protobuf e criarei meu plugin protobuf para nos ajudar a gerar código, então vamos começar.

Este artigo foi publicado pela primeira vez no plano Médio MPP. Se você é um usuário do Medium, siga-me no Medium. Muito obrigado.

Devemos ter usado gRPC e protobuf durante nosso trabalho, mas eles não estão vinculados. gRPC pode ser codificado usando JSON e protobuf pode ser implementado em outras linguagens.

Protocol Buffers (Protobuf) é um formato de dados multiplataforma gratuito e de código aberto usado para serializar dados estruturados. É útil no desenvolvimento de programas que se comunicam entre si através de uma rede ou para armazenamento de dados. O método envolve uma linguagem de descrição de interface que descreve a estrutura de alguns dados e um programa que gera código-fonte a partir dessa descrição para gerar ou analisar um fluxo de bytes que representa os dados estruturados.

Um exemplo de uso do protobuf

Primeiro escrevemos um arquivo proto hello-service.proto que define uma mensagem "String"

syntax = "proto3";
package api;
option  go_package="api";

message String {
  string value = 1;
}

Em seguida, use o utilitário protoc para gerar o código Go para a mensagem String

protoc --go_out=. hello-service.proto

Em seguida, modificamos os argumentos da função Hello para usar a String gerada pelo arquivo protobuf.

type HelloServiceInterface = interface {  
    Hello(request api.String, reply *api.String) error  
}  

Usá-lo não é diferente de antes, inclusive, não é tão conveniente quanto usar string diretamente. Então, por que deveríamos usar o protobuf? Como eu disse anteriormente, usar o Protobuf para definir interfaces e mensagens de serviço RPC independentes de linguagem e, em seguida, usar a ferramenta protoc para gerar código em diferentes linguagens, é onde reside seu valor real. Por exemplo, use o plugin oficial protoc-gen-go para gerar código gRPC.

protoc --go_out=plugins=grpc. hello-service.proto

Sistema de plugins para protoc

Para gerar código a partir de arquivos protobuf, devemos instalar o protoc , mas o protoc não sabe qual é nosso idioma alvo, então precisamos de plugins para nos ajudar a gerar código. como funciona o sistema de plugins do protoc? Tome o grpc acima como exemplo.

Há um parâmetro --go_out aqui. Como o plugin que estamos chamando é protoc-gen-go, o parâmetro é chamado go_out; se o nome fosse XXX, o parâmetro se chamaria XXX_out.

Quando o protoc está em execução, ele primeiro analisa o arquivo protobuf e gera um conjunto de dados descritivos codificados em buffers de protocolo. Ele primeiro determinará se o plugin go está incluído ou não no protoc e, em seguida, tentará procurar por protoc-gen-go em $PATH e, se não conseguir encontrá-lo, reportará um erro e, em seguida, executará o protoc-gen-go. comando protoc-gen-go e envia os dados de descrição para o comando do plugin via stdin. Depois que o plug-in gera o conteúdo do arquivo, ele insere os dados codificados dos buffers de protocolo no stdout para informar ao protoc para gerar o arquivo específico.

plugins=grpc é um plugin que vem com o protoc-gen-go para invocá-lo. Se você não usá-lo, ele gerará apenas uma mensagem no Go, mas você pode usar este plugin para gerar código relacionado ao grpc.

Personalize um plugin protoc

Se adicionarmos o tempo da interface Hello ao protobuf, podemos personalizar um plugin protoc para gerar código diretamente?

syntax = "proto3";  
package api;  
option  go_package="./api";  
service HelloService {  
  rpc Hello (String) returns (String) {}  
}  
message String {  
  string value = 1;
}

Objetivo

Para este artigo, meu objetivo era criar um plugin que seria então usado para gerar código RPC do lado do servidor e do lado do cliente que seria parecido com este.

// HelloService_rpc.pb.go
type HelloServiceInterface interface {  
    Hello(String, *String) error  
}  

func RegisterHelloService(  
    srv *rpc.Server, x HelloServiceInterface,  
) error {  
    if err := srv.RegisterName("HelloService", x); err != nil {  
       return err  
    }  
    return nil  
}  

type HelloServiceClient struct {  
    *rpc.Client  
}  

var _ HelloServiceInterface = (*HelloServiceClient)(nil)  

func DialHelloService(network, address string) (  
    *HelloServiceClient, error,  
) {  
    c, err := rpc.Dial(network, address)  
    if err != nil {  
       return nil, err  
    }  
    return &HelloServiceClient{Client: c}, nil  
}  

func (p *HelloServiceClient) Hello(  
    in String, out *String,  
) error {  
    return p.Client.Call("HelloService.Hello", in, out)  
}

Isso mudaria nosso código comercial para ficar parecido com o seguinte

// service
func main() {  
    listener, err := net.Listen("tcp", ":1234")  
    if err != nil {  
       log.Fatal("ListenTCP error:", err)  
    }  
    _ = api.RegisterHelloService(rpc.DefaultServer, new(HelloService))  
    for {  
       conn, err := listener.Accept()  
       if err != nil {  
          log.Fatal("Accept error:", err)  
       }  
       go rpc.ServeConn(conn)  
    }  
}  

type HelloService struct{}  

func (p *HelloService) Hello(request api.String, reply *api.String) error {  
    log.Println("HelloService.proto Hello")  
    *reply = api.String{Value: "Hello:"   request.Value}  
    return nil  
}
// client.go
func main() {  
    client, err := api.DialHelloService("tcp", "localhost:1234")  
    if err != nil {  
       log.Fatal("net.Dial:", err)  
    }  
    reply := &api.String{}  
    err = client.Hello(api.String{Value: "Hello"}, reply)  
    if err != nil {  
       log.Fatal(err)  
    }  
    log.Println(reply)  
}

Com base no código gerado, nossa carga de trabalho já é bem menor e as chances de erro já são muito pequenas. Um bom começo.

Com base no código da API acima, podemos extrair um arquivo de modelo:

const tmplService = `  
import (  
    "net/rpc")  
type {{.ServiceName}}Interface interface {  
func Register{{.ServiceName}}(  
    if err := srv.RegisterName("{{.ServiceName}}", x); err != nil {        return err    }    return nil}  
    *rpc.Client}  
func Dial{{.ServiceName}}(network, address string) (  
{{range $_, $m := .MethodList}}  
    return p.Client.Call("{{$root.ServiceName}}.{{$m.MethodName}}", in, out)}  
`

Todo o modelo é claro e contém alguns espaços reservados, como MethodName, ServiceName, etc., que abordaremos mais tarde.

Como desenvolver um plug-in?

O Google lançou a API 1 da linguagem Go, que apresenta um novo pacote google.golang.org/protobuf/compile R/protogen, que reduz bastante a dificuldade de desenvolvimento de plugins:

  1. Primeiro de tudo, criamos um projeto de linguagem go, como protoc-gen-go-spprpc
  2. Então precisamos definir um protogen.Options, chamar seu método Run e passar um retorno de chamada de erro func(*protogen.Plugin). Este é o fim do código do processo principal.
  3. Também podemos definir o parâmetro ParamFunc de protogen.Options, para que o protogen analise automaticamente os parâmetros passados ​​​​pela linha de comando para nós. Operações como leitura e decodificação de informações de protobuf da entrada padrão, codificação de informações de entrada em protobuf e gravação de stdout são todas tratadas pelo protogen. O que precisamos fazer é interagir com protogen.Plugin para implementar a lógica de geração de código.

O mais importante para cada serviço é o nome do serviço, e então cada serviço possui um conjunto de métodos. Para o método definido pelo serviço, o mais importante é o nome do método, bem como o nome do parâmetro de entrada e o tipo do parâmetro de saída. Vamos primeiro definir um ServiceData para descrever as meta informações do serviço:

// ServiceData 
type ServiceData struct {  
    PackageName string  
    ServiceName string  
    MethodList  []Method  
}
// Method 
type Method struct {  
    MethodName     string  
    InputTypeName  string  
    OutputTypeName string  
}

Depois vem a lógica principal, e a lógica de geração de código, e finalmente a chamada ao tmpl para gerar o código.

func main() {  
    protogen.Options{}.Run(func(gen *protogen.Plugin) error {  
       for _, file := range gen.Files {  
          if !file.Generate {  
             continue  
          }  
          generateFile(gen, file)  
       }  
       return nil  
    })  
}  

// generateFile function definition
func generateFile(gen *protogen.Plugin, file *protogen.File) {  
    filename := file.GeneratedFilenamePrefix   "_rpc.pb.go"  
    g := gen.NewGeneratedFile(filename, file.GoImportPath)  
    tmpl, err := template.New("service").Parse(tmplService)  
    if err != nil {  
       log.Fatalf("Error parsing template: %v", err)  
    }  
    packageName := string(file.GoPackageName)  
// Iterate over each service to generate code
    for _, service := range file.Services {  
       serviceData := ServiceData{  
          ServiceName: service.GoName,  
          PackageName: packageName,  
       }  
       for _, method := range service.Methods {  
          inputType := method.Input.GoIdent.GoName  
          outputType := method.Output.GoIdent.GoName  

          serviceData.MethodList = append(serviceData.MethodList, Method{  
             MethodName:     method.GoName,  
             InputTypeName:  inputType,  
             OutputTypeName: outputType,  
          })  
       }  
// Perform template rendering
       err = tmpl.Execute(g, serviceData)  
       if err != nil {  
          log.Fatalf("Error executing template: %v", err)  
       }  
    }  
}

Plug-in de depuração

Finalmente, colocamos o arquivo de execução binário compilado protoc-gen-go-spprpc em $PATH e, em seguida, executamos protoc para gerar o código que desejamos.

protoc --go_out=.. --go-spprpc_out=.. HelloService.proto

Como o protoc-gen-go-spprpc precisa depender do protoc para ser executado, é um pouco complicado de depurar. Podemos usar

fmt.Fprintf(os.Stderr, "Fprintln: %v\n", err)

Para imprimir o log de erros para depuração.

Resumo

Isso é tudo neste artigo. Primeiro implementamos uma chamada RPC usando protobuf e depois criamos um plugin protobuf para nos ajudar a gerar o código. Isso abre a porta para aprendermos o protobuf RPC e é o nosso caminho para uma compreensão completa do gRPC. Espero que todos possam dominar esta tecnologia.

Referência

  1. https://taoshu.in/go/create-protoc-plugin.html
  2. https://chai2010.cn/advanced-go-programming-book/ch4-rpc/ch4-02-pb-intro.html
Declaração de lançamento Este artigo foi reproduzido em: https://dev.to/huizhou92/rpc-action-ep2-using-protobuf-and-creating-a-custom-plugin-2j9j?1 Se houver alguma violação, entre em contato com study_golang@163 .com para excluí-lo
Tutorial mais recente Mais>

Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.

Copyright© 2022 湘ICP备2022001581号-3