雲栖号資訊:【 點選檢視更多行業資訊】
在這裡您可以找到不同行業的第一手的上雲資訊,還在等什麼,快來!
今天我們來了解一下 Go 語言是如何進行遠端方法調用的,遠端方法調用是服務間進行通信的基礎方式之一,是 Go 語言實作微服務架構必須掌握的開發知識和原理。
gRPC
gRPC 是一個高性能、開源、通用的 RPC 架構,由 Google 推出,基于HTTP/2 協定标準設計開發,預設采用 Protocol Buffers 資料序列化協定,支援多種開發語言。gRPC 提供了一種簡單的方法來精确的定義服務,并且為用戶端和服務端自動生成可靠代碼的功能庫。
我們來詳細了解一下 gRPC 的衆多特性:
- gRPC 使用 ProtoBuf 來定義服務、接口和資料類型,ProtoBuf 是由 Google 開發的一種資料序列化協定(類似于XML、JSON和hessian)。ProtoBuf 能夠将資料進行序列化,并廣泛應用在資料存儲和通信協定等方面。
- gRPC 支援多種語言,并能夠基于語言自動生成用戶端和服務端代碼。gRPC支援 C、C++、Node.js、Python、Ruby、Objective-C、PHP和C# 等語言,目前已提供了 C 語言版本的 gRPC、Java 語言版本的 grpc-java 和 Go 語言版本的 grpc-go,其他語言的版本正在積極開發中,其中,grpc-java 已經支援 Android 開發。

