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

          開發(fā)基于 gRPC 協(xié)議的 Node 服務(wù)

          共 16435字,需瀏覽 33分鐘

           ·

          2021-07-04 19:02

          本文由騰訊文檔的前端開發(fā)工程師張南華撰寫。他曾在 Shopee 主導(dǎo)上萬(wàn) qps 配置中心項(xiàng)目的研發(fā)工作,負(fù)責(zé) Node 項(xiàng)目架構(gòu)、技術(shù)方案設(shè)計(jì)等核心研發(fā)工作。目前主要負(fù)責(zé)騰訊文檔前端容器與新品類研發(fā)工作。擅長(zhǎng) Node 服務(wù)工程化、前端性能優(yōu)化、質(zhì)量體系建設(shè)等相關(guān)內(nèi)容。喜歡研究新技術(shù)、參加開源活動(dòng)。

          祝大家端午安康!

          前言

          在 Shopee 任職期間,我在開發(fā) gRPC 協(xié)議的 node 微服務(wù)時(shí)有過不錯(cuò)的一些實(shí)踐,配置中心、差分服務(wù)、官網(wǎng)服務(wù)等。因此我寫下這篇文章,做一些些的總結(jié),記錄一下碰到的問題、解決的方法以及一些個(gè)人的小小見解。

          這篇文章主要會(huì)介紹一下在前端領(lǐng)域使用 gRPC 協(xié)議的方法、使用時(shí)碰到的一些問題以及目前開源社區(qū)的一些反應(yīng)。所有的內(nèi)容不僅出自個(gè)人的感受,還會(huì)有一些相應(yīng)的資料作為支撐。文章里面不會(huì)有具體的實(shí)現(xiàn)細(xì)節(jié),我覺得沒太必要,官方文檔告訴你得更多更準(zhǔn)確,更多的可能會(huì)是一些方法論的內(nèi)容。

          開始之前給大家簡(jiǎn)單介紹一下 gRPC 的背景知識(shí),有基礎(chǔ)的同學(xué)可以直接跳過。

          What is gRPC?

          gRPC is a modern, open source remote procedure call (RPC) framework that can run anywhere. It enables client and server applications to communicate transparently, and makes it easier to build connected systems.

          Read the longer Motivation & Design Principles[1] post for background on why we created gRPC.

          gRPC 是一個(gè)由 Google 推出的、高性能、開源、通用的 rpc 框架。它是基于 HTTP2 協(xié)議標(biāo)準(zhǔn)設(shè)計(jì)開發(fā),默認(rèn)采用 Protocol Buffers 數(shù)據(jù)序列化協(xié)議,支持多種開發(fā)語(yǔ)言。通俗說(shuō)就是一種 Google 設(shè)計(jì)的二進(jìn)制rpc協(xié)議。

          What is protobuf?

          hello.pb

          // The greeter 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 協(xié)議是基于 protobuf 進(jìn)行通信的。開發(fā)者在 protobuf 文件里面定義詳細(xì)的 service,message。在 messgae 內(nèi)部,為請(qǐng)求、返回的字段定義精確的數(shù)據(jù)類型。protobuf 文件再通過編譯成各種語(yǔ)言版本的文件,提供給 grpc 服務(wù)的 server、client 使用。

          server & client 的使用

          動(dòng)態(tài)編譯

          官方提供了 node-grpc 類庫(kù),為 node 端使用 gRPC 協(xié)議提供了一系列的支持。其中 `packages/proto-loader`[2] 提供了一個(gè)動(dòng)態(tài)編譯 protobuf 文件的功能。它會(huì)將一個(gè) protobuf 文件內(nèi)的 server 轉(zhuǎn)化成一個(gè)實(shí)例對(duì)象返回。如下我們就獲取了一個(gè) routeguide 對(duì)象,然后我們就可以使用這個(gè)對(duì)象去做接口訪問或者創(chuàng)建一個(gè)server。

          var PROTO_PATH = __dirname + '/../../../protos/hello.proto';
          var grpc = require('@grpc/grpc-js');
          var protoLoader = require('@grpc/proto-loader');
          // Suggested options for similarity to existing grpc.load behavior
          var packageDefinition = protoLoader.loadSync(
              PROTO_PATH,
              {keepCasetrue,
               longsString,
               enumsString,
               defaultstrue,
               oneofstrue
              });
          var protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
          // The protoDescriptor object has the full package hierarchy
          var helloObj = protoDescriptor.hello;
          使用 helloObj 對(duì)象為創(chuàng)建 gRPC server 提供 protobuf 對(duì)象的定義
          import grpc from "@grpc/grpc-js";

          // 具體方法實(shí)現(xiàn)可以去看官方的樣例。
          // https://grpc.io/docs/languages/node/basics/
          // 這里的方法都是(call,callback,metedata)=>{} 類型的。
          // 這里只舉一個(gè)例子
          function sayHello(call,callback{
            // do something
            callback();
          }

          function getServer({
            var server = new grpc.Server();
            server.addService(helloObj.Greeter.service, {
              sayHello: sayHello,
            });
            return server;
          }

          var greeterServer = getServer();
          greeterServer.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => {
           routeServer.start();
          });
          使用 helloObj 對(duì)象建立 gRPC 連接

          真正應(yīng)用于業(yè)務(wù)時(shí),服務(wù)具體接口的實(shí)現(xiàn)及代碼結(jié)構(gòu)應(yīng)該要更有邏輯,這里只是作為調(diào)用樣例展示。

          var client = new helloObj.Greeter('localhost:50051',
                                                 grpc.credentials.createInsecure());

          function sayHello(callback{
            const helloReq = {
              name"you"
            };
            var call = client.sayHello(name, function(err, response{
              console.log('Greeting:', response.message);
            });
          }

          靜態(tài)編譯

          使用官方的類庫(kù) grpc-tools,編譯生成 _pb.d.ts_grpc_pb.d.ts 文件,前者將 protobuf 里面的 message、enum 等定義生成代碼的具體實(shí)現(xiàn),后者則生產(chǎn) client、server 的接口及具體的類。下面的例子會(huì)列舉 hello_pb.d.tshello_grpc_pb.d.ts 文件這個(gè)例子。

          因?yàn)橛猩?ts 聲明文件,因此在靜態(tài)編譯時(shí),我們也因此可以使用 ts 類型啦。

          // hello_pb.d.ts 里面 HelloRequest 的定義
          import * as jspb from "google-protobuf";

          export class HelloRequest extends jspb.Message 
              getName(): string;
              setName(value: number): HelloRequest;
            
              serializeBinary(): Uint8Array;
              toObject(includeInstance?: boolean): HelloRequest.AsObject;
              static toObject(includeInstance: boolean, msg: Book): HelloRequest.AsObject;
          }
                                  
          export namespace HelloRequest {
              export type AsObject = {
                  name: string,
              }


          hello_grpc_pb.d.tsGreeterClient,第一個(gè)參數(shù)接收 address(即服務(wù)地址),生成可調(diào)用的 client 去訪問 server。目前最新的版本支持 uds、dns 兩種格式,如果需要支持 etcd,需要自行實(shí)現(xiàn)官方的 resolver 去做 etcd 地址的解析及獲取。[3]

          // hello_grpc_pb.d.ts 文件里面截取需要的內(nèi)容

          import * as hello_pb from "./hello_pb";
          import * as grpc from "@grpc/grpc-js";

          interface IGreeterService extends grpc.ServiceDefinition<grpc.UntypedServiceImplementation> {
              sayHello: IGreeterServiceService_ISayHello;
          }
          export const GreeterService: IGreeterService;
                                  
                                  
          export class GreeterClient extends grpc.Client {
            constructor(address: string, credentials: grpc.ChannelCredentials, options?: object);
            sayHello(
              argument: helloworld_pb.HelloRequest,
              callback: grpc.requestCallback<helloworld_pb.HelloReply>
            ): grpc.ClientUnaryCall;
            sayHello(
              argument: helloworld_pb.HelloRequest,
              metadataOrOptions: grpc.Metadata | grpc.CallOptions | null,
              callback: grpc.requestCallback<helloworld_pb.HelloReply>
            ): grpc.ClientUnaryCall;
            sayHello(
              argument: helloworld_pb.HelloRequest,
              metadata: grpc.Metadata | null,
              options: grpc.CallOptions | null,
              callback: grpc.requestCallback<helloworld_pb.HelloReply>
            ): grpc.ClientUnaryCall;
          }
          使用靜態(tài)文件建立服務(wù)
          import {GreeterService, IGreeterService} from "./proto/hello_grpc_pb";

          function sayHello(call,callback,metedata){
            // do something
            callback();
          }

          function main({
            var server = new grpc.Server();
            server.addService(GreeterService,
                                   {sayHello: sayHello});
            server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => {
              server.start();
            });
          }
          使用靜態(tài)文件建立 Client 連接
          import { GreeterClient } from "./proto/hello_grpc_pb";
          import { HelloRequest, HelloReply } from "./proto/hello_pb";

          const client = new GreeterClient("127.0.0.1:50051", grpc.credentials.createInsecure());

          const helloRequest = new HelloRequest();
          helloRequest.setName('hello world!')

          // 完成一次調(diào)用
          client.sayHello(request, (err, reply: HelloReply) => {
            if (err != null) {
              debug(`[sayHello] err:\nerr.message: ${err.message}\nerr.stack:\n${err.stack}`);
              reject(err); return;
            }
            log(`[sayHello] HelloReply: ${JSON.stringify(reply.toObject())}`);
            resolve(book);
          });


          二進(jìn)制rpc協(xié)議?

          這里用一張圖簡(jiǎn)單解釋一下,一次gRPC請(qǐng)求發(fā)生的事情。就以客戶端發(fā)起一次sayHello請(qǐng)求為例。

          如果是動(dòng)態(tài)調(diào)用的話,在序列化HelloRequest前還有一個(gè)步驟。json對(duì)象向HelloRequest的轉(zhuǎn)化。


          Why use gRPC?

          在版本推進(jìn)的過程中,ShopeePay 的前端團(tuán)隊(duì)承接的一些內(nèi)容越來(lái)越多,從最開始簡(jiǎn)單的微服務(wù)接口的合并轉(zhuǎn)發(fā)、到技術(shù)項(xiàng)目以 node 服務(wù)實(shí)現(xiàn)、再到部分業(yè)務(wù)服務(wù)直接由 node 承擔(dān)。那為什么 shopee 的前端 node 服務(wù)要使用 gRPC 呢?直接使用 http 協(xié)議的 koa、express 開源框架不香嗎?

          其實(shí)最開始的時(shí)候,最早有一些 node 服務(wù)是用 http 協(xié)議去實(shí)現(xiàn)的。但是隨著基建漸漸成熟、開發(fā)環(huán)境逐漸穩(wěn)定、前端尋求承擔(dān)團(tuán)隊(duì)更多的業(yè)務(wù)職責(zé)之后,我們最終還是開始使用 gRPC。那除了上面介紹的 gRPC 二進(jìn)制流協(xié)議本身的優(yōu)勢(shì)之外,這里當(dāng)然也有一部分是為了開發(fā)環(huán)境的兼容。

          協(xié)議同步

          在微服務(wù)的架構(gòu)中,前后端網(wǎng)關(guān)(grpc 微服務(wù))和 node 微服務(wù)的通訊、后臺(tái) go 微服務(wù)和 node 微服務(wù)的相互調(diào)用是避免不了。如果使用 http 服務(wù),就會(huì)面臨協(xié)議溝通上的問題,即網(wǎng)關(guān)會(huì)增加特殊邏輯去訪問 http 接口、go 服務(wù)及網(wǎng)關(guān)訪問 node 的 http 服務(wù)時(shí)也無(wú)法直接發(fā)起 grpc 連接,http 服務(wù)也無(wú)法直接訪問一個(gè) gRPC 服務(wù)。

          下圖簡(jiǎn)單的介紹了一下區(qū)別,當(dāng) http 服務(wù)的職責(zé)比較單一,或者是作為一個(gè)單純的”資源提供者“,那還是可以接受的,開發(fā)者需要在代碼里面重新定義新的網(wǎng)關(guān)地址,為該服務(wù)封裝新的 http 前綴地址;反之則是相對(duì)比較麻煩的。當(dāng)然總可以通過一些 hack 的手法去達(dá)到目的,但無(wú)疑都會(huì)增加很多的開銷,得不償失。


          而在協(xié)議統(tǒng)一之后,我們的 gRPC 服務(wù)就與語(yǔ)言無(wú)關(guān)了。通訊時(shí)即不需要做特殊邏輯的代碼,又可以享受統(tǒng)一網(wǎng)關(guān)的支持、監(jiān)控平臺(tái)數(shù)據(jù)的采集等通用的功能支持,不需要自實(shí)現(xiàn)一套。


          服務(wù)路由復(fù)雜化

          ShopeePay 的線上環(huán)境是是部署在 Kubernetes 集群內(nèi)部的。由 ingress 分配給 pod 對(duì)應(yīng)的 ingress 域名,可以理解為內(nèi)部域名,只能在集群內(nèi)部訪問。線上環(huán)境,集群內(nèi)部不允許訪問任何公網(wǎng)域名,需要申請(qǐng)白名單。因此我們需要訪問集群內(nèi)的node服務(wù)的話,需要做以下幾件事情:

          • 為集群內(nèi)的 node 服務(wù)申請(qǐng)公網(wǎng)域名,通過公網(wǎng)域名訪問。
          • 同時(shí)也需要向運(yùn)維申請(qǐng)對(duì)應(yīng)域名在集群內(nèi)部的訪問白名單權(quán)限。

          帶來(lái)的后果就是,服務(wù)數(shù)量對(duì)應(yīng)申請(qǐng)的域名也越來(lái)越多,接口路由的這些職責(zé)本來(lái)就應(yīng)該由網(wǎng)關(guān)去承擔(dān),但卻因此復(fù)雜化了。

          同時(shí)就在前端網(wǎng)關(guān)中應(yīng)用的實(shí)際場(chǎng)景來(lái)說(shuō),前端網(wǎng)關(guān)適配了一些額外的 http 服務(wù),既有業(yè)務(wù)相關(guān),也有技術(shù)側(cè)相關(guān)的一些服務(wù)。當(dāng)用戶請(qǐng)求這些服務(wù)的接口時(shí),會(huì)先去訪問前端網(wǎng)關(guān),網(wǎng)關(guān)再去請(qǐng)求服務(wù)對(duì)外的公網(wǎng)域名,公網(wǎng)域名經(jīng)過 nginx 代理到 ingress 域名,然后才訪問到接口。


          全鏈路

          對(duì)于從客戶端發(fā)起一次請(qǐng)求,再到客戶端接收響應(yīng),在復(fù)雜的業(yè)務(wù)場(chǎng)景里面整個(gè)鏈路是相當(dāng)長(zhǎng)的,業(yè)務(wù)網(wǎng)關(guān)(gRPC 服務(wù))會(huì)將唯一的 trace-id 存放在 metadata 里面,然后在一整個(gè)鏈路上傳遞下去。metedata 可以理解為 gRPC 協(xié)議的特有的 header,http 服務(wù)從協(xié)議層就無(wú)法獲取。如果鏈路中訪問的有 http 服務(wù),那么一整個(gè)請(qǐng)求鏈路就會(huì)出現(xiàn)斷鏈,這樣對(duì)我們線上查找鏈路的錯(cuò)誤日志會(huì)造成比較大的麻煩。


          挑戰(zhàn)

          其實(shí)拋開 gRPC 協(xié)議不談,開發(fā)微服務(wù)和普通的node服務(wù)沒什么差別。我們沒有使用 protobuf.js[4],它也使用 node 實(shí)現(xiàn)了 gRPC 協(xié)議,同時(shí)在我看來(lái)這個(gè) gRPC 庫(kù)更靈活,可以攔截請(qǐng)求,完成一些比如 json 解析器等比較好用的事情,但是官方項(xiàng)目的 manager 認(rèn)為這種攔截請(qǐng)求并更改的行為是相當(dāng)不安全的,對(duì)比之下我們就堅(jiān)持官方庫(kù)。

          適配后臺(tái)網(wǎng)關(guān)

          正好說(shuō)到這個(gè),在業(yè)務(wù)進(jìn)展的過程里,我們前后端其實(shí)都碰到了針對(duì)網(wǎng)關(guān)的優(yōu)化。對(duì)于網(wǎng)關(guān)這一主體來(lái)說(shuō),不應(yīng)該也不需要存儲(chǔ)任何 pb 文件的,鑒權(quán)的 pb 接口除外。之前介紹的時(shí)候有說(shuō)過,gRPC 必須基于 gRPC 的 pb 文件通訊,不同語(yǔ)言編譯成不同的版本的源文件。那這里前后端是分別怎么解決這個(gè)問題的呢?

          后端網(wǎng)關(guān)發(fā)送請(qǐng)求時(shí)傳遞一個(gè)標(biāo)志位和 json 數(shù)據(jù),當(dāng) go 服務(wù)接收請(qǐng)求獲取到該標(biāo)志位時(shí),就由服務(wù)側(cè)將 json 轉(zhuǎn)化為 go 服務(wù)需要的 pb struct 對(duì)象。從實(shí)現(xiàn)層看起來(lái),就是網(wǎng)關(guān)傳遞 json,go 服務(wù)接收 json,協(xié)議沒變但是沒有涉及二進(jìn)制的轉(zhuǎn)換。

          而前端服務(wù)因?yàn)榈讓訋?kù)直接給開發(fā)者的就是 call 對(duì)象,不支持?jǐn)r截請(qǐng)求。所以我們放棄了去更改,而是為接入后端網(wǎng)關(guān)時(shí)做了一層適配,我們采用了一個(gè)統(tǒng)一的 protobuf message,我們稱之 CommonMessage,發(fā)起請(qǐng)求和獲取請(qǐng)求都由 CommonMessage 去序列化、反序列化。因?yàn)?CommonMessage 的 messgae 只有一個(gè)二進(jìn)制,帶來(lái)的問題是,前端開發(fā)者調(diào)試的時(shí)候需要費(fèi)一下心轉(zhuǎn)化哈哈。


          CommonMessage 的pb定義

          // The request message containing the common content.
          message CommonMessage {
          byte content = 1;
          }

          grpc vs grpc-js

          當(dāng)你打開 grpc-node[5] 這個(gè)地址時(shí),明晃晃的告訴大家,node 的 grpc 官方庫(kù)有兩個(gè)版本。一個(gè)是純 c 的 grpc,一個(gè)是純 js 的 grpc-js。在我們決定使用并開發(fā) grpc 微服務(wù)時(shí),當(dāng)時(shí)的版本是 grpc,因此我們也經(jīng)歷的一次版本升級(jí)。這里不會(huì)詳細(xì)的介紹兩者的區(qū)別,因?yàn)闆]有比這寫的更詳細(xì)的了。https://github.com/grpc/grpc-node/blob/master/PACKAGE-COMPARISON.md

          當(dāng)然,推動(dòng)我們更新大版本的動(dòng)力是,grpc 在使用 http2 協(xié)議打包傳送的數(shù)據(jù)越大,性能就越差。而 grpc-js 則不會(huì)有這個(gè)性能損耗。之前面臨的一個(gè)問題,在我們的測(cè)試環(huán)境只傳遞 300KB 的數(shù)據(jù)為返回時(shí),grpc 消耗 1000~2000ms,grpc-js 則維持在了 20~30ms。其余的遷移可以參考https://github.com/grpc/grpc-node/tree/master/packages/grpc-js

          callback(回包數(shù)據(jù)大小)grpc-jsgrpc
          5KB1~10ms1~10ms
          500KB1~10ms1000ms~2000ms

          callback 參看兩個(gè)版本庫(kù)的源碼,可以知道它主要做了反序列化、打包的一些事情。

          // grpc 源碼
          /**
           * Send a response to a unary or client streaming call.
           * @private
           * @param {grpc.Call} call The call to respond on
           * @param {*} value The value to respond with
           * @param {grpc~serialize} serialize Serialization function for the
           *     response
           * @param {grpc.Metadata=} metadata Optional trailing metadata to send with
           *     status
           * @param {number=} [flags=0] Flags for modifying how the message is sent.
           */

          function sendUnaryResponse(call, value, serialize, metadata, flags{
            // a 
            var end_batch = {};
            var statusMetadata = new Metadata();
            var status = {
              code: constants.status.OK,
              details'OK'
            };
            if (metadata) {
              statusMetadata = metadata;
            }
            var message;
            try {
              message = serialize(value);
            } catch (e) {
              common.log(constants.logVerbosity.ERROR, e);
              e.code = constants.status.INTERNAL;
              handleError(call, e);
              return;
            }
            status.metadata = statusMetadata._getCoreRepresentation();
            if (!call.metadataSent) {
              end_batch[grpc.opType.SEND_INITIAL_METADATA] =
                  (new Metadata())._getCoreRepresentation();
              call.metadataSent = true;
            }
            message.grpcWriteFlags = flags;
            end_batch[grpc.opType.SEND_MESSAGE] = message;
            end_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = status;
            // 打點(diǎn)
            // b
            call.startBatch(end_batch, function (){});
            // 打點(diǎn) 
            // c
          }

          在 grpc 庫(kù)的源碼里,a-b:組裝返回的 messge 和 metadata,b-c:使用 http2 發(fā)起請(qǐng)求。a-b 的序列化數(shù)據(jù)、打包數(shù)據(jù)耗時(shí)打點(diǎn)約為 1~2ms,主要的耗時(shí)都在 b-c 這一段代碼。startBatch 方法是用 c 實(shí)現(xiàn)的,無(wú)從優(yōu)化,因此只能選擇升級(jí)為 grpc-js。截止到寫這篇文章時(shí),grpc 庫(kù)已經(jīng)處于 deprecated 狀態(tài)了。

          擁抱?.開源社區(qū)

          gRPC node 版本的開源生態(tài)感覺起來(lái)不是特別好。在 GitHub 上看一些項(xiàng)目 issue 查找問題的過程中,我時(shí)不時(shí)碰到這樣的回答 “放棄node,轉(zhuǎn)入go語(yǔ)言的懷抱”,因此常常不得不自己上手解決一些問題,比如為 grpc 協(xié)議 fork一個(gè) node-grpc-interceptors[6] (ctx 增加 response、增加 errorcallback)、壓測(cè)嘗試 grpc 是否支持 worker(多進(jìn)程)等等。這屬實(shí)讓人感受到一些沮喪,蓽露藍(lán)蔞的感受。

          在 20 年 4 月份 @grpc/grpc-js 1.0.0[7]正式發(fā)布之后,官方又開始迅速迭代。這反而引發(fā)了上文所說(shuō)的 grpc-js 新舊版本的 Resolver 類不兼容,導(dǎo)致之前我們一位大神自定義的 etcd 的 Resolver 在新版本報(bào)錯(cuò)的情況;proto-tools 的版本與 grpc-js 的版本有相對(duì)比較嚴(yán)格的定義;官方的實(shí)現(xiàn)的 plugin 插件與私人實(shí)現(xiàn)的 plugin 重名沖突,導(dǎo)致無(wú)法生成必要的 ts 文件等。為了解決這些問題,我們采取了一些不太合適的解決方式(固定版本等),但我相信隨著版本的逐漸穩(wěn)定,這些問題也就會(huì)不再出現(xiàn)。

          新的 Protobuff 倉(cāng)庫(kù)

          Protobuff 是在 ShopeePay 做的一個(gè)管理前端 protobuff 文件、生成的 static 文件以及為網(wǎng)關(guān)類服務(wù)提供 proto client 對(duì)象的公共項(xiàng)目。

          在 ShopeePay 的前端服務(wù)越來(lái)越來(lái)多的場(chǎng)景下,我們也不得不面對(duì)和業(yè)務(wù)服務(wù)一樣的問題,越來(lái)越多的服務(wù)對(duì)應(yīng)越來(lái)越多的 protobuff 文件及配置、node 服務(wù)的 gRPC 請(qǐng)求調(diào)用這樣的公共模塊(發(fā)起 gRPC 調(diào)用需要 proto 文件,一個(gè)服務(wù)的 proto 在多個(gè)服務(wù)的代碼里面維護(hù))越來(lái)越強(qiáng)烈剝離出去的需求。

          因此我們就做了這樣的一個(gè)公共倉(cāng)庫(kù),由于 grpc-js 不暴露任何接口攔截的可能,于是上文說(shuō)的 json 解析器(json to protobuff message 對(duì)象)的應(yīng)用也就無(wú)從說(shuō)起。那怎么做呢?最終我們采取了給前端網(wǎng)關(guān)提供一個(gè)存儲(chǔ)在內(nèi)存的 client 對(duì)象列表的方案(動(dòng)態(tài)編譯),供網(wǎng)關(guān)服務(wù)調(diào)用接口使用。同時(shí)我們也手動(dòng)實(shí)現(xiàn) etcd 的 resolver,注冊(cè),解決了我們動(dòng)態(tài)獲取 Kubernetes 部署的 pod的內(nèi)網(wǎng) ip 地址的需求。于是就完美的滿足了當(dāng)前的環(huán)境開發(fā)、使用的需求。


          主要的受益在以下幾個(gè)方面:

          • 前端網(wǎng)關(guān)是存儲(chǔ) protobuff 文件的,但是在 node_modules 里面存放,所以服務(wù)、網(wǎng)關(guān)和 pb 文件是解耦的。
          • 在網(wǎng)關(guān)類應(yīng)用時(shí),靜態(tài)生成的類只有通過屬性的 set 方法才能設(shè)置,因此不被采納。所以網(wǎng)關(guān)類應(yīng)用獲益于這個(gè)項(xiàng)目,實(shí)現(xiàn)了 pb 配置和網(wǎng)關(guān)代碼的耦合。
          • 發(fā)布上線時(shí),從網(wǎng)關(guān)一份 pb、服務(wù)本身一份 pb、n 個(gè)調(diào)用方的 n 份 pb 的改動(dòng)轉(zhuǎn)變?yōu)?Protobuff 倉(cāng)庫(kù)的一份 pb。
          • ...

          總結(jié)

          兜兜轉(zhuǎn)轉(zhuǎn)寫了很多,其實(shí)每一段內(nèi)容都可以更加深入的展開來(lái)講講。gRPC 協(xié)議的 ‘hello world’,一些為了推動(dòng)前端微服務(wù)化在 ShopeePay 做的嘗試,在需求迭代時(shí)碰到的一些問題以及去解決的過程等等。gRPC 很好用也很不好用,很好用是指接入很方便、上手很快、性能很穩(wěn)定、日常需求 cover 很簡(jiǎn)單,很不好用是指深入碰到問題得自己解決,目前的版本還在快速迭代等等。

          即是總結(jié)語(yǔ),也是展望語(yǔ)。

          以上就是我在 ShopeePay 任職期間接觸 gRPC 相關(guān)的全部日常。感謝前老大 BrandonXiang、HeyLi 的在 Shopee 任職時(shí)提供的各方面的幫助,同時(shí)也很幸運(yùn)在那個(gè)優(yōu)秀的前端團(tuán)隊(duì)內(nèi)擰了許久的螺絲釘(~_~)。展望即是,在新的團(tuán)隊(duì)也可以摸摸索索的前行,把自己不熟悉的領(lǐng)域變得熟悉起來(lái),繼續(xù)擰好自己手里的那顆不太一樣的新螺絲釘。

          關(guān)注我們

          我們將為你帶來(lái)最前沿的前端資訊。

          參考資料

          [1]

          Motivation & Design Principles: https://grpc.io/blog/principles/

          [2]

          packages/proto-loader: https://github.com/grpc/grpc-node/tree/master/packages/proto-loader

          [3]

          https://github.com/grpc/grpc/blob/master/doc/naming.md

          [4]

          protobuf.js: https://github.com/protobufjs/protobuf.js

          [5]

          grpc-node: https://github.com/grpc/grpc-node

          [6]

          node-grpc-interceptors: https://github.com/NanhuaZhang/node-grpc-interceptors

          [7]

          @grpc/grpc-js 1.0.0: https://github.com/grpc/grpc-node/releases/tag/%40grpc%2Fgrpc-js%401.0.0

          瀏覽 85
          點(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>
                  亚洲乱伦无码视频 | 亚洲人视频网站 | 国产视频精品i | 丝袜乱伦超碰 | 91大香蕉熟女 |