Maison > développement back-end > Golang > le corps du texte

gRPC : où habites-tu ? qu'est-ce que tu manges ?

Linda Hamilton
Libérer: 2024-09-28 06:07:29
original
972 Les gens l'ont consulté

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.

Allez, qu'est-ce que le RPC ?

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 :

gRPC: onde vive? o que come?

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 !

OK, mais en quoi RPC diffère-t-il d'un appel REST, par exemple ?

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.

Et comment implémenter un serveur RPC avec Go ?

Nous avons deux entités principales, le client et le serveur.

A commencer par 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()

  // ...
}
Copier après la connexion

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
}
Copier après la connexion

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)
}
Copier après la connexion

Définir le client...

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
}
Copier après la connexion

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("-------------------------")
  }
}
Copier après la connexion

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.

Et qu’est-ce que gRPC ?

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 :

  • Bibliotecas idiomáticas em 11 linguagens;
  • Framework simples para definição do seu serviço e extremamente performático.
  • Fluxo bi-direcional de dados utilizando http/2 para transporte;
  • Funcionalidades extensíveis como autenticação, tracing, balanceador de carga e verificador de saúde.

E como utilizar o gRPC com Go?

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
Copier après la connexion

E adicionar os plugins ao seu PATH:

export PATH="$PATH:$(go env GOPATH)/bin"
Copier après la connexion

A mágica do protobuf...

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;
}
Copier après la connexion

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
Copier après la connexion

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:

Construindo 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)
  }
}
Copier après la connexion

E o cliente...

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("-------------------------")
    }
}
Copier après la connexion

Quem tiver interesse para ver o código completo, pode acessar a PoC gRPC.

Considerações finais

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:

  • RPC
  • RPC RFC
  • RPC x REST
  • PoC RPC
  • net/rpc
  • encoding/gob
  • CNCF - Cloud Native Computing Foundation
  • gRPC
  • Protocol Buffer
  • PoC gRPC
  • http/1 x http/2 x gRPC
  • http/2 issue
  • JSON x Protobuffers X Flatbuffers

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!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!