「労働者が自分の仕事をうまくやりたいなら、まず自分の道具を研ぎ澄まさなければなりません。」 - 孔子、「論語。陸霊公」
表紙 > プログラミング > gRPC: どこに住んでいますか?何を食べますか?

gRPC: どこに住んでいますか?何を食べますか?

2024 年 11 月 8 日に公開
ブラウズ:657

A primeira vez que ouvi falar sobre RPC foi em uma aula de sistema distribuídos, ainda quando estava cursando a graduação em Ciência da Computação. Achei legal, mas na época lembro de não compreender exatamente o porque eu usaria RPC ao invés de usar o padrão REST, por exemplo. Passa o tempo, e vou trabalhar em uma empresa em que parte do sistema legado era utilizando SOAP. Lembro de pensar: "hmm, interessante! Parece com RPC, mas traféga XML". Anos depois, ouço pela primeira vez falar sobre gRPC, mas nunca entendi complementamente o que era, o que comia e pra que servia.

Como meu blog serve muito de documentação pessoal, achei legal documentar aqui o que aprendi sobre, começando sobre o que é RPC e depois indo para o gRPC.

Vamos lá, o que é RPC?

RPC é uma sigla para Remote Procedure Call (em Português Chamada de Procedimento Remoto). Ou seja, você envia procedimentos/comandos para um servidor remoto. Sendo simples e direto, isso é RPC. Ele funciona da seguinte forma:

gRPC: onde vive? o que come?

O RPC funciona tanto sobre UDP, quanto TCP. Cabe a você ver o que faz sentido para seu caso de uso! Se você não se importa com uma eventual resposta ou até mesmo em perder pacotes, UDP. Caso contrário, use TCP. Para aqueles que gostam de ler as RFCs, pode encontrar o link aqui!

OK, mas como o RPC se difere de uma chamada REST, por exemplo?

Ambos são maneiras de arquiteturar APIs, porém, a arquitetura REST possuí principíos muito bem definidos e que devem ser seguidos para se ter uma arquitetura RESTfull. O RPC até possui principios, mas eles são definidos entre cliente e servidor. Para o cliente RPC, é como se ele tivesse chamando um procedimento local.

Outro ponto importante é que para o RPC, não importa muito se a conexão é TCP ou UDP. Já para APIs REST, se você quiser seguir o RESTfull, não vai conseguir utilizar UDP.

Para quem quiser saber mais sobre, recomendo este excelente guia da AWS sobre RPC x REST.

E como implementar um servidor RPC com Go?

Temos duas entidades principais, o cliente e o servidor.

Começando pelo servidor...

O servidor é um servidor WEB, comumente usado em qualquer microsserviço. Vamos definir então o tipo de conexão que vamos utilizar, para nosso caso, TCP foi o escolhido:

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()

  // ...
}

Com o nosso servidor instânciado, vamos precisar de um handler, ou seja, nosso procedimento a ser executado. É importante dizer que precisamos definir sempre o que vai vir de argumentos e o que vamos responder na nossa conexão HTTP. Para simplificar nossa prova de conceito, vamos receber uma estrutura de argumentos e responder essa mesma estrutura:

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
}

Tendo o nosso processador criado, agora é só fazer ele aceitar as conexões:

func main() {
  // ...

  h := new(Handler)
  log.Printf("Server listening at %v", conn.Addr())
  s := rpc.NewServer()
  s.Register(h)
  s.Accept(conn)
}

Definindo o cliente...

Como o cliente e o servidor precisam seguir a mesma estrutura definida, vamos redefinir aqui a estrutura de argumentos a ser enviada pelo nosso cliente:

type Args struct {
  Message string
}

Para facilitar, vamos fazer um cliente interativo: ele vai ficar lendo entradas no STDIN e ao receber uma nova entrada, ele envia para o nosso servidor. Para fins didáticos, vamos escrever a resposta recebida.

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("-------------------------")
  }
}

Pode-se notar que precisamos fornecer o endereço de onde o servidor está rodando e qual o Handler (procedimento) que queremos executar.

Um adendo importante é que estamos trafegando dados binários e por padrão o Go vai utilizar o encoding/gob. Se quiser utilizar um outro padrão, como por exemplo JSON, vai ser preciso dizer ao seu servidor que aceite aquele codec novo.

Para quem quiser ver o código completo, é só acessar a PoC.

E o que é gRPC?

