gRPC是什么?
gRPC是什么可以用官網的一句話來概括
A high-performance, open-source universal RPC framework
所謂RPC(remote procedure call 遠程過程調用)框架實際是提供了一套機制,使得應用程序之間可以進行通信,而且也遵從server/client模型。使用的時候客戶端調用server端提供的接口就像是調用本地的函數一樣。如下圖所示就是一個典型的RPC結構圖。

gRPC有什么好處以及在什么場景下需要用gRPC
既然是server/client模型,那么我們直接用restful api不是也可以滿足嗎,為什么還需要RPC呢?下面我們就來看看RPC到底有哪些優勢
gRPC vs. Restful API
gRPC和restful API都提供了一套通信機制,用於server/client模型通信,而且它們都使用http作為底層的傳輸協議(嚴格地說, gRPC使用的http2.0,而restful api則不一定)。不過gRPC還是有些特有的優勢,如下:
- gRPC可以通過protobuf來定義接口,從而可以有更加嚴格的接口約束條件。關於protobuf可以參見筆者之前的小文Google Protobuf簡明教程
- 另外,通過protobuf可以將數據序列化為二進制編碼,這會大幅減少需要傳輸的數據量,從而大幅提高性能。
- gRPC可以方便地支持流式通信(理論上通過http2.0就可以使用streaming模式, 但是通常web服務的restful api似乎很少這么用,通常的流式數據應用如視頻流,一般都會使用專門的協議如HLS,RTMP等,這些就不是我們通常web服務了,而是有專門的服務器應用。)
使用場景
- 需要對接口進行嚴格約束的情況,比如我們提供了一個公共的服務,很多人,甚至公司外部的人也可以訪問這個服務,這時對於接口我們希望有更加嚴格的約束,我們不希望客戶端給我們傳遞任意的數據,尤其是考慮到安全性的因素,我們通常需要對接口進行更加嚴格的約束。這時gRPC就可以通過protobuf來提供嚴格的接口約束。
- 對於性能有更高的要求時。有時我們的服務需要傳遞大量的數據,而又希望不影響我們的性能,這個時候也可以考慮gRPC服務,因為通過protobuf我們可以將數據壓縮編碼轉化為二進制格式,通常傳遞的數據量要小得多,而且通過http2我們可以實現異步的請求,從而大大提高了通信效率。
但是,通常我們不會去單獨使用gRPC,而是將gRPC作為一個部件進行使用,這是因為在生產環境,我們面對大並發的情況下,需要使用分布式系統來去處理,而gRPC並沒有提供分布式系統相關的一些必要組件。而且,真正的線上服務還需要提供包括負載均衡,限流熔斷,監控報警,服務注冊和發現等等必要的組件。不過,這就不屬於本篇文章討論的主題了,我們還是先繼續看下如何使用gRPC。
gRPC HelloWorld實例詳解
gRPC的使用通常包括如下幾個步驟:
- 通過protobuf來定義接口和數據類型
- 編寫gRPC server端代碼
-
編寫gRPC client端代碼
下面來通過一個實例來詳細講解上述的三步。
下邊的hello world實例完成之后,其目錄結果如下:
project helloworld
定義接口和數據類型
- 通過protobuf定義接口和數據類型
syntax = "proto3"; package rpc_package; // define a service service HelloWorldService { // define the interface and data type rpc SayHello (HelloRequest) returns (HelloReply) {} } // define the data type of request message HelloRequest { string name = 1; } // define the data type of response message HelloReply { string message = 1; }
- 使用gRPC protobuf生成工具生成對應語言的庫函數
python -m grpc_tools.protoc -I=./protos --python_out=./rpc_package --grpc_python_out=./rpc_package ./protos/user_info.proto
這個指令會自動生成rpc_package文件夾中的helloworld_pb2.py
和helloworld_pb2_grpc.py
,但是不會自動生成__init__.py
文件,需要我們手動添加
關於protobuf的詳細解釋請參考Google Protobuf簡明教程
gRPC server端代碼
#!/usr/bin/env python # -*-coding: utf-8 -*- from concurrent import futures import grpc import logging import time from rpc_package.helloworld_pb2_grpc import add_HelloWorldServiceServicer_to_server, \ HelloWorldServiceServicer from rpc_package.helloworld_pb2 import HelloRequest, HelloReply class Hello(HelloWorldServiceServicer): # 這里實現我們定義的接口 def SayHello(self, request, context): return HelloReply(message='Hello, %s!' % request.name) def serve(): # 這里通過thread pool來並發處理server的任務 server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) # 將對應的任務處理函數添加到rpc server中 add_HelloWorldServiceServicer_to_server(Hello(), server) # 這里使用的非安全接口,世界gRPC支持TLS/SSL安全連接,以及各種鑒權機制 server.add_insecure_port('[::]:50000') server.start() try: while True: time.sleep(60 * 60 * 24) except KeyboardInterrupt: server.stop(0) if __name__ == "__main__": logging.basicConfig() serve()
gRPC client端代碼
#!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import print_function import logging import grpc from rpc_package.helloworld_pb2 import HelloRequest, HelloReply from rpc_package.helloworld_pb2_grpc import HelloWorldServiceStub def run(): # 使用with語法保證channel自動close with grpc.insecure_channel('localhost:50000') as channel: # 客戶端通過stub來實現rpc通信 stub = HelloWorldServiceStub(channel) # 客戶端必須使用定義好的類型,這里是HelloRequest類型 response = stub.SayHello(HelloRequest(name='eric')) print ("hello client received: " + response.message) if __name__ == "__main__": logging.basicConfig() run()
演示
先執行server端代碼
python hello_server.py
接着執行client端代碼如下:
➜ grpc_test python hello_client.py
hello client received: Hello, eric!
References
gRPC 是一個高性能、通用的開源RPC框架,基於HTTP/2協議標准和Protobuf序列化協議開發,支持眾多的開發語言。
概述
在gRPC框架中,客戶端可以像調用本地對象一樣直接調用位於不同機器的服務端方法,如此我們就可以非常方便的創建一些分布式的應用服務。
在服務端,我們實現了所定義的服務和可供遠程調用的方法,運行一個gRPC server來處理客戶端的請求;在客戶端,gRPC實現了一個stub(可以簡單理解為一個client),其提供跟服務端相同的方法。