如上圖所示為 gRPC 的調用示意圖,我們可以看到,一個 C++ 語言的伺服器可以通過 gRPC 分别與 Ruby 語言開發的桌面用戶端和 Java 語言開發的 Android 用戶端進行互動。
gRPC基于 HTTP/2 标準設計,是以相對于其他 RPC 架構,gRPC 擁有更多強大功能,如雙向流、頭部壓縮、多複用請求等。這些功能給移動裝置帶來重大益處,如節省帶寬、降低 TCP 連接配接次數、提高 CPU 使用率和延長電池壽命等。同時,gRPC 還提高了雲端服務和 Web 應用的性能。gRPC 既能夠在用戶端應用,也能夠在伺服器端應用,進而以透明的方式實作用戶端和伺服器端的通信和簡化通信系統的建構。
gRPC 的安裝
首先使用 go get 指令安裝 grpc-go。
go get -u google.golang.org/grpc
接着要安裝插件,先使用 which protoc 指令檢查是否安裝了protoc;如果沒有,則使用 go install 指令安裝 proto 和 protoc-gen-go 兩個庫,最後可以使用 protoc 方法判斷是否成功安裝了。
----- 檢視 protoc 是否安裝,確定是3.0版本
$ which protoc
$ protoc --version
----- 安裝插件
$ go install github.com/golang/protobuf/proto
$ go install github.com/golang/protobuf/protoc-gen-go
----- 測試是否安裝成功
$ protoc -I pb/string.proto--go_out=plugins=grpc:.pb/string.proto
gRPC 過程調用實踐
gRPC 過程調用時,服務端和用戶端需要依賴共同的 proto 檔案。proto 檔案可以定義遠端調用的接口、方法名、參數和傳回值等。通過 proto 檔案可以自動生成用戶端和用戶端的相應 RPC 代碼。借助這些代碼,用戶端可以十分友善地發送 RPC 請求,并且服務端也可以很簡單地建立 RPC 伺服器,處理 RPC 請求并且将傳回值作為響應發送給用戶端。
定義和編譯 proto 檔案
首先,我們要定義一個 proto 檔案,其具體文法請檢視 Protobuf3 語言指南。在該檔案中,我們定義了兩個參數結果,分别是 StringRequest 和 StringResponse,同時還有一個服務結構 StringService,代碼如下:
syntax = "proto3";
package pb;
service StringService{
rpc Concat(StringRequest) returns (StringResponse) {}
rpc Diff(StringRequest) returns (StringResponse) {}
}
message StringRequest {
string A = 1;
string B = 2;
}
message StringResponse {
string Ret = 1;
string err = 2;
}
StrtingService 有兩個方法,分别為 Concat 和 Diff,每個方法都有對應的輸入參數和傳回值,這些值也都定義在 proto 檔案中。
gRPC 可以定義 4 種類型的服務接口,分别是一進制 RPC、伺服器流 RPC、用戶端流式 RPC 和雙向流 RPC。
(1)一進制 RPC 是指用戶端向伺服器發送請求并獲得響應,就像正常的函數調用一樣。
rpc Concat(StringRequest) returns (StringResponse) {}
(2)伺服器流 RPC 是指用戶端發送一個對象,伺服器端傳回一個Stream(流式消息)。
rpc LotsOfServerStream(StringRequest) returns (stream StringResponse) {}
(3)用戶端流式 RPC,用戶端發送一個 Stream(流式消息)服務端傳回一個對象。
rpc LotsOfClientStream(stream StringRequest) returns (StringResponse) {}
(4)雙向流 RPC,兩個流獨立運作,用戶端和伺服器可以按照它們喜歡的順序進行讀取和寫入;例如,伺服器可以在寫入響應之前等待接收所有用戶端消息,也可以交替地進行消息的讀取和寫入,或讀取和寫入的其他組合。每個流中消息的順序被保留。類似于 WebSocket(長連接配接),用戶端可以向服務端請求消息,伺服器端也可以向用戶端請求消息。
rpc LotsOfServerAndClientStream(stream StringRequest) returns (stream StringResponse) {}
接下來我們使用 protoc 編譯工具編譯這個protoc檔案,生成服務端和用戶端的代碼,如下:
protoc --go_out=plugins=grpc:. pb/string.proto
從 proto 檔案中的服務定義開始,gRPC 提供了生成客戶機和伺服器端代碼的 protocol buffer 編譯器插件。gRPC 使用者通常在用戶端調用這些 API,并在伺服器端實作相應的 API。
在伺服器端,伺服器實作服務聲明的方法,并運作 gRPC 伺服器來處理用戶端調用。gRPC 架構會接受網絡傳入請求,解析請求資料,執行相應服務方法和将方法結果編碼成響應通過網絡傳遞給用戶端。用戶端的本地定義方法,其方法名、參數和傳回值與服務端定義的方法相同。用戶端可以直接在本地對象上調用這些方法,将調用的參數包含在對應的 protocol buffer 消息類型中,gRPC再将請求發送到服務端,服務端解析請求。
用戶端發送 RPC 請求
我們先來看用戶端代碼,首先調用 grpc.Dial 建立網絡連接配接,然後使用 protoc 編譯生成的 pb.NewStringServiceClient 函數建立 gRPC 用戶端,然後調用用戶端的 Concat 函數,進行RPC調用,代碼如下所示:
package grpc
import (
"context"
"fmt"
"github.com/keets2012/Micro-Go-Pracrise/ch9-rpc/pb"
"google.golang.org/grpc"
)
func main() {
serviceAddress := "127.0.0.1:1234"
conn, err := grpc.Dial(serviceAddress, grpc.WithInsecure())
if err != nil {
panic("connect error")
}
defer conn.Close()
stringClient := pb.NewStringServiceClient(conn)
stringReq := &pb.StringRequest{A: "A", B: "B"}
reply, _ := stringClient.Concat(context.Background(), stringReq)
fmt.Printf("StringService Concat : %s concat %s = %s",
stringReq.A, stringReq.B, reply.Ret)
}
服務端建立 RPC 服務
再來看看伺服器端的代碼,它首先需要調用 grpc.NewServer() 來建立RPC的服務端,然後将 StringService 注冊到RPC服務端上,其具有的兩個函數分别處理 Concat 和 Diff 請求,代碼如下:
func main() {
flag.Parse()
lis, err := net.Listen("tcp","127.0.0.1:1234")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
grpcServer := grpc.NewServer()
stringService := new(string_service.StringService)
pb.RegisterStringServiceServer(grpcServer, stringService)
grpcServer.Serve(lis)
}
最後我們來看 StringService 的具體代碼實作,它首先定義了StringService 結構體,然後實作了它的 Concat 方法和 Diff 方法。
type StringService struct{}
func (s * StringService) Concat(ctx context.Context, req *pb.StringRequest) (*pb.StringResponse, error) {
if len(req.A)+len(req.B) > StrMaxSize {
response := pb.StringResponse{Ret: ""}
return &response, nil
}
response := pb.StringResponse{Ret: req.A + req.B}
return &response, nil
}
func (s * StringService) Diff(ctx context.Context, req *pb.StringRequest) (*pb.StringResponse, error) {
if len(req.A) < 1 || len(req.B) < 1 {
response := pb.StringResponse{Ret: ""}
return &response, nil
}
res := ""
if len(req.A) >= len(req.B) {
for _, char := range req.B {
if strings.Contains(req.A, string(char)) {
res = res + string(char)
}
}
} else {
for _, char := range req.A {
if strings.Contains(req.B, string(char)) {
res = res + string(char)
}
}
}
response := pb.StringResponse{Ret: res}
return &response, nil
}
如上代碼所示,StringService 的 Concat 方法和 Diff 方法實作起來都很簡單,Concat 方法就是将 StringRequest 中的 A 和 B 字元拼接在一起;而 Diff 方法則是通過循環周遊,将 A 和 B 字元的差異部分計算出來。
從上面的講述可以看出,用戶端發送一個請求後,必須等待伺服器發回響應才能繼續發送下一個請求,這種互動模式具有一定局限性,它無法更好地利用網絡帶寬,傳遞更多的請求或響應。而 gRPC 支援流式的請求響應模式來優化解決這一問題。
【雲栖号線上課堂】每天都有産品技術專家分享!
課程位址:
https://yqh.aliyun.com/live立即加入社群,與專家面對面,及時了解課程最新動态!
【雲栖号線上課堂 社群】
https://c.tb.cn/F3.Z8gvnK
原文釋出時間:2020-06-21
本文作者:大衛
本文來自:“
dockone”,了解相關資訊可以關注“dockone”