<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          5分鐘學(xué)會 gRPC

          共 2181字,需瀏覽 5分鐘

           ·

          2022-03-08 14:57

          adfd6863e3b9e20a884b514d0d9af6ba.webp介紹

          我猜測大部分長期使用 Java 的開發(fā)者應(yīng)該較少會接觸 gRPC,畢竟在 Java 圈子里大部分使用的還是 Dubbo/SpringClound 這兩類服務(wù)框架。

          我也是近段時間有機(jī)會從零開始重構(gòu)業(yè)務(wù)才接觸到 gRPC 的,當(dāng)時選擇 gRPC 時也有幾個原因:

          d1556061d712d7a5f89fb472ce63d03e.webp
          • 基于云原生的思路開發(fā)部署項目,而在云原生中 gRPC 幾乎已經(jīng)是標(biāo)準(zhǔn)的通訊協(xié)議了。
          • 開發(fā)語言選擇了 Go,在 Go 圈子中 gRPC 顯然是更好的選擇。
          • 公司內(nèi)部有部分業(yè)務(wù)使用的是 Python 開發(fā),在多語言兼容性上 gRPC 支持的非常好。

          經(jīng)過線上一年多的平穩(wěn)運行,可以看出 gRPC 還是非常穩(wěn)定高效的;rpc 框架中最核心的幾個要點:

          • 序列化
          • 通信協(xié)議
          • IDL(接口描述語言)

          這些在 gRPC 中分別對應(yīng)的是:

          • 基于 Protocol Buffer 序列化協(xié)議,性能高效。
          • 基于 HTTP/2 標(biāo)準(zhǔn)協(xié)議開發(fā),自帶 stream、多路復(fù)用等特性;同時由于是標(biāo)準(zhǔn)協(xié)議,第三方工具的兼容性會更好(比如負(fù)載均衡、監(jiān)控等)
          • 編寫一份 .proto 接口文件,便可生成常用語言代碼。
          HTTP/2

          學(xué)習(xí) gRPC 之前首先得知道它是通過什么協(xié)議通信的,我們?nèi)粘2还苁情_發(fā)還是應(yīng)用基本上接觸到最多的還是 HTTP/1.1 協(xié)議。

          eb172bc2309d2b8f4de99c1fde40ab31.webp

          由于 HTTP/1.1 是一個文本協(xié)議,對人類非常友好,相反的對機(jī)器性能就比較低。

          需要反復(fù)對文本進(jìn)行解析,效率自然就低了;要對機(jī)器更友好就得采用二進(jìn)制,HTTP/2 自然做到了。

          除此之外還有其他優(yōu)點:

          • 多路復(fù)用:可以并行的收發(fā)消息,互不影響
          • HPACK 節(jié)省 header 空間,避免 HTTP1.1 對相同的 header 反復(fù)發(fā)送。
          Protocol

          gRPC 采用的是 Protocol 序列化,發(fā)布時間比 gRPC 早一些,所以也不僅只用于 gRPC,任何需要序列化 IO 操作的場景都可以使用它。

          它會更加的省空間、高性能;之前在開發(fā) https://github.com/crossoverJie/cim 時就使用它來做數(shù)據(jù)交互。

          package order.v1;

          service OrderService{

          rpc Create(OrderApiCreate) returns (Order) {}

          rpc Close(CloseApiCreate) returns (Order) {}

          // 服務(wù)端推送
          rpc ServerStream(OrderApiCreate) returns (stream Order) {}

          // 客戶端推送
          rpc ClientStream(stream OrderApiCreate) returns (Order) {}

          // 雙向推送
          rpc BdStream(stream OrderApiCreate) returns (stream Order) {}
          }

          message OrderApiCreate{
          int64 order_id = 1;
          repeated int64 user_id = 2;
          string remark = 3;
          repeated int32 reason_id = 4;
          }

          使用起來也是非常簡單的,只需要定義自己的 .proto 文件,便可用命令行工具生成對應(yīng)語言的 SDK。

          具體可以參考官方文檔:https://grpc.io/docs/languages/go/generated-code/

          調(diào)用
          ?protoc?--go_out=.?--go_opt=paths=source_relative?\
          ????--go-grpc_out=.?--go-grpc_opt=paths=source_relative?\
          ????test.proto

          2fecf7c6c488fa2152de8bd023b80da6.webp生成代碼之后編寫服務(wù)端就非常簡單了,只需要實現(xiàn)生成的接口即可。

          func?(o?*Order)?Create(ctx?context.Context,?in?*v1.OrderApiCreate)?(*v1.Order,?error)?{
          ?//?獲取?metadata
          ?md,?ok?:=?metadata.FromIncomingContext(ctx)
          ?if?!ok?{
          ??return?nil,?status.Errorf(codes.DataLoss,?"failed?to?get?metadata")
          ?}
          ?fmt.Println(md)
          ?fmt.Println(in.OrderId)
          ?return?&v1.Order{
          ??OrderId:?in.OrderId,
          ??Reason:??nil,
          ?},?nil
          }
          c478b4b2dee4102b6072620a0020e20e.webp

          客戶端也非常簡單,只需要依賴服務(wù)端代碼,創(chuàng)建一個 connection 然后就和調(diào)用本地方法一樣了。

          這是經(jīng)典的 unary(一元)調(diào)用,類似于 http 的請求響應(yīng)模式,一個請求對應(yīng)一次響應(yīng)。

          7ce6da143ae286eecf0eb09467a3cc01.webp

          Server stream

          gRPC 除了常規(guī)的 unary 調(diào)用之外還支持服務(wù)端推送,在一些特定場景下還是很有用的。

          69482fc58631d096b4e065ab1794cbd9.webp
          func?(o?*Order)?ServerStream(in?*v1.OrderApiCreate,?rs?v1.OrderService_ServerStreamServer)?error?{
          ?for?i?:=?0;?i?5;?i++?{
          ??rs.Send(&v1.Order{
          ???OrderId:?in.OrderId,
          ???Reason:??nil,
          ??})
          ?}
          ?return?nil
          }

          服務(wù)端的推送如上所示,調(diào)用 Send 函數(shù)便可向客戶端推送。

          ?for?{
          ??msg,?err?:=?rpc.RecvMsg()
          ??if?err?==?io.EOF?{
          ???marshalIndent,?_?:=?json.MarshalIndent(msgs,?"",?"\t")
          ???fmt.Println(msg)
          ???return
          ??}
          ?}

          客戶端則通過一個循環(huán)判斷當(dāng)前接收到的數(shù)據(jù)包是否已經(jīng)截止來獲取服務(wù)端消息。

          為了能更直觀的展示這個過程,優(yōu)化了之前開發(fā)的一個 gRPC 客戶端,可以直觀的調(diào)試 stream 調(diào)用。

          a9f2adb13902ac49edc246ad93992ce9.webp

          上圖便是一個服務(wù)端推送示例。

          Client Stream

          18c005196820214e533d43e8eba2b9a4.webp

          除了支持服務(wù)端推送之外,客戶端也支持。

          客戶端在同一個連接中一直向服務(wù)端發(fā)送數(shù)據(jù),服務(wù)端可以并行處理消息。


          //?服務(wù)端代碼
          func?(o?*Order)?ClientStream(rs?v1.OrderService_ClientStreamServer)?error?{
          ?var?value?[]int64
          ?for?{
          ??recv,?err?:=?rs.Recv()
          ??if?err?==?io.EOF?{
          ???rs.SendAndClose(&v1.Order{
          ????OrderId:?100,
          ????Reason:??nil,
          ???})
          ???log.Println(value)
          ???return?nil
          ??}
          ??value?=?append(value,?recv.OrderId)
          ??log.Printf("ClientStream?receiv?msg?%v",?recv.OrderId)
          ?}
          ?log.Println("ClientStream?finish")
          ?return?nil
          }

          ?//?客戶端代碼
          ?for?i?:=?0;?i?5;?i++?{
          ??messages,?_?:=?GetMsg(data)
          ??rpc.SendMsg(messages[0])
          ?}
          ?receive,?err?:=?rpc.CloseAndReceive()

          代碼與服務(wù)端推送類似,只是角色互換了。

          786b610afc55b23831b14b5bfa499fe0.webp

          Bidirectional Stream

          ddb1f6c142215fcea25ac1a94b90da4b.webp

          同理,當(dāng)客戶端、服務(wù)端同時都在發(fā)送消息也是支持的。

          //?服務(wù)端
          func?(o?*Order)?BdStream(rs?v1.OrderService_BdStreamServer)?error?{
          ?var?value?[]int64
          ?for?{
          ??recv,?err?:=?rs.Recv()
          ??if?err?==?io.EOF?{
          ???log.Println(value)
          ???return?nil
          ??}
          ??if?err?!=?nil?{
          ???panic(err)
          ??}
          ??value?=?append(value,?recv.OrderId)
          ??log.Printf("BdStream?receiv?msg?%v",?recv.OrderId)
          ??rs.SendMsg(&v1.Order{
          ???OrderId:?recv.OrderId,
          ???Reason:??nil,
          ??})
          ?}
          ?return?nil
          }
          //?客戶端
          ?for?i?:=?0;?i?5;?i++?{
          ??messages,?_?:=?GetMsg(data)
          ??//?發(fā)送消息
          ??rpc.SendMsg(messages[0])
          ??//?接收消息
          ??receive,?_?:=?rpc.RecvMsg()
          ??marshalIndent,?_?:=?json.MarshalIndent(receive,?"",?"\t")
          ??fmt.Println(string(marshalIndent))
          ?}
          ?rpc.CloseSend()

          其實就是將上訴兩則合二為一。

          b540358510ae0feb11ca47ea62a1b320.webp

          通過調(diào)用示例很容易理解。

          元數(shù)據(jù)

          gRPC 也支持元數(shù)據(jù)傳輸,類似于 HTTP 中的 header

          ?//?客戶端寫入
          ?metaStr?:=?`{"lang":"zh"}`
          ?var?m?map[string]string
          ?err?:=?json.Unmarshal([]byte(metaStr),?&m)
          ?md?:=?metadata.New(m)
          ?//?調(diào)用時將?ctx?傳入即可
          ?ctx?:=?metadata.NewOutgoingContext(context.Background(),?md)
          ?
          ?//?服務(wù)端接收
          ?md,?ok?:=?metadata.FromIncomingContext(ctx)
          ?if?!ok?{
          ??return?nil,?status.Errorf(codes.DataLoss,?"failed?to?get?metadata")
          ?}
          ?fmt.Println(md)?

          gRPC gateway

          gRPC 雖然功能強(qiáng)大使用也很簡單,但對于瀏覽器、APP的支持還是不如 REST 應(yīng)用廣泛(瀏覽器也支持,但應(yīng)用非常少)。

          為此社區(qū)便創(chuàng)建了 https://github.com/grpc-ecosystem/grpc-gateway 項目,可以將 gRPC 服務(wù)暴露為 RESTFUL API。

          284f8d2653173ab0e2b8be5367e75655.webp

          為了讓測試可以習(xí)慣用 postman 進(jìn)行接口測試,我們也將 gRPC 服務(wù)代理出去,更方便的進(jìn)行測試。

          反射調(diào)用

          作為一個 rpc 框架,泛化調(diào)用也是必須支持的,可以方便開發(fā)配套工具;gRPC 是通過反射支持的,通過拿到服務(wù)名稱、pb 文件進(jìn)行反射調(diào)用。

          https://github.com/jhump/protoreflect 這個庫封裝了常見的反射操作。

          上圖中看到的可視化 stream 調(diào)用也是通過這個庫實現(xiàn)的。

          負(fù)載均衡

          由于 gRPC 是基于 HTTP/2 實現(xiàn)的,客戶端和服務(wù)端會保持長連接;這時做負(fù)載均衡就不像是 HTTP 那樣簡單了。

          而我們使用 gRPC 想達(dá)到效果和 HTTP 是一樣的,需要對請求進(jìn)行負(fù)載均衡而不是連接。

          通常有兩種做法:

          • 客戶端負(fù)載均衡
          • 服務(wù)端負(fù)載均衡

          客戶端負(fù)載均衡在 rpc 調(diào)用中應(yīng)用廣泛,比如 Dubbo 就是使用的客戶端負(fù)載均衡。

          gRPC 中也提供有相關(guān)接口,具體可以參考官方demo。

          https://github.com/grpc/grpc-go/blob/87eb5b7502/examples/features/load_balancing/README.md

          客戶端負(fù)載均衡相對來說對開發(fā)者更靈活(可以自定義適合自己的策略),但相對的也需要自己維護(hù)這塊邏輯,如果有多種語言那就得維護(hù)多份。

          所以在云原生這個大基調(diào)下,更推薦使用服務(wù)端負(fù)載均衡。

          可選方案有:

          • istio
          • envoy
          • apix

          這塊我們也在研究,大概率會使用 envoy/istio

          總結(jié)

          gRPC 內(nèi)容還是非常多的,本文只是作為一份入門資料希望能讓不了解 gRPC 的能有一個基本認(rèn)識;這在云原生時代確實是一門必備技能。

          對文中的 gRPC 客戶端感興趣的朋友,可以參考這里的源碼:https://github.com/crossoverJie/ptg



          瀏覽 52
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  射射蜜桃av免费电影 | 亚洲中文字幕在线播放 | 欧美三级黄 | 黄网免费在线观看 | 欧美搞搞网 |