總結一下用grpc
協議寫服務的若干心得,並記錄一些有用鏈接。
-
下載grpc依賴及安裝grpc代碼生成工具, 也可參見該鏈接創建簡單可行的
helloworld
服務。 -
golang
開發grpc
服務用"google.golang.org/grpc"
, 參考此例子, 及閱讀官方的tutorial可以應付大部分開發情況。 -
grpc
服務端及客戶端均需要保留protoc
生成的.pb.go
文件作爲交互依據。 -
.
proto
文件指定了grpc
服務端和客戶端交互的序列化規定,即指定了客戶端如何將請求內容序列化成字節, 服務端收到請求字節後也根據.proto
文件的內容反序列化成對象。 理解上跟在go
結構體屬性的json:"user_name"
類似,只是proto的序列化和反序列化效率更高。 -
使用
protoc
生成的.pb.go
文件也會生成json",,,"
和db:",,,"
等tag, 方便開發者在restful
服務和grpc
服務自由轉換和選擇, protoc本身不支持指定json:...
tag的命名, 只能通過輔助插件在生成.pb.go
時或事後修改.pb.go
的tag屬性。 -
若在原
restful web
服務中開發grpc
服務, 可將原指定數據交互的數據結構文件如model.go/types.go
與.pb.go
合爲一份。但這也會引起一系列問題。如生產的pb.go
中的結構體不支持embedded, 以及不能指定生成的pb.go
中的結構體去使用本身以經用golang
定義好的結構體。 -
Restful web
服務開發和grpc
服務交互也會引起很多序列化麻煩,儘量在開發前設計好序列化流程, 可將json,protobuf, db
等三者的序列化由同一個model定義, 省去互相轉換的麻煩。
type User struct{
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty" db:"name"`
}
-
protoc 有protoc 2 和protoc 3, 也有不少類型的支持, 可通過導入類型依賴包來使用更多的數據類型。
-
grpc可以實現連接池。在實踐中發現,單個
*grpc.ClientConn
支持多線程併發使用,瓶頸不在於*grpc.ClientConn
數量,參見及此文及其issue。grpc
連接池的建立會佔用較多資源, 即每個*grpc.ClientConn
均有若干goroutine
作爲watch
協程, 一旦連接池的量級較大時, 佔用的資源也可觀的。 -
grpc
瞬時流量大會引起grpc Resource Exhausted
, 可通過配置grpc服務端和客戶端來解決, 參見 -
grpc
不能用於前端瀏覽器與後端直接交互, 其grpc
的典型應用場景如下:
-
使用場景如
Kafka
接入使用,往往只需使用grpc
的序列化結構體, 而不使用grpc
的方法,以優化序列化和反序列化的性能。
proto.Unmarshal(......)
- 進行
grpc
方法調用需要注意內存泄露, 這裏引用源碼上的註釋作爲解釋:
// NewStream creates a new Stream for the client side. This is typically
// called by generated code. ctx is used for the lifetime of the stream.
//
// To ensure resources are not leaked due to the stream returned, one of the following
// actions must be performed:
//
// 1. Call Close on the ClientConn.
// 2. Cancel the context provided.
// 3. Call RecvMsg until a non-nil error is returned. A protobuf-generated
// client-streaming RPC, for instance, might use the helper function
// CloseAndRecv (note that CloseSend does not Recv, therefore is not
// guaranteed to release all resources).
// 4. Receive a non-nil, non-io.EOF error from Header or SendMsg.
//
// If none of the above happen, a goroutine and a context will be leaked, and grpc
// will not call the optionally-configured stats handler with a stats.End message.
func (cc *ClientConn) NewStream(ctx context.Context, desc *StreamDesc, method string, opts ...CallOption) (ClientStream, error) {...}
對於http handler
調用grpc
方法的情況, 爲防止內存泄露,可以爲各handler
增加利用context
控制超時的中間件
- 幾個
grpc
的典型應用舉例
- kubelet調用CRI
當kubelet
接收到apiserver
發送過來的創建容器的指令時,kubelet
會創建grpc連接
到CRI shim
的grpc server
, 最終grpc server
發送容器創建請求到本地的container runtime
(docker daemon
)。 流程詳見此. 另dockershim grpc server 源碼詳見.
14 gRPC stream
允許·grpc
·方法調用的時候建立長連接, 允許客戶端和服務端建立雙向連接,具體詳見。後臺服務通過stream
連接可實現更豐富的交互形式。如服務A基於etcd
建立元數據服務, 服務B可通過stream
達到訂閱watch
特定元數據的效果, 確保獲取最新的元數據改動。
15 後臺開發中涉及到較多的.proto
文件時, 可以建立獨立項目將.proto
文件統一保存, 通過git
管理。項目中也可以將.proto
文件構建成對應編程語言的文件, 如foo.pb.go
, 讓有需要的項目直接通過import
來導入。對於沒有上傳到github
的項目, 可以通過replace
方式來實現本地import
, 詳見。
16 後臺服務中, 若涉及到多個功能模塊的grpc
方法, 則建議每個功能模塊設置獨立的grpc server
,如kebelet的grpc server源碼, 若服務中有多個grpc server
則需要監聽不同的tcp port
,如kebelet實際上運行着多個grpc server。
17. 實踐中發現,數據結構越是複雜的情況, 越不適合將proto
生成的xxx.pb.go
中的結構體作爲通用的model
來提供給restful
和grpc
接口使用,這是由於生成的xxx.pb.go
有若干點侷限:
- 不支持
embeded
結構, 由此生成的struct
本身難以沿用embeded
屬性的特性, issue#192。 - 生成的
struct
若要實現某interface
,需要在xxx.pb.go
同package
下另起文件實現。 - 生成的
struct
的slice
定義只能爲[]*object
在大部分情況下,restful
和grpc
接口對model
部分的關注點會有差異, 因此實現restful
model
到grpc
model
的轉換會效率更高, 例子詳見
grpc
或rpc
的原理類似(區別見此), 大致爲開發者定義protobuf
的數據結構和方法接口, 編寫方法實現並實現該接口。服務啓動時rpc server
端將protobuf
指定的接口註冊到內存(map
結構), 然後監聽指定的socket
。client
端調用rpc
方法時,序列化調用參數, 向server
端指定端口發起tcp
請求及發送參數數據,server
端查詢內存找到client
端請求的方法對應的實現,運行該方法並反序列化,返回結果。 另外rpc
基於tcp/udp
,grpc
基於http
。