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.
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
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.
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; }
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.
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:
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) } } }
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.
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.
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