gRPC使用protocol buffers作為接口描述語言(IDL)以及底層的信息交換格式,一般情況下推薦使用 proto3因為其能夠支持更多的語言,並減少一些兼容性的問題。
特性
基於HTTP/2
HTTP/2 提供了連接多路復用、雙向流、服務器推送、請求優先級、首部壓縮等機制。可以節省帶寬、降低TCP鏈接次數、節省CPU,幫助移動設備延長電池壽命等。gRPC 的協議設計上使用了HTTP2 現有的語義,請求和響應的數據使用HTTP Body 發送,其他的控制信息則用Header 表示。
IDL使用ProtoBuf
gRPC使用ProtoBuf來定義服務,ProtoBuf是由Google開發的一種數據序列化協議(類似於XML、JSON、hessian)。ProtoBuf能夠將數據進行序列化,並廣泛應用在數據存儲、通信協議等方面。壓縮和傳輸效率高,語法簡單,表達力強。
多語言支持(C, C++, Python, PHP, Nodejs, C#, Objective-C、Golang、Java)
gRPC支持多種語言,並能夠基於語言自動生成客戶端和服務端功能庫。目前已提供了C版本grpc、Java版本grpc-java 和 Go版本grpc-go,其它語言的版本正在積極開發中,其中,grpc支持C、C++、Node.js、Python、Ruby、Objective-C、PHP和C#等語言,grpc-java已經支持Android開發。
gRPC已經應用在Google的雲服務和對外提供的API中,其主要應用場景如下:
- 低延遲、高擴展性、分布式的系統
- 同雲服務器進行通信的移動應用客戶端
- 設計語言獨立、高效、精確的新協議
- 便於各方面擴展的分層設計,如認證、負載均衡、日志記錄、監控等
grpc優缺點:
優點:
- 1、protobuf二進制消息,性能好/效率高(空間和時間效率都很不錯)
- 2、proto文件生成目標代碼,簡單易用
- 3、序列化反序列化直接對應程序中的數據類,不需要解析后在進行映射(XML,JSON都是這種方式)
- 4、支持向前兼容(新加字段采用默認值)和向后兼容(忽略新加字段),簡化升級
- 5、支持多種語言(可以把proto文件看做IDL文件)
- 6、Netty等一些框架集成
缺點:
- 1、GRPC尚未提供連接池,需要自行實現
- 2、尚未提供“服務發現”、“負載均衡”機制
- 3、因為基於HTTP2,絕大部多數HTTP Server、Nginx都尚不支持,即Nginx不能將GRPC請求作為HTTP請求來負載均衡,而是作為普通的TCP請求。(nginx1.9版本已支持)
- 4、Protobuf二進制可讀性差(貌似提供了Text_Fromat功能)
- 5、默認不具備動態特性(可以通過動態定義生成消息類型或者動態編譯支持)
gRPC有四種通信方式:
- 1、 Simple RPC
簡單rpc
這就是一般的rpc調用,一個請求對象對應一個返回對象
proto語法:
rpc simpleHello(Person) returns (Result) {}
- 2、 Server-side streaming RPC
服務端流式rpc
一個請求對象,服務端可以傳回多個結果對象
proto語法
rpc serverStreamHello(Person) returns (stream Result) {}
- 3、 Client-side streaming RPC
客戶端流式rpc
客戶端傳入多個請求對象,服務端返回一個響應結果
proto語法
rpc clientStreamHello(stream Person) returns (Result) {}
- 4、 Bidirectional streaming RPC
雙向流式rpc
結合客戶端流式rpc和服務端流式rpc,可以傳入多個對象,返回多個響應對象
proto語法
rpc biStreamHello(stream Person) returns (stream Result) {}
服務定義及ProtoBuf
gRPC使用ProtoBuf定義服務, 我們可以一次性的在一個 .proto 文件中定義服務並使用任何支持它的語言去實現客戶端和服務器,反過來,它們可以在各種環境中,從雲服務器到你自己的平板電腦—— gRPC 幫你解決了不同語言及環境間通信的復雜性。使用 protocol buffers 還能獲得其他好處,包括高效的序列號,簡單的 IDL 以及容易進行接口更新。
protoc編譯工具
protoc工具可在https://github.com/google/protobuf/releases 下載到源碼。 且將protoc的bin目錄配置到環境變量中,如下圖:

實際開發中一般都通過在idea上配置com.google.protobuf
插件進行開發,這一點在https://github.com/grpc/grpc-java上的文檔有詳細說明,如果使用gradle進行項目構建的話,https://github.com/google/protobuf-gradle-plugin上有protobuf-gradle-plugin插件的詳細使用說明。
protobuf語法
-
1、syntax = “proto3”;
文件的第一行指定了你使用的是proto3的語法:如果你不指定,protocol buffer 編譯器就會認為你使用的是proto2的語法。這個語句必須出現在.proto文件的非空非注釋的第一行。 -
2、message SearchRequest {……}
message 定義實體,c/c++/go中的結構體,php中類 -
3、基本數據類型
image.png -
4、注釋符號: 雙斜線,如://xxxxxxxxxxxxxxxxxxx
-
5、字段唯一數字標識(用於在二進制格式中識別各個字段,上線后不宜再變動):Tags
1到15使用一個字節來編碼,包括標識數字和字段類型(你可以在Protocol Buffer 編碼中查看更多詳細);16到2047占用兩個字節。因此定義proto文件時應該保留1到15,用作出現最頻繁的消息類型的標識。記得為將來會繼續增加並可能頻繁出現的元素留一點兒標識區間,也就是說,不要一下子把1—15全部用完,為將來留一點兒。
標識數字的合法范圍:最小是1,最大是 229 - 1,或者536,870,911。
另外,不能使用19000 到 19999之間的數字(FieldDescriptor::kFirstReservedNumber through FieldDescriptor::kLastReservedNumber),因為它們被Protocol Buffers保留使用 -
6、字段修飾符:
required:值不可為空
optional:可選字段
singular:符合語法規則的消息包含零個或者一個這樣的字段(最多一個)
repeated:一個字段在合法的消息中可以重復出現一定次數(包括零次)。重復出現的值的次序將被保留。在proto3中,重復出現的值類型字段默認采用壓縮編碼。你可以在這里找到更多關於壓縮編碼的東西: Protocol Buffer Encoding。
默認值: optional PhoneType type = 2 [default = HOME];
proto3中,省略required,optional,singular,由protoc自動選擇。 -
7、代理類生成
1)、C++, 每一個.proto 文件可以生成一個 .h 文件和一個 .cc 文件
2)、Java, 每一個.proto文件可以生成一個 .java 文件
3)、Python, 每一個.proto文件生成一個模塊,其中為每一個消息類型生成一個靜態的描述器,在運行時,和一個metaclass一起使用來創建必要的Python數據訪問類
4)、Go, 每一個.proto生成一個 .pb.go 文件
5)、Ruby, 每一個.proto生成一個 .rb 文件
6)、Objective-C, 每一個.proto 文件可以生成一個 pbobjc.h 和一個pbobjc.m 文件
7)、C#, 每一個.proto文件可以生成一個.cs文件.
8)、php, 每一個message消息體生成一個.php類文件,並在GPBMetadata目錄生成一個對應包名的.php類文件,用於保存.proto的二進制元數據。 -
8、字段默認值
- strings, 默認值是空字符串(empty string)
- bytes, 默認值是空bytes(empty bytes)
- bools, 默認值是false
- numeric, 默認值是0
- enums, 默認值是第一個枚舉值(value必須為0)
- message fields, the field is not set. Its exact value is langauge-dependent. See the generated code guide for details.
- repeated fields,默認值為empty,通常是一個空list
- 9、枚舉
// 枚舉類型,必須從0開始,序號可跨越。同一包下不能重名,所以加前綴來區別 enum WshExportInstStatus { INST_INITED = 0; INST_RUNNING = 1; INST_FINISH = 2; INST_FAILED = 3; }
- 10、Maps字段類型
map<key_type, value_type> map_field = N;
其中key_type可以是任意Integer或者string類型(所以,除了floating和bytes的任意標量類型都是可以的)value_type可以是任意類型。
例如,如果你希望創建一個project的映射,每個Projecct使用一個string作為key,你可以像下面這樣定義:
map<string, Project> projects = 3;
Map的字段可以是repeated。
序列化后的順序和map迭代器的順序是不確定的,所以你不要期望以固定順序處理Map
當為.proto文件產生生成文本格式的時候,map會按照key 的順序排序,數值化的key會按照數值排序。
從序列化中解析或者融合時,如果有重復的key則后一個key不會被使用,當從文本格式中解析map時,如果存在重復的key。
-
11、默認值
字符串類型默認為空字符串
字節類型默認為空字節
布爾類型默認false
數值類型默認為0值
enums類型默認為第一個定義的枚舉值,必須是0 -
12、服務
服務使用service{}包起來,每個方法使用rpc起一行申明,一個方法包含一個請求消息體和一個返回消息體
service HelloService { rpc SayHello (HelloRequest) returns (HelloResponse); } message HelloRequest { string greeting = 1; } message HelloResponse { string reply = 1; }
更多protobuf參考(google)
更多protobuf參考(csdn)
對於開發者而言:
-
1、需要使用protobuf定義接口,即.proto文件
-
2、然后使用compile工具生成特定語言的執行代碼,比如JAVA、C/C++、Python等。類似於thrift,為了解決跨語言問題。
-
3、啟動一個Server端,server端通過偵聽指定的port,來等待Client鏈接請求,通常使用Netty來構建,GRPC內置了Netty的支持。
-
4、啟動一個或者多個Client端,Client也是基於Netty,Client通過與Server建立TCP長鏈接,並發送請求;Request與Response均被封裝成HTTP2的stream Frame,通過Netty Channel進行交互。