gRPC é um framework para se escrever aplicações usando RPC! Esse framework hoje é mantido pela CNCF e segundo a documentação oficial foi criado pela Google:

gRPC was initially created by Google, which has used a single general-purpose RPC infrastructure called Stubby to connect the large number of microservices running within and across its data centers for over a decade. In March 2015, Google decided to build the next version of Stubby and make it open source. The result was gRPC, which is now used in many organizations outside of Google to power use cases from microservices to the "last mile" of computing (mobile, web, and Internet of Things).

Além de funcionar em diversos sistemas operacionais e em diversas arquiteturas, o gRPC ainda possui as seguintes vantagens:

  • 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

E adicionar os plugins ao seu PATH:

export PATH="$PATH:$(go env GOPATH)/bin"

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;
}

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:

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)
  }
}

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("-------------------------")
    }
}

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!

リリースステートメント この記事は次の場所に転載されています: https://dev.to/mfbmina/grpc-onde-vive-o-que-come-5049?1 侵害がある場合は、[email protected] に連絡して削除してください。
最新のチュートリアル もっと>
  • 画像を任意の角度で回転するための React フックを作成する
    画像を任意の角度で回転するための React フックを作成する
    Web 開発では画像を回転する必要がある場合がありますが、これは CSS で簡単に行うことができます。次のような単純なコード:rotate(90deg);。しかし、それを JS でやりたい場合はどうすればよいでしょうか? TLDR ブラウザ環境のキャンバスに画像を描画し、回転させます...
    プログラミング 2024 年 11 月 8 日に公開
  • Lithe のミドルウェア: その仕組みと独自のミドルウェアを作成する方法
    Lithe のミドルウェア: その仕組みと独自のミドルウェアを作成する方法
    ミドルウェアは、アプリケーションに入る HTTP リクエストを検査およびフィルタリングするための便利なメカニズムを提供します。 たとえば、Lithe には、ユーザーが認証されているかどうかを確認するミドルウェアが含まれています。そうでない場合、ミドルウェアはユーザーをログイン画面にリダイレクトします...
    プログラミング 2024 年 11 月 8 日に公開
  • JavaScript で要素が繰り返される配列を作成するにはどうすればよいですか?
    JavaScript で要素が繰り返される配列を作成するにはどうすればよいですか?
    JavaScript で繰り返される要素の配列同じ要素が複数回繰り返される配列を作成することは、さまざまなプログラミング シナリオで不可欠です。 Python では、これは [2] * 5 にあるようにリスト乗算で実現できます。ただし、この機能は JavaScript の配列では直接利用できません。...
    プログラミング 2024 年 11 月 8 日に公開
  • ## MySQL における LIKE と LOCATE: パフォーマンスの点で優れているのはどちらの演算子ですか?
    ## MySQL における LIKE と LOCATE: パフォーマンスの点で優れているのはどちらの演算子ですか?
    MySQL LIKE と LOCATE のパフォーマンスの比較MySQL でデータを検索するとき、LIKE と LOCATE のどちらの演算子がより効率的であるか疑問に思うかもしれません。この記事では、これら 2 つの演算子のパフォーマンスの違いについて説明します。一般的な使用シナリオでは、LIKE...
    プログラミング 2024 年 11 月 8 日に公開
  • PHP を使用してフォーム データで複数の MySQL 行を更新するにはどうすればよいですか?
    PHP を使用してフォーム データで複数の MySQL 行を更新するにはどうすればよいですか?
    フォーム データによる複数の MySQL 行の更新Web 開発では、ユーザーがデータベースのレコードを編集できるフォームを使用するのが一般的です。一般的なシナリオは、変更されたデータで同じテーブル内の複数の行を更新することです。これは、PHP と MySQL を使用して実現できます。フォームの構造と...
    プログラミング 2024 年 11 月 8 日に公開
  • Go で []byte を文字列に代入できないのはなぜですか?
    Go で []byte を文字列に代入できないのはなぜですか?
    バイト割り当てエラーについて: [] バイトを文字列に割り当てることができませんフォルダー内のファイルを読み取ろうとしたときに、エラーが発生しましたファイルの内容を読み取ろうとすると、「複数の割り当てで []byte を z (文字列型) に割り当てることができません」というメッセージが表示されます...
    プログラミング 2024 年 11 月 8 日に公開
  • React と Typescript を使用してカスタム テーブル コンポーネントを作成する方法 (パート 2)
    React と Typescript を使用してカスタム テーブル コンポーネントを作成する方法 (パート 2)
    導入 わーい! ?この 2 部構成のシリーズの最終部分に到達しました。まだパート 1 をチェックしていない場合は、ここで止めて、最初にパート 1 を読んでください。心配しないでください。戻ってくるまで待っています。 ? パート 1 では、CustomTable コンポーネントを構築...
    プログラミング 2024 年 11 月 8 日に公開
  • TypeScript と ioredis を使用して Node.js で高性能キャッシュ マネージャーを構築する
    TypeScript と ioredis を使用して Node.js で高性能キャッシュ マネージャーを構築する
    ioredis 上に構築された多用途で使いやすいキャッシュ マネージャーを使用して、Node.js アプリのパフォーマンスを向上させます。キャッシュを簡素化し、効率を最適化し、運用を合理化します。 私は、使いやすさとパフォーマンスに重点を置いて、自分のニーズに合わせて ioredis 上に構築された...
    プログラミング 2024 年 11 月 8 日に公開
  • スーパークラス参照とサブクラスオブジェクト
    スーパークラス参照とサブクラスオブジェクト
    Java は厳密に型指定された言語です。 標準変換と自動プロモーションはプリミティブ型に適用されます。 タイプの互換性は厳密に適用されます。 通常、あるクラスの参照変数は別のクラスのオブジェクトを参照できません。 クラス X とクラス Y は構造的に同じであっても、型が異なるため、X の参照を Y...
    プログラミング 2024 年 11 月 8 日に公開
  • Flexbox における flex-grow と width はどのように異なりますか?
    Flexbox における flex-grow と width はどのように異なりますか?
    Flexbox の flex-grow と width の違いFlexbox には、要素間でスペースを分配するための 2 つの主な方法、flex-grow と width が用意されています。これらのプロパティの違いを理解することは、フレックスボックスを効果的に使用するために非常に重要です。Flex...
    プログラミング 2024 年 11 月 8 日に公開
  • フォームのラベルと入力を同じ行に水平方向に揃えるにはどうすればよいですか?
    フォームのラベルと入力を同じ行に水平方向に揃えるにはどうすればよいですか?
    フォームのラベルと入力を同じ行に水平方向に配置するWeb 開発では、フォームの美しさはユーザー エクスペリエンスにとって非常に重要です。ラベルと入力フィールドを同じ行に配置すると、フォームの読みやすさと使いやすさが向上します。この記事では、入力要素の長さに関係なく、入力要素をそのラベルとシームレスに...
    プログラミング 2024 年 11 月 8 日に公開
  • 再帰 -1
    再帰 -1
    はじめに 1 関数がそれ自体を呼び出すプロセスは再帰と呼ばれ、 対応する関数は 再帰関数. と呼ばれます。 コンピュータープログラミングは数学の基本的な応用なので、let まず、再帰の背後にある数学的推論を理解しようとします。 一般に、関数の概念は誰もが知っています。簡単に言うと、...
    プログラミング 2024 年 11 月 8 日に公開
  • ロギングおよびエラー処理ミドルウェアを Go API に追加する
    ロギングおよびエラー処理ミドルウェアを Go API に追加する
    簡単なメモ: JWT 認証に関する私の以前の投稿をチェックして、レンダリングの問題がいくつかあることに気づいた場合は、それらは現在修正されています。これらの例はそのチュートリアルの上に構築されているため、必ずもう一度見てください。 :) さて、Go API を実行し、JWT 認証を追加し、Postg...
    プログラミング 2024 年 11 月 8 日に公開
  • Tensorflow 音楽予測
    Tensorflow 音楽予測
    この記事では、tensorflow を使用して音楽のスタイルを予測する方法を示します。 私の例では、テクノとクラシック音楽を比較します。 コードは私の github にあります: https://github.com/victordalet/sound_to_partition I ...
    プログラミング 2024 年 11 月 8 日に公開
  • useEffect フックの説明
    useEffect フックの説明
    useEffect フックは React の基本的な部分であり、機能コンポーネントで副作用を実行できるようにします。詳細な内訳は次のとおりです: useEffectとは何ですか? useEffect フックを使用すると、データのフェッチ、サブスクリプション、DOM の手動変更などの副...
    プログラミング 2024 年 11 月 8 日に公開

免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。

Copyright© 2022 湘ICP备2022001581号-3