gRPC:你住在哪裡?你吃什麼?
我第一次聽到 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中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

Go語言在構建高效且可擴展的系統中表現出色,其優勢包括:1.高性能:編譯成機器碼,運行速度快;2.並發編程:通過goroutines和channels簡化多任務處理;3.簡潔性:語法簡潔,降低學習和維護成本;4.跨平台:支持跨平台編譯,方便部署。

Golang在並發性上優於C ,而C 在原始速度上優於Golang。 1)Golang通過goroutine和channel實現高效並發,適合處理大量並發任務。 2)C 通過編譯器優化和標準庫,提供接近硬件的高性能,適合需要極致優化的應用。

Golang在性能和可擴展性方面優於Python。 1)Golang的編譯型特性和高效並發模型使其在高並發場景下表現出色。 2)Python作為解釋型語言,執行速度較慢,但通過工具如Cython可優化性能。

Golang和C 在性能競賽中的表現各有優勢:1)Golang適合高並發和快速開發,2)C 提供更高性能和細粒度控制。選擇應基於項目需求和團隊技術棧。

C 更適合需要直接控制硬件資源和高性能優化的場景,而Golang更適合需要快速開發和高並發處理的場景。 1.C 的優勢在於其接近硬件的特性和高度的優化能力,適合遊戲開發等高性能需求。 2.Golang的優勢在於其簡潔的語法和天然的並發支持,適合高並發服務開發。

Golang和Python各有优势:Golang适合高性能和并发编程,Python适用于数据科学和Web开发。Golang以其并发模型和高效性能著称,Python则以简洁语法和丰富库生态系统著称。

goimpactsdevelopmentpositationality throughspeed,效率和模擬性。 1)速度:gocompilesquicklyandrunseff,IdealforlargeProjects.2)效率:效率:ITScomprehenSevestAndardArdardArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdEcceSteral Depentencies,增強的Depleflovelmentimency.3)簡單性。

Golang和C 在性能上的差異主要體現在內存管理、編譯優化和運行時效率等方面。 1)Golang的垃圾回收機制方便但可能影響性能,2)C 的手動內存管理和編譯器優化在遞歸計算中表現更為高效。
