<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>

          gRPC服務(wù)的響應(yīng)設(shè)計(jì)

          共 8455字,需瀏覽 17分鐘

           ·

          2021-11-19 09:44

          1. 服務(wù)端響應(yīng)的現(xiàn)狀

          做后端服務(wù)的開發(fā)人員對(duì)錯(cuò)誤處理總是很敏感的,因此在做服務(wù)的響應(yīng)(response/reply)設(shè)計(jì)時(shí)總是會(huì)很慎重。

          如果后端服務(wù)選擇的是HTTP API(rest api),比如json over http,API響應(yīng)(Response)中大多會(huì)包含如下信息:

          {
          ?"code":?0,
          ?"msg":?"ok",
          ?"payload"?:?{
          ????????...?...
          ?}
          }

          在這個(gè)http api的響應(yīng)設(shè)計(jì)中,前兩個(gè)狀態(tài)標(biāo)識(shí)這個(gè)請(qǐng)求的響應(yīng)狀態(tài)。這個(gè)狀態(tài)由一個(gè)狀態(tài)代碼(code)與狀態(tài)信息(msg)組成。狀態(tài)信息是對(duì)狀態(tài)代碼所對(duì)應(yīng)錯(cuò)誤原因的詳細(xì)詮釋。只有當(dāng)狀態(tài)為正常時(shí)(code = 0),后面的payload才具有意義。payload顯然是在響應(yīng)中意圖傳給客戶端的業(yè)務(wù)信息。

          這樣的服務(wù)響應(yīng)設(shè)計(jì)是目前比較常用且成熟的方案,理解起來也十分容易。

          好,現(xiàn)在我們看看另外一大類服務(wù):采用RPC方式提供的服務(wù)。我們還是以使用最為廣泛的gRPC為例。在gRPC中,一個(gè)service的定義如下(我們借用一下grpc-go提供的helloworld示例[1]):

          //?https://github.com/grpc/grpc-go/blob/master/examples/helloworld/helloworld/helloworld.proto
          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?{
          ??string?message?=?1;
          }

          grpc對(duì)于每個(gè)rpc方法(比如SayHello)都有約束,只能有一個(gè)輸入?yún)?shù)和一個(gè)返回值。這個(gè).proto定義通過protoc生成的go代碼變成了這樣:

          //?https://github.com/grpc/grpc-go/blob/master/examples/helloworld/helloworld/helloworld_grpc.pb.go
          type?GreeterServer?interface?{
          ?//?Sends?a?greeting
          ????SayHello(context.Context,?*HelloRequest)?(*HelloReply,?error)
          ????...?...
          }

          我們看到對(duì)于SayHello RPC方法,protoc生成的go代碼中,SayHello方法的返回值列表中多了一個(gè)Gopher們熟悉的error返回值。對(duì)于已經(jīng)習(xí)慣了HTTP API那套響應(yīng)設(shè)計(jì)的gopher來說,現(xiàn)在問題來了! http api響應(yīng)中表示響應(yīng)狀態(tài)的code與msg究竟是定義在HelloReply這個(gè)業(yè)務(wù)響應(yīng)數(shù)據(jù)中,還是通過error來返回的呢?這個(gè)grpc官方文檔似乎也沒有明確說明(如果各位看官找到位置,可以告訴我哦)。

          2. gRPC服務(wù)端響應(yīng)設(shè)計(jì)思路

          我們先不急著下結(jié)論!我們繼續(xù)借用helloworld這個(gè)示例程序來測(cè)試一下當(dāng)error返回值不為nil時(shí)客戶端的反應(yīng)!先改一下greeter_server[2]的代碼:

          //?SayHello?implements?helloworld.GreeterServer
          func?(s?*server)?SayHello(ctx?context.Context,?in?*pb.HelloRequest)?(*pb.HelloReply,?error)?{?
          ????log.Printf("Received:?%v",?in.GetName())
          ????return?&pb.HelloReply{Message:?"Hello?"?+?in.GetName()},?errors.New("test?grpc?error")
          }

          在上面代碼中,我們故意構(gòu)造一個(gè)錯(cuò)誤并返回給調(diào)用該方法的客戶端。我們來運(yùn)行一下這個(gè)服務(wù)并啟動(dòng)greeter_client[3]來訪問該服務(wù),在客戶端側(cè),我們得到的結(jié)果如下:

          2021/09/20?17:04:35?could?not?greet:?rpc?error:?code?=?Unknown?desc?=?test?grpc?error

          從客戶端的輸出結(jié)果中,我們看到了我們自定義的錯(cuò)誤的內(nèi)容(test grpc error)。但我們還發(fā)現(xiàn)錯(cuò)誤輸出的內(nèi)容中還有一個(gè)"code = Unknown"的輸出,這個(gè)code是從何而來呢?似乎grpc期待的error形式是包含code與desc的形式。

          這時(shí)候就不得不查看一下gprc-go(v1.40.0)的參考文檔[4]了!在grpc-go的文檔中我們發(fā)現(xiàn)幾個(gè)被DEPRECATED的與Error有關(guān)的函數(shù):

          在這幾個(gè)作廢的函數(shù)的文檔中都提到了用status包的同名函數(shù)替代。那么這個(gè)status包又是何方神圣?我們翻看grpc-go的源碼,終于找到了status包,在包說明的第一句中我們就找到了答案:

          Package?status?implements?errors?returned?by?gRPC.?

          原來status包實(shí)現(xiàn)了上面grpc客戶端所期望的error類型。那么這個(gè)類型是什么樣的呢?我們逐步跟蹤代碼:

          在grpc-go/status包中我們看到如下代碼:

          type?Status?=?status.Status

          //?New?returns?a?Status?representing?c?and?msg.
          func?New(c?codes.Code,?msg?string)?*Status?{
          ????return?status.New(c,?msg)
          }

          status包使用了internal/status包中的Status,我們?cè)賮砜磇nternal/status包中Status結(jié)構(gòu)的定義:

          //?internal/status
          type?Status?struct?{
          ????s?*spb.Status
          }

          //?New?returns?a?Status?representing?c?and?msg.
          func?New(c?codes.Code,?msg?string)?*Status?{
          ????return?&Status{s:?&spb.Status{Code:?int32(c),?Message:?msg}}
          }

          internal/status包的Status結(jié)構(gòu)體組合了一個(gè)*spb.Status類型(google.golang.org/genproto/googleapis/rpc/status包中的類型)的字段,繼續(xù)追蹤spb.Status:

          //?https://pkg.go.dev/google.golang.org/genproto/googleapis/rpc/status
          type?Status?struct?{
          ?//?The?status?code,?which?should?be?an?enum?value?of?[google.rpc.Code][google.rpc.Code].
          ?Code?int32?`protobuf:"varint,1,opt,name=code,proto3"?json:"code,omitempty"`
          ?//?A?developer-facing?error?message,?which?should?be?in?English.?Any
          ?//?user-facing?error?message?should?be?localized?and?sent?in?the
          ?//?[google.rpc.Status.details][google.rpc.Status.details]?field,?or?localized?by?the?client.
          ?Message?string?`protobuf:"bytes,2,opt,name=message,proto3"?json:"message,omitempty"`
          ?//?A?list?of?messages?that?carry?the?error?details.??There?is?a?common?set?of
          ?//?message?types?for?APIs?to?use.
          ?Details?[]*anypb.Any?`protobuf:"bytes,3,rep,name=details,proto3"?json:"details,omitempty"`
          ?//?contains?filtered?or?unexported?fields
          }

          我們看到最后的這個(gè)Status結(jié)構(gòu)包含了Code與Message。這樣一來,grpc的設(shè)計(jì)意圖就很明顯了,它期望開發(fā)者在error這個(gè)返回值中包含rpc方法的響應(yīng)狀態(tài),而自定義的響應(yīng)結(jié)構(gòu)體只需包含業(yè)務(wù)所需要的數(shù)據(jù)即可。我們用一幅示意圖來橫向建立一下http api與rpc響應(yīng)的映射關(guān)系:

          有了這幅圖,再面對(duì)如何設(shè)計(jì)grpc方法響應(yīng)這個(gè)問題時(shí),我們就胸有成竹了!

          grpc-go在codes包[5]中定義了grpc規(guī)范要求的10余種錯(cuò)誤碼:

          const?(
          ?//?OK?is?returned?on?success.
          ?OK?Code?=?0

          ?//?Canceled?indicates?the?operation?was?canceled?(typically?by?the?caller).
          ?//
          ?//?The?gRPC?framework?will?generate?this?error?code?when?cancellation
          ?//?is?requested.
          ?Canceled?Code?=?1

          ?//?Unknown?error.?An?example?of?where?this?error?may?be?returned?is
          ?//?if?a?Status?value?received?from?another?address?space?belongs?to
          ?//?an?error-space?that?is?not?known?in?this?address?space.?Also
          ?//?errors?raised?by?APIs?that?do?not?return?enough?error?information
          ?//?may?be?converted?to?this?error.
          ?//
          ?//?The?gRPC?framework?will?generate?this?error?code?in?the?above?two
          ?//?mentioned?cases.
          ?Unknown?Code?=?2

          ?//?InvalidArgument?indicates?client?specified?an?invalid?argument.
          ?//?Note?that?this?differs?from?FailedPrecondition.?It?indicates?arguments
          ?//?that?are?problematic?regardless?of?the?state?of?the?system
          ?//?(e.g.,?a?malformed?file?name).
          ?//
          ?//?This?error?code?will?not?be?generated?by?the?gRPC?framework.
          ?InvalidArgument?Code?=?3

          ????...?...

          ?//?Unauthenticated?indicates?the?request?does?not?have?valid
          ?//?authentication?credentials?for?the?operation.
          ?//
          ?//?The?gRPC?framework?will?generate?this?error?code?when?the
          ?//?authentication?metadata?is?invalid?or?a?Credentials?callback?fails,
          ?//?but?also?expect?authentication?middleware?to?generate?it.
          ?Unauthenticated?Code?=?16

          在這些標(biāo)準(zhǔn)錯(cuò)誤碼之外,我們還可以擴(kuò)展定義自己的錯(cuò)誤碼與錯(cuò)誤描述。

          3. 服務(wù)端如何構(gòu)造error與客戶端如何解析error

          前面提到,gRPC服務(wù)端采用rpc方法的最后一個(gè)返回值error來承載應(yīng)答狀態(tài)。google.golang.org/grpc/status包為構(gòu)建客戶端可解析的error提供了一些方便的函數(shù),我們看下面示例(基于上面helloworld的greeter_server[6]改造):

          func?(s?*server)?SayHello(ctx?context.Context,?in?*pb.HelloRequest)?(*pb.HelloReply,?error)?{
          ????log.Printf("Received:?%v",?in.GetName())
          ????return?nil,?status.Errorf(codes.InvalidArgument,?"you?have?a?wrong?name:?%s",?in.GetName())
          }

          status包提供了一個(gè)類似于fmt.Errorf的函數(shù),我們可以很方便的構(gòu)造一個(gè)帶有code與msg的error實(shí)例并返回給客戶端。

          而客戶端同樣可以通過status包提供的函數(shù)將error中攜帶的信息解析出來,我們看下面代碼:

          ctx,?_?:=?context.WithTimeout(context.Background(),?time.Second)
          r,?err?:=?c.SayHello(ctx,?&pb.HelloRequest{Name:?"tony")})
          if?err?!=?nil?{
          ????errStatus?:=?status.Convert(err)
          ????log.Printf("SayHello?return?error:?code:?%d,?msg:?%s\n",?errStatus.Code(),?errStatus.Message())
          }
          log.Printf("Greeting:?%s",?r.GetMessage())

          我們看到:通過status.Convert函數(shù)可以很簡答地將rpc方法返回的不為nil的error中攜帶的信息提取出來。

          4. 空應(yīng)答

          gRPC的proto文件規(guī)范要求每個(gè)rpc方法的定義中都必須包含一個(gè)返回值,返回值不能為空,比如上面helloworld項(xiàng)目的.proto文件中的SayHello方法:

          rpc?SayHello?(HelloRequest)?returns?(HelloReply)?{}

          如果去掉HelloReply這個(gè)返回值,那么protoc在生成代碼時(shí)會(huì)報(bào)錯(cuò)!

          但是有些方法本身不需要返回業(yè)務(wù)數(shù)據(jù),那么我們就需要為其定義一個(gè)空應(yīng)答消息,比如:

          message?Empty?{

          }

          考慮到每個(gè)項(xiàng)目在遇到空應(yīng)答時(shí)都要重復(fù)造上面Empty message定義的輪子,grpc官方提供了一個(gè)可被復(fù)用的空message:

          //?https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/empty.proto

          //?A?generic?empty?message?that?you?can?re-use?to?avoid?defining?duplicated
          //?empty?messages?in?your?APIs.?A?typical?example?is?to?use?it?as?the?request
          //?or?the?response?type?of?an?API?method.?For?instance:
          //
          //?????service?Foo?{
          //???????rpc?Bar(google.protobuf.Empty)?returns?(google.protobuf.Empty);
          //?????}
          //
          //?The?JSON?representation?for?`Empty`?is?empty?JSON?object?`{}`.
          message?Empty?{}

          我們只需在.proto文件中導(dǎo)入該empty.proto并使用Empty即可,比如下面代碼:

          //?xxx.proto

          syntax?=?"proto3";
          ??
          import?"google/protobuf/empty.proto";

          service?MyService?{
          ?rpc?MyRPCMethod(...)?returns?(google.protobuf.Empty);
          }

          當(dāng)然google.protobuf.Empty不僅僅適用于空響應(yīng),也適合空請(qǐng)求,這個(gè)就留給大家可自行完成吧。

          5. 小結(jié)

          本文我們講述了gRPC服務(wù)端響應(yīng)設(shè)計(jì)的相關(guān)內(nèi)容,最主要想說的是直接使用gRPC生成的rpc方面的error返回值來表示rpc調(diào)用的響應(yīng)狀態(tài),不要再在自定義的Message結(jié)構(gòu)中重復(fù)放入code與msg字段來表示響應(yīng)狀態(tài)了。

          btw,做API的錯(cuò)誤設(shè)計(jì),google的這份API設(shè)計(jì)方面的參考資料[7]是十分好的。有時(shí)間一定要好好讀讀哦。

          參考資料

          [1]?

          helloworld示例:?https://github.com/grpc/grpc-go/tree/master/examples/helloworld

          [2]?

          greeter_server:?https://github.com/grpc/grpc-go/blob/master/examples/helloworld/greeter_server/main.go

          [3]?

          greeter_client:?https://github.com/grpc/grpc-go/tree/master/examples/helloworld/greeter_client

          [4]?

          gprc-go(v1.40.0)的參考文檔:?https://pkg.go.dev/google.golang.org/grpc#section-readme

          [5]?

          codes包:?https://pkg.go.dev/google.golang.org/[email protected]/codes#Code

          [6]?

          greeter_server:?https://github.com/grpc/grpc-go/blob/master/examples/helloworld/greeter_server/main.go

          [7]?

          google的這份API設(shè)計(jì)方面的參考資料:?https://cloud.google.com/apis/design/errors

          [8]?

          改善Go語?編程質(zhì)量的50個(gè)有效實(shí)踐:?https://www.imooc.com/read/87

          [9]?

          Kubernetes實(shí)戰(zhàn):高可用集群搭建、配置、運(yùn)維與應(yīng)用:?https://coding.imooc.com/class/284.html

          [10]?

          鏈接地址:?https://m.do.co/c/bff6eed92687



          推薦閱讀


          福利

          我為大家整理了一份從入門到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。關(guān)注公眾號(hào) 「polarisxu」,回復(fù)?ebook?獲取;還可以回復(fù)「進(jìn)群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。

          瀏覽 47
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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 | 在线免费观看亚洲a |