時間飛逝 如一名攜帶信息的郵差 但那隻不過是咱們的比喻 人物是杜撰的 匆忙是僞裝的 攜帶的也不是人的訊息
grpc
主要包括如下兩點緣由:html
protocl buffer
一種高效的序列化結構。http 2.0
標準化協議。很對人常常拿thrift
跟grpc
比較,如今先不發表任何見解,後續會深刻thrift
進行介紹。node
http/2
HTTP/2 enables a more efficient use of network resources and a reduced perception of latency by introducing header field compression and allowing multiple concurrent exchanges on the same connection… Specifically, it allows interleaving of request and response messages on the same connection and uses an efficient coding for HTTP header fields. It also allows prioritization of requests, letting more important requests complete more quickly, further improving performance.The resulting protocol is more friendly to the network, because fewer TCP connections can be used in comparison to HTTP/1.x. This means less competition with other flows, and longer-lived connections, which in turn leads to better utilization of available network capacity. Finally, HTTP/2 also enables more efficient processing of messages through use of binary message framing.git
http/2
帶來了網絡性能的巨大提高,下面列舉一些我的以爲比較重要的細節:github
http/2
對每一個源只需建立一個持久鏈接,在這一個鏈接內,能夠並行的處理多個請求和響應,並且作到不相互影響。更多細節,請參考文章末尾的連接,固然,後續也會專門介紹。golang
準備工做
你們能夠參考protobuf
的介紹,具體包括:web
Go
的開發環境,由於後續是基於Go
語言的開發項目protocol-buffers
protoc-gen-go
,用於自動生成源碼生成源碼的命令以下,其中,--go_out
用於指定生成源碼的保存路徑;而-I
是-IPATH
的簡寫,用於指定查找import
文件的路徑,能夠指定多個;最後的order
是編譯的grpc
文件的存儲路徑。apache
protoc -I proto/ proto/order.proto --go_out=plugins=grpc:order
protocol buffer
google
開發的高效、跨平臺的數據傳輸格式。固然,本質仍是數據傳輸結構。但google
賦予了它豐富的功能,好比import
、package
、消息嵌套等等。import
用於引入別的.proto
文件;package
用於定義命名空間,轉換到go
源碼中就是包名;repeated
用於定義重複的數據;enum
用於定義枚舉類型等。bash
.proto
內字段的基本定義:服務器
type name = tag;
Protocol buffer
自己不包含類型的描述信息,所以獲取了沒有.proto
描述文件的二進制信息是毫無用處的,咱們很難提取出很是有用的信息。Go
語言complier
生成的文件後綴是.pb.go
,它自動生成了set
、get
以及read
、write
方法,咱們能夠很方便的序列化數據。restful
下面咱們定義一個建立訂單的.proto
文件,歸納的描述:buyerID
在device
上支付amount
買sku
商品。
proto3
,package
是order
。ANDROID
和IOS
兩種,並且類型被嵌套聲明在OrderParams
內。sku
聲明爲repeated
,由於用戶可能購買多個商品。OrderResult
爲響應的消息體結構,包括生成的訂單號和處理的響應碼。service
聲明瞭order
要提供的服務。當前僅僅實現一個simple RPC
:客戶端使用OrderParams
參數請求RPC
服務器,收到OrderResult
做爲響應。syntax = "proto3"; package order; service Order { //a simple RPC //create new order rpc Add (OrderParams) returns (OrderResult) { } } message OrderParams { string amount = 1; //訂單金額 int64 buyerID = 2; //購買用戶ID enum Device { IOS = 0; ANDROID = 1; } Device device = 3; repeated Sku sku = 4; } message Sku { int32 num = 1; string skuId = 2; int32 unitPrice = 3; } message OrderResult { int32 statusCode = 1; string orderID = 2; }
grpc
接口經過定義的.proto
文件生成grpc client
和server
端實現的接口類型。生成的內容主要包括:
protocol buffer
各類消息類型的序列化操做grpc client
實現的接口類型,以及client
實現的grpc
方法grpc server
待實現的接口類型service
處理流程第一步. 服務端爲每一個接收的鏈接建立單獨的goroutine
進行處理。
第二步. 自動生成的代碼中,聲明瞭服務的具體描述,也是該服務的「路由」。包括服務名稱ServiceName
以Methods
、Streams
。當rpc
接收到新的數據時,會根據路由執行對應的方法。由於咱們的設定沒有處理流的場景,因此Streams
爲空的結構體。
代碼中的服務名稱被指定爲:order.Order
,對應建立訂單的方法是:Add
。
var _Order_serviceDesc = grpc.ServiceDesc{ ServiceName: "order.Order", HandlerType: (*OrderServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "Add", Handler: _Order_Add_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "order.proto", }
第三步. 將路由註冊到rpc
服務中。以下所示,就是將上述的路由轉換爲map
對應關係的過程。類比restful
風格的接口定義,等價於/order/
這種請求都由這個service
來進行處理。
最終將service
註冊到gRPC server
上。同時,咱們能夠逆向猜出服務的處理過程:經過請求的路徑獲取service
,而後經過MethodName
調用相應的處理方法。
srv := &service{ server: ss, md: make(map[string]*MethodDesc), sd: make(map[string]*StreamDesc), mdata: sd.Metadata, } for i := range sd.Methods { d := &sd.Methods[i] srv.md[d.MethodName] = d } for i := range sd.Streams { d := &sd.Streams[i] srv.sd[d.StreamName] = d } s.m[sd.ServiceName] = srv
第四步. gRPC
服務處理請求。經過請求的:path
,獲取對應的service
和MethodName
進行處理。
service := sm[:pos] method := sm[pos+1:] if srv, ok := s.m[service]; ok { if md, ok := srv.md[method]; ok { s.processUnaryRPC(t, stream, srv, md, trInfo) return } if sd, ok := srv.sd[method]; ok { s.processStreamingRPC(t, stream, srv, sd, trInfo) return } }
經過結合protoc
自動生成的client
端代碼,無需抓包,咱們就能夠推斷出path
的格式,以及系統是如何處理路由的。代碼中定義的:/order.Order/Add
就是依據。
func (c *orderClient) Add(ctx context.Context, in *OrderParams, opts ...grpc.CallOption) (*OrderResult, error) { out := new(OrderResult) err := c.cc.Invoke(ctx, "/order.Order/Add", in, out, opts...) if err != nil { return nil, err } return out, nil }
建立訂單
爲了簡單起見,咱們只保證訂單的惟一性。這裏咱們實現一個簡易版本,並且也不作過多介紹。感興趣的同窗能夠移步到另外一篇文章:探討分佈式ID生成系統去了解,畢竟不該該是本節的重心。
//上次建立訂單使用的毫秒時間 var lastTimestamp = time.Now().UnixNano() / 1000000 var sequence int64 const MaxSequence = 4096 // 42bit分配給毫秒時間戳 // 12bit分配給序列號,每4096就從新開始循環 // 10bit分配給機器ID func CreateOrder(nodeId int64) string { currentTimestamp := getCurrentTimestamp() if currentTimestamp == lastTimestamp { sequence = (sequence + 1) % MaxSequence if sequence == 0 { currentTimestamp = waitNextMillis(currentTimestamp) } } else { sequence = 0 } orderId := currentTimestamp << 22 orderId |= nodeId << 10 orderId |= sequence return strings.ToUpper(fmt.Sprintf("%x", orderId)) } func getCurrentTimestamp() int64 { return time.Now().UnixNano() / 1000000 } func waitNextMillis(currentTimestamp int64) int64 { for currentTimestamp == lastTimestamp { currentTimestamp = getCurrentTimestamp() } return currentTimestamp }
運行系統
建立服務端代碼。注意:使用grpc
提供的默認選項,實際上是很危險的行爲。在生產開發中,被不熟悉的默認選項坑到的狀況比比皆是。這裏的代碼不要做爲後續生產環境開發的參考。服務端的代碼相比客戶端要複雜一點,須要咱們去實現處理請求的接口。
type Order struct { } func (o *Order) Add(ctx context.Context, in *order.OrderParams) (*order.OrderResult, error) { return &order.OrderResult{ OrderID: util.CreateOrder(1), }, nil } func main() { lis, err := net.Listen("tcp", "127.0.0.1:10000") if err != nil { log.Fatalf("Failed to listen: %v", err) } grpcServer := grpc.NewServer() order.RegisterOrderServer(grpcServer, &Order{}) grpcServer.Serve(lis) }
客戶端的代碼很是簡單,構造參數,處理返回就Ok
了。
func createOrder(client order.OrderClient, params *order.OrderParams) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() orderResult, err := client.Add(ctx, params) if err != nil { log.Fatalf("%v.GetFeatures(_) = _, %v: ", client, err) } log.Println(orderResult) } func main() { conn, err := grpc.Dial("127.0.0.1:10000") if err != nil { log.Fatalf("fail to dial: %v", err) } defer conn.Close() client := order.NewOrderClient(conn) orderParams := &order.OrderParams{ BuyerID: 10318003, } createOrder(client, orderParams) }
文章介紹了gRPC
的入門知識,包括protocol buffer
以及http/2
,gRPC
封裝了不少東西,對於通常場合,咱們只須要指定配置,實現接口就能夠了,很是簡單。
在入門的介紹裏,你們會以爲gRPC
不就跟RESTFUL
請求同樣嗎?確實是,我也這樣以爲。但存在一個最直觀的優勢:經過使用gRPC
,能夠將複雜的接口調用關係封裝在SDK
中,直接提供給第三方使用,並且還能有效避免錯誤調用接口的狀況。
若是gRPC
只能這樣的話,它就太失敗了,他用HTTP/2
簡直就是用來打蚊子的,讓咱們後續繼續深刻了解吧。
參考文章: