1. 程式人生 > >【實戰分享】從選型到專案落地,漫談 gRPC

【實戰分享】從選型到專案落地,漫談 gRPC

## 什麼是 gRPC? **gRPC 的幾種常見模式** 在學習 gRPC 的時候,相信大家對於它的四種模式都有了解,我們來簡單回顧一下: - 簡單模式(Simple RPC):這種模式最為傳統,即客戶端發起一次請求,服務端響應一個數據,這和大家平時熟悉的 RPC 沒有什麼大的區別,所以不再詳細介紹。 - 服務端資料流模式(Server-side streaming RPC):這種模式是客戶端發起一次請求,服務端返回一段連續的資料流。典型的例子是客戶端向服務端傳送一個股票程式碼,服務端就把該股票的實時資料來源源不斷的返回給客戶端。如果是使用我們容器雲功能的同學應該會發現,我們的容器實時日誌流就是使用了這個典型模式。 - 客戶端資料流模式(Client-side streaming RPC):與服務端資料流模式相反,這次是客戶端源源不斷地向服務端傳送資料流,而在傳送結束後,由服務端返回一個響應。典型的例子是物聯網終端向伺服器報送資料。 - 雙向資料流模式(Bidirectional streaming RPC):顧名思義,這是客戶端和服務端都可以向對方傳送資料流,這個時候雙方的資料可以同時互相傳送,也就是可以實現實時互動。典型的例子是聊天機器人。 接下來我們通過一個小例子來看看 gRPC 具體的使用流程。 假設我們有一個聊天機器人,現需要增加一個對外提供服務的介面。具體需求為,介面傳入引數是一個人名,返回一段內容是“Hello 人名”的音訊。如果這個是讓你在不使用 gRPC 的情況下,你會怎麼做?大家可能會選擇使用 restful api 來實現這個功能,傳入人名,返回音訊二進位制資料。 那麼如果使用 gRPC,我們需要怎麼來設計呢? 第一步,需要定義一個介面文件,也就是 proto 檔案。在定義內會定義一個 Service,接下來再在 Service 裡定義一個 SayHello 的方法。下面定義傳入引數,輸入 name 返回 message,需要注意 message 是 bytes 型別,即返回的格式是二進位制資料。對於 Golang 底層對應的是一個 bytes 資料,對於其他語言可能是位元組流或二進位制。 ``` syntax = "proto3"; package helloworld; // The greeting service definition. service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) {} } // The request message containing the user's name. message HelloRequest { string name = 1; } // The response message containing the greetings message HelloReply { bytes message = 1; ``` 定義完成後 ,下一步就是使用 protoc 命令列工具生成程式碼。下圖左側是初始化的專案,你會發現,有一個單獨的目錄 protoc,存放了 hello.proto 這個檔案,這個檔案就是前面定義好的。 下圖右側是自動生成程式碼後的專案結構,生成了一個 pkg/helloworld 的包,裡面有一個 hello.pb.go,開啟這個檔案,你會發現剛才定義的 proto 已經被翻譯成了 Go 語言。具體 protoc 命令列工具如何使用,可以自行搜尋下,這裡不再過多展開。 ![](https://upload-images.jianshu.io/upload_images/80097-627f47bc6ddcbceb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 定義好了 proto 檔案,以及生成了相應的 package 程式碼,下一步我們就可以編寫業務邏輯了。 **Hello gRPC - 服務端業務程式碼** ``` import ( "google.golang.org/grpc" pb "grpc-hw/pkg/helloworld" ) // server is used to implement helloworld.GreeterServer. type server struct { pb.UnimplementedGreeterServer } // SayHello implements helloworld.GreeterServer func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { log.Printf("Received: %v", in.GetName()) tempFile := "srv.wav" err := exec.Command("flite", "-t", "Hello "+in.GetName(), "-o", tempFile).Run() if err != nil { return nil, fmt.Errorf("make audio failed: %v", err) } data, _ := ioutil.ReadFile(tempFile) return &pb.HelloReply{Message: data}, nil } func main() { lis, _ := net.Listen("tcp", port) s := grpc.NewServer() pb.RegisterGreeterServer(s, &server{}) s.Serve(lis) } ``` 在服務端側,需要實現 SayHello 的方法來滿足 GreeterServer 介面的要求。SayHello 方法的傳入引數,是在 proto 檔案中定義的 HelloRequest,傳出引數,是在 proto 檔案中定義的 HelloReply,以及一個 error。 業務邏輯也比較簡單,獲取 HelloRequest 中 Name 欄位,然後通過命令列行工具轉換成對應的音訊,將 bytes 陣列存在在 HelloReply 中返回。 **Hello gRPC - 客戶端業務程式碼** ``` func main() { flag.StringVar(&address, "addr", address, "server address") flag.StringVar(&name, "name", "world", "name") flag.Parse() // Set up a connection to the server. conn, _ := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock()) defer conn.Close() c := pb.NewGreeterClient(conn) // Contact the server and print out its response. ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name}) if err != nil { log.Fatalf("could not greet: %v", err) } tempFile := "cli.wav" ioutil.WriteFile(tempFile, r.Message, 0666) exec.Command("afplay", tempFile).Run() } ``` 我們再來看一下如何實現 Client。首先,建立一個 gRPC 的連線,並初始化 GreeterClient,然後直接呼叫下 GreeterClient 的 SayHello 的方法,將把返回結果另存為一個檔案,通過播放器播放就可以了。 總體而言,整個使用過程很簡單,並且非常容易上手,讓開發可以更加關注在客戶端、服務端業務的實現,不用操心傳輸層的事情。 **gRPC 的使用總結** 通過剛剛的小例子,我們來總結一下 gRPC 的使用: - 定義好介面文件 - 工具生成服務端/客戶端程式碼 - 服務端補充業務程式碼 - 客戶端建立 gRPC 連線後,使用自動生成的程式碼呼叫函式 - 編譯、執行 以上 5 步就是 gRPC 的簡單使用方法了。 ## gRPC 與 Protobuf 接下來我們來聊聊 gRPC 跟 Protobuf 之間的聯絡,當然在這之前我們需要先知道 Protobuf 是什麼。 **Protobuf** Protobuf 是一個語言無關、平臺無關的可擴充套件的結構化資料序列化方案。大家可能會覺得它跟 JSON 好像沒什麼區別,功能上看起來是一樣的,但像上文那樣去定義 SayHello 的操作,JSON 是做不到的,JSON 只能定義一個請求體或者一個返回體,沒法定義一個方法,但是 Protobuf 是可以的。 Protobuf 多用於協議通訊、資料儲存和其他更多用途。它是一個比較靈活的、高效的、自動化的結構化資料序列機制,但是更小,更快並且更簡單。一旦定義好資料如何構造, 就可以使用特殊生成的程式碼來輕易地讀寫結構化資料,無需關心用什麼語言來實現。你甚至可以更新資料結構而不打破已部署的使用"舊有"格式編譯的程式。 ![](https://upload-images.jianshu.io/upload_images/80097-d2cb0418750d399a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 上圖是 Protobuf 與 JSON 以及 JSON stream 三者之間的效能比較,可以明顯的看到在解碼的時候 Protobuf 比其他兩項快了不只一星半點。 ![Protobuf 與 XML、JSON 的吞吐量比較](https://upload-images.jianshu.io/upload_images/80097-bd362b6b36f90366.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 上圖中,我們可以看到 Protobuf 還是有一些缺點的,比如瀏覽器的支援沒有其他幾個支援的好。但是,在資料安全方面,由於傳輸過程中採用的是加密壓縮後的位元組流,一般無法直接檢視,安全性非常好。以及在處理速度方面,因為編解碼效率很高使得整體吞吐量有了顯著提升。還有一點,定義方法,這個是其他兩種序列化協議所做不到的。 **gRPC 跟 Protobuf 的聯絡** 雖然每次 gRPC 與 Protobuf 都是同時出現的,但是其實兩者之間並沒有很深的聯絡。只是因為兩者都是由 Google 開發的,和 gRPC 本身負載無關,在使用時也可以選擇 JSON 等等,但是考慮到 Protobuf 有定義方法的優勢,在微服務裡還是很推薦使用的。 ## gRPC vs Restful API ![](https://upload-images.jianshu.io/upload_images/80097-09e623f8f3a16ea2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 上圖是 gRPC 與 Restful API 的對比,平常我們可能更多使用 Restful API。但從圖上可以看到,因為 gRPC 用的是 Protobuf,本身就比較小所以很快,不像 JSON 體積比較大、比較慢。另外,gRPC 是載入 HTTP/2 上面的,延遲比較低,也因為 HTTP/2 支援連結複用,這就可以讓多個 stream 共用一個連線,從而進一步提升速度。對比 Restful 則使用的是 HTTP 1.1,延遲比較高,而且在一般情況下,每次請求都需要建一下新的連線。 gRPC 是雙向的。什麼是雙向呢?比如我們平常做 Restful,都是從客戶端到服務端,但是服務端沒辦法直接主動向客戶端傳送資訊,gRPC 則可以。gRPC 也支援流,Restful只支援Request/Response 這樣的機制。gRPC是面向 API 的,沒有限制,也面向增刪改查。gRPC 可以通過 Protobuf 直接生成程式碼,而 Restful 需要依賴第三方。 另外 gRPC 支援 RPC 可以呼叫伺服器上的一些方法,而 Restful 是基於HTTP的語法,很多東西需要自己去定義。這方面相信大家都有感觸,比如 REST 定義post/put/delete/get時,因為每個人都有自己的習慣,所以協作時需要溝通討論進行指定。但是 gRPC 就不需要了,它支援定義函式式辦法,不需要大家去考慮如何設計語法。 以上就是 gRPC 跟 Restful API 的對比。 ## 引入 gRPC 需要考慮哪些問題? 那麼當我們引入 gRPC 的時候需要考慮什麼呢?以下幾點肯定是不可避免的考慮項: - 是否可以滿足當前需求 - 效能如何 - 連線異常斷開後,是否需要客戶端重試 - TCP 連線是否可以複用 - 業務層改動是否足夠便利 - 業務後期迭代是否會出現問題,如何避免 這個也是我們引入一項新的東西時,往往需要考慮到的問題。 ## 回顧與總結 從選擇 gRPC 到整個專案落地,以及現在上線後正常使用。整個過程中,我對於專案的思考可以包括了過去、現在和未來三個階段。 對我而言,過去就是要去看我選擇的這個東西,用的人多不多,完善程度怎麼樣了?而現在則是要結合專案,看看合不合適,能不能使用。當然不能思考到能使用就結束,我們還需要考慮這個專案在未來的 3-5 年的發展,你引入它後在這個時間內需不需要大的變動。這個是非常重要的,雖然我們現在常說敏捷開發,也經常會進行很多的調整,但是在類似 gRPC 這種底層基礎來說,是固定的。 以上就是我今天的全部分享內容,講的比較簡單,希望能帶給大家一些收穫。 ![](https://upload-images.jianshu.io/upload_images/80097-1c68fbc95f43fc7a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) #### 推薦閱讀 [秋天的第一份“乾貨” I Referer 防盜鏈,為什麼少了個字母 R?](https://www.upyun.com/tech/article/584/%E7%A7%8B%E5%A4%A9%E7%9A%84%E7%AC%AC%E4%B8%80%E4%BB%BD%E2%80%9C%E5%B9%B2%E8%B4%A7%E2%80%9D%20I%20Referer%20%E9%98%B2%E7%9B%97%E9%93%BE%EF%BC%8C%E4%B8%BA%E4%BB%80%E4%B9%88%E5%B0%91%E4%BA%86%E4%B8%AA%E5%AD%97%E6%AF%8D%20R%EF%BC%9F.html) [“網頁內容無法訪問”可能是跨域錯誤!](https://www.upyun.com/tech/article/586/%E2%80%9C%E7%BD%91%E9%A1%B5%E5%86%85%E5%AE%B9%E6%97%A0%E6%B3%95%E8%AE%BF%E9%97%AE%E2%80%9D%E5%8F%AF%E8%83%BD%E6%98%AF%E8%B7%A8%E5%9F%9F%E9%94%99%E8%AF%AF%EF%BC%81.html)