gRPC: where do you live? what do you eat?

Linda Hamilton
Release: 2024-09-28 06:07:29
Original
970 people have browsed it

我第一次听说 RPC 是在分布式系统课上,当时我正在学习计算机科学。我认为这很酷,但当时我记得并不完全理解为什么我会使用 RPC 而不是使用 REST 标准。随着时间的推移,我去一家公司工作,其中部分遗留系统使用 SOAP。我记得我当时想:“嗯,有趣!它看起来像 RPC,但通过 XML 传递”。多年后,我第一次听说 gRPC,但我一直不明白它是什么、它吃什么、它有什么用。

由于我的博客提供了大量个人文档,我认为在这里记录我学到的知识会很酷,从 RPC 开始,然后转向 gRPC。

来吧,什么是RPC?

RPC 是远程过程调用的缩写。换句话说,您将过程/命令发送到远程服务器。简单来说,这就是RPC。其工作原理如下:

gRPC: onde vive? o que come?

RPC 可在 UDP 和 TCP 上工作。由您决定什么对您的用例有意义!如果您不介意可能的响应甚至丢失数据包,则使用 UDP。否则,请使用 TCP。对于那些喜欢阅读 RFC 的人,您可以在这里找到链接!

好的,但是 RPC 与 REST 调用有何不同?

两者都是构建 API 的方法,但是,REST 架构具有非常明确的原则,必须遵循这些原则才能拥有 RESTfull 架构。 RPC甚至有原理,但它们是在客户端和服务器之间定义的。对于 RPC 客户端来说,就像调用本地过程一样。

还有一点很重要,对于RPC来说,连接是TCP还是UDP并没有多大关系。至于REST API,如果你想遵循RESTfull,你将无法使用UDP。

对于那些想了解更多信息的人,我推荐这篇关于 RPC x REST 的优秀 AWS 指南。

以及如何用Go实现RPC服务器?

我们有两个主要实体,客户端和服务器。

从服务器开始...

服务器是一个WEB服务器,常用于任何微服务。然后让我们定义将使用的连接类型,在我们的例子中,选择了 TCP:

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

  // ...
}
Copy after login

实例化我们的服务器后,我们将需要一个处理程序,即要执行的过程。重要的是,我们始终需要定义 HTTP 连接中的参数来自哪些参数以及我们将响应什么。为了简化我们的概念证明,我们将收到一个参数结构并响应相同的结构:

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
}
Copy after login

创建了我们的处理器,现在只需让它接受连接:

func main() {
  // ...

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

定义客户...

由于客户端和服务器需要遵循相同的定义结构,这里我们将重新定义客户端发送的参数结构:

type Args struct {
  Message string
}
Copy after login

为了更简单,让我们创建一个交互式客户端:它将读取 STDIN 中的条目,当它收到新条目时,会将其发送到我们的服务器。出于教育目的,我们将写下收到的答案。

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

您可以看到我们需要提供服务器运行的地址以及我们要执行的Handler(过程)。

一个重要的附录是我们正在传输二进制数据,默认情况下 Go 将使用编码/gob。如果您想使用其他标准,例如 JSON,您需要告诉您的服务器接受新的编解码器。

想要查看完整代码的人,只需访问 PoC。

什么是 gRPC?

gRPC 是一个使用 RPC 编写应用程序的框架!该框架目前由 CNCF 维护,根据官方文档,它是由 Google 创建的:

gRPC 最初由 Google 创建,十多年来,Google 一直使用名为 Stubby 的单一通用 RPC 基础设施来连接其数据中心内部和跨数据中心运行的大量微服务。 2015 年 3 月,Google 决定构建 Stubby 的下一个版本并将其开源。结果就是 gRPC,它现在已在 Google 之外的许多组织中使用,为从微服务到计算“最后一英里”(移动、网络和物联网)的用例提供支持。

除了可以工作在不同操作系统、不同架构上之外,gRPC 还具有以下优点:

  • 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
Copy after login

E adicionar os plugins ao seu PATH:

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

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;
}
Copy after login

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
Copy after login

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)
  }
}
Copy after login

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

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!

The above is the detailed content of gRPC: where do you live? what do you eat?. For more information, please follow other related articles on the PHP Chinese website!

source:dev.to
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Articles by Author
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!