Maison >développement back-end >Golang >gRPC : où habites-tu ? qu'est-ce que tu manges ?
La première fois que j'ai entendu parler de RPC, c'était dans un cours de systèmes distribués, alors que j'étudiais l'informatique. Je pensais que c'était cool, mais à l'époque, je me souviens de ne pas avoir compris exactement pourquoi j'utiliserais RPC au lieu d'utiliser le standard REST, par exemple. Le temps passe et je pars travailler dans une entreprise où une partie de l'ancien système utilisait SOAP. Je me souviens avoir pensé : "hmm, intéressant ! Cela ressemble à RPC, mais passe par XML". Des années plus tard, j'ai entendu parler de gRPC pour la première fois, mais je n'ai jamais vraiment compris de quoi il s'agissait, ce qu'il mangeait et à quoi il servait.
Comme mon blog contient beaucoup de documentation personnelle, j'ai pensé que ce serait sympa de documenter ici ce que j'ai appris, en commençant par ce qu'est RPC, puis en passant à gRPC.
RPC est un acronyme pour Remote Procedure Call. En d’autres termes, vous envoyez des procédures/commandes à un serveur distant. En termes simples, il s'agit de RPC. Cela fonctionne comme suit :
RPC fonctionne à la fois sur UDP et TCP. A vous de voir ce qui a du sens pour votre cas d'usage ! Si une éventuelle réponse ou même la perte de paquets ne vous dérange pas, UDP. Sinon, utilisez TCP. Pour ceux qui aiment lire les RFC, vous pouvez trouver le lien ici !
Les deux sont des manières d'architecturer les API, cependant, l'architecture REST a des principes très bien définis qui doivent être suivis pour avoir une architecture RESTfull. RPC a même des principes, mais ils sont définis entre le client et le serveur. Pour le client RPC, c'est comme s'il appelait une procédure locale.
Un autre point important est que pour RPC, peu importe que la connexion soit TCP ou UDP. Quant aux API REST, si vous souhaitez suivre RESTfull, vous ne pourrez pas utiliser UDP.
Pour ceux qui souhaitent en savoir plus, je recommande cet excellent guide AWS sur RPC x REST.
Nous avons deux entités principales, le client et le serveur.
Le serveur est un serveur WEB, couramment utilisé dans n'importe quel microservice. Définissons ensuite le type de connexion que nous utiliserons, dans notre cas, TCP a été choisi :
func main() { addr, err := net.ResolveTCPAddr("tcp", "0.0.0.0:52648") if err != nil { log.Fatal(err) } conn, err := net.ListenTCP("tcp", addr) if err != nil { log.Fatal(err) } defer conn.Close() // ... }
Avec notre serveur instancié, nous aurons besoin d'un handler, c'est-à-dire que notre procédure soit exécutée. Il est important de dire que nous devons toujours définir de quels arguments proviendront et à quoi nous répondrons dans notre connexion HTTP. Pour simplifier notre preuve de concept, nous recevrons une structure d'argument et répondrons à cette même structure :
type Args struct { Message string } type Handler int func (h *Handler) Ping(args *Args, reply *Args) error { fmt.Println("Received message: ", args.Message) switch args.Message { case "ping", "Ping", "PING": reply.Message = "pong" default: reply.Message = "I don't understand" } fmt.Println("Sending message: ", reply.Message) return nil }
Après avoir créé notre processeur, faites-lui maintenant accepter les connexions :
func main() { // ... h := new(Handler) log.Printf("Server listening at %v", conn.Addr()) s := rpc.NewServer() s.Register(h) s.Accept(conn) }
Comme le client et le serveur doivent suivre la même structure définie, redéfinissons ici la structure des arguments à envoyer par notre client :
type Args struct { Message string }
Pour faciliter les choses, créons un client interactif : il lira les entrées dans STDIN et lorsqu'il recevra une nouvelle entrée, il l'enverra à notre serveur. À des fins pédagogiques, nous écrirons la réponse reçue.
func main() { client, err := rpc.Dial("tcp", "localhost:52648") if err != nil { log.Fatal(err) } for { log.Println("Please, inform the message:") scanner := bufio.NewScanner(os.Stdin) scanner.Scan() args := Args{Message: scanner.Text()} log.Println("Sent message:", args.Message) reply := &Args{} err = client.Call("Handler.Ping", args, reply) if err != nil { log.Fatal(err) } log.Println("Received message:", reply.Message) log.Println("-------------------------") } }
Vous pouvez voir que nous devons fournir l'adresse où le serveur est exécuté et quel gestionnaire (procédure) nous voulons exécuter.
Un addendum important est que nous transportons des données binaires et que par défaut Go utilisera encoding/gob. Si vous souhaitez utiliser un autre standard, tel que JSON, vous devrez indiquer à votre serveur d'accepter ce nouveau codec.
Pour ceux qui souhaitent voir le code complet, accédez simplement au PoC.
gRPC est un framework pour écrire des applications utilisant RPC ! Ce framework est actuellement maintenu par la CNCF et selon la documentation officielle il a été créé par Google :
gRPC a été initialement créé par Google, qui utilise une seule infrastructure RPC à usage général appelée Stubby pour connecter le grand nombre de microservices exécutés au sein et entre ses centres de données depuis plus d'une décennie. En mars 2015, Google a décidé de créer la prochaine version de Stubby et de la rendre open source. Le résultat a été gRPC, qui est désormais utilisé dans de nombreuses organisations en dehors de Google pour alimenter des cas d'utilisation depuis les microservices jusqu'au « dernier kilomètre » de l'informatique (mobile, Web et Internet des objets).
En plus de fonctionner sur différents systèmes d'exploitation et sur différentes architectures, gRPC présente également les avantages suivants :
Para nossa sorte, Go é uma das 11 linguagens que tem bibliotecas oficiais para o gRPC! É importante falar que esse framework usa o Protocol Buffer para serializar a mensagem. O primeiro passo então é instalar o protobuf de forma local e os plugins para Go:
brew install protobuf go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
E adicionar os plugins ao seu PATH:
export PATH="$PATH:$(go env GOPATH)/bin"
Vamos então criar nossos arquivos .proto! Nesse arquivo vamos definir nosso serviço, quais os handlers que ele possui e para cada handler, qual a requisição e qual resposta esperadas.
syntax = "proto3"; option go_package = "github.com/mfbmina/poc_grpc/proto"; package ping_pong; service PingPong { rpc Ping (PingRequest) returns (PingResponse) {} } message PingRequest { string message = 1; } message PingResponse { string message = 1; }
Com o arquivo .proto, vamos fazer a mágica do gRPC + protobuf acontecer. Os plugins instalados acima, conseguem gerar tudo o que for necessário para um servidor ou cliente gRPC com o seguinte comando:
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/ping_pong.proto
Esse comando vai gerar dois arquivos: ping_pong.pb.go e ping_pong_grpc.pb.go. Recomendo dar uma olhada nesses arquivos para entender melhor a estrutura do servidor e do cliente. Com isso, podemos então construir o servidor:
Para conseguir comparar com o RPC comum, vamos utilizar a mesma lógica: recebemos PING e respondemos PONG. Aqui definimos um servidor e um handler para a requisição e usamos as definições vindas do protobuf para a requisição e resposta. Depois, é só iniciar o servidor:
type server struct { pb.UnimplementedPingPongServer } func (s *server) Ping(_ context.Context, in *pb.PingRequest) (*pb.PingResponse, error) { r := &pb.PingResponse{} m := in.GetMessage() log.Println("Received message:", m) switch m { case "ping", "Ping", "PING": r.Message = "pong" default: r.Message = "I don't understand" } log.Println("Sending message:", r.Message) return r, nil } func main() { l, err := net.Listen("tcp", ":50051") if err != nil { log.Fatal(err) } s := grpc.NewServer() pb.RegisterPingPongServer(s, &server{}) log.Printf("Server listening at %v", l.Addr()) err = s.Serve(l) if err != nil { log.Fatal(err) } }
Para consumir o nosso servidor, precisamos de um cliente. o cliente é bem simples também. A biblioteca do gRPC já implementa basicamente tudo que precisamos, então inicializamos um client e só chamamos o método RPC que queremos usar, no caso o Ping. Tudo vem importado do código gerado via plugins do protobuf.
func main() { conn, err := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Fatal(err) } defer conn.Close() c := pb.NewPingPongClient(conn) for { log.Println("Enter text: ") scanner := bufio.NewScanner(os.Stdin) scanner.Scan() msg := scanner.Text() log.Printf("Sending message: %s", msg) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() r, err := c.Ping(ctx, &pb.PingRequest{Message: msg}) if err != nil { log.Fatal(err) } log.Printf("Received message: %s", r.GetMessage()) log.Println("-------------------------") } }
Quem tiver interesse para ver o código completo, pode acessar a PoC gRPC.
O gRPC não é nada mais que uma abstração em cima do RPC convencional utilizando o protobuf como serializador e o protocolo http/2. Existem algumas considerações de performance ao se utilizar o http/2 e em alguns cenários, como em requisições com o corpo simples, o http/1 se mostra mais performático que o http/2. Recomendo a leitura deste benchmark e desta issue aberta no golang/go sobre o http/2. Contudo, em requisições de corpo complexo, como grande parte das que resolvemos dia a dia, gRPC se torna uma solução extremamente atraente devido ao serializador do protobuf, que é extremamente mais rápido que JSON. O Elton Minetto fez um blog post explicando melhor essas alternativas e realizando um benchmark. Um consideração também é o protobuf consegue resolver o problema de inconsistência de contratos entre servidor e cliente, contudo é necessário uma maneira fácil de distribuir os arquivos .proto.
Por fim, minha recomendação é use gRPC se sua equipe tiver a necessidade e a maturidade necessária para tal. Hoje, grande parte das aplicações web não necessitam da performance que gRPC visa propor e nem todos já trabalharam com essa tecnologia, o que pode causar uma menor velocidade e qualidade nas entregas. Como nessa postagem eu citei muitos links, decidi listar todas as referências abaixo:
Espero que vocês tenham gostado do tema e obrigado!
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!