我第一次听说 RPC 是在分布式系统课上,当时我正在学习计算机科学。我认为这很酷,但当时我记得并不完全理解为什么我会使用 RPC 而不是使用 REST 标准。随着时间的推移,我去一家公司工作,其中部分遗留系统使用 SOAP。我记得我当时想:“嗯,有趣!它看起来像 RPC,但通过 XML 传递”。多年后,我第一次听说 gRPC,但我一直不明白它是什么、它吃什么、它有什么用。
由于我的博客提供了大量个人文档,我认为在这里记录我所学到的知识会很酷,从什么是 RPC 开始,然后转向 gRPC。
来吧,什么是RPC?
RPC 是远程过程调用的缩写。换句话说,您将过程/命令发送到远程服务器。简单来说,这就是RPC。其工作原理如下:
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() // ... }
实例化我们的服务器后,我们将需要一个处理程序,即要执行的过程。重要的是,我们始终需要定义 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 }
创建了我们的处理器,现在只需让它接受连接:
func main() { // ... h := new(Handler) log.Printf("Server listening at %v", conn.Addr()) s := rpc.NewServer() s.Register(h) s.Accept(conn) }
定义客户...
由于客户端和服务器需要遵循相同的定义结构,因此我们在这里重新定义客户端发送的参数结构:
type Args struct { Message string }
为了更简单,让我们创建一个交互式客户端:它将读取 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("-------------------------") } }
您可以看到我们需要提供服务器运行的地址以及我们要执行的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
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!
以上是gRPC:你住在哪里?你吃什么?的详细内容。更多信息请关注PHP中文网其他相关文章!

Golangisidealforbuildingscalablesystemsduetoitsefficiencyandconcurrency,whilePythonexcelsinquickscriptinganddataanalysisduetoitssimplicityandvastecosystem.Golang'sdesignencouragesclean,readablecodeanditsgoroutinesenableefficientconcurrentoperations,t

Golang在并发性上优于C ,而C 在原始速度上优于Golang。1)Golang通过goroutine和channel实现高效并发,适合处理大量并发任务。2)C 通过编译器优化和标准库,提供接近硬件的高性能,适合需要极致优化的应用。

选择Golang的原因包括:1)高并发性能,2)静态类型系统,3)垃圾回收机制,4)丰富的标准库和生态系统,这些特性使其成为开发高效、可靠软件的理想选择。

Golang适合快速开发和并发场景,C 适用于需要极致性能和低级控制的场景。1)Golang通过垃圾回收和并发机制提升性能,适合高并发Web服务开发。2)C 通过手动内存管理和编译器优化达到极致性能,适用于嵌入式系统开发。

Golang在编译时间和并发处理上表现更好,而C 在运行速度和内存管理上更具优势。1.Golang编译速度快,适合快速开发。2.C 运行速度快,适合性能关键应用。3.Golang并发处理简单高效,适用于并发编程。4.C 手动内存管理提供更高性能,但增加开发复杂度。

Golang在Web服务和系统编程中的应用主要体现在其简洁、高效和并发性上。1)在Web服务中,Golang通过强大的HTTP库和并发处理能力,支持创建高性能的Web应用和API。2)在系统编程中,Golang利用接近硬件的特性和对C语言的兼容性,适用于操作系统开发和嵌入式系统。

Golang和C 在性能对比中各有优劣:1.Golang适合高并发和快速开发,但垃圾回收可能影响性能;2.C 提供更高性能和硬件控制,但开发复杂度高。选择时需综合考虑项目需求和团队技能。

Golang适合高性能和并发编程场景,Python适合快速开发和数据处理。 1.Golang强调简洁和高效,适用于后端服务和微服务。 2.Python以简洁语法和丰富库着称,适用于数据科学和机器学习。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

适用于 Eclipse 的 SAP NetWeaver 服务器适配器
将Eclipse与SAP NetWeaver应用服务器集成。

螳螂BT
Mantis是一个易于部署的基于Web的缺陷跟踪工具,用于帮助产品缺陷跟踪。它需要PHP、MySQL和一个Web服务器。请查看我们的演示和托管服务。

Atom编辑器mac版下载
最流行的的开源编辑器

Dreamweaver Mac版
视觉化网页开发工具

SublimeText3 Linux新版
SublimeText3 Linux最新版