<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從入門到放棄之概述與實(shí)戰(zhàn)

          共 16388字,需瀏覽 33分鐘

           ·

          2022-03-18 23:23

          ?

          微服務(wù)如火如荼的當(dāng)下,各種服務(wù)框架層出不窮。Dubbo、SpringCloud在國(guó)內(nèi)Java后端微服務(wù)領(lǐng)域目前占據(jù)大部分份額。

          但是隨著云原生愈發(fā)普及,具備跨語(yǔ)言、高性能特性的RPC通信框架橫空出世,其中g(shù)RPC與Thrift是其中的佼佼者。

          ?

          本文我們將視角集中在gRPC這RPC框架。

          ?

          gRPC 是Google開(kāi)源的高性能、通用的RPC框架??蛻舳伺c服務(wù)端約定接口調(diào)用, 可以在各種環(huán)境中運(yùn)行,具有跨語(yǔ)言特性, 適合構(gòu)建分布式、微服務(wù)應(yīng)用。

          ?

          個(gè)人認(rèn)為,gRPC最為殺手锏的特性就是“跨語(yǔ)言”,其次才是高性能。

          它的跨語(yǔ)言特性體現(xiàn)在,通過(guò)定義IDL(接口定義語(yǔ)言),隔離了不同編程語(yǔ)言之間的差異,對(duì)IDL進(jìn)行編譯后,生成對(duì)應(yīng)編程語(yǔ)言的nativeCode,讓開(kāi)發(fā)者能夠集中注意在實(shí)現(xiàn)業(yè)務(wù)需求上,而不需要花費(fèi)額外的精力在語(yǔ)言層面上。

          ?

          官網(wǎng)的一張圖能夠很好地體現(xiàn)這個(gè)特點(diǎn)

          ?

          gRPC多語(yǔ)言

          gRPC特性介紹

          ?

          gRPC具備以下特性

          ?
          • 性能優(yōu)異:

            1. 它采用Proto Buffer作序列化傳輸媒介, 對(duì)比JSON與XML有數(shù)倍提升。

            2. 采用HTTP2協(xié)議, 對(duì)頭部信息(header)壓縮, 對(duì)連接進(jìn)行復(fù)用,能夠減少TCP連接次數(shù)。

            3. 針對(duì)Java語(yǔ)言,gRPC底層采用Netty作為NIO處理框架, 性能強(qiáng)勁。

          • 多語(yǔ)言支持,多客戶端接入, 支持C++/GO/Ruby等語(yǔ)言。

          • 支持負(fù)載均衡、跟蹤、健康檢查和認(rèn)證。

          gRPC的線程模型是怎樣的?

          ?

          筆者主力語(yǔ)言為Java,因此我們講解也集中在Java的實(shí)現(xiàn)上。

          ?

          gRPC的Java實(shí)現(xiàn),服務(wù)端底層采用了Netty作為核心處理框架,因此其線程模型核心也是遵循了 Netty 的線程分工原則。

          協(xié)議層消息的接收和編解碼由 Netty 的 I/O(NioEventLoop) 線程負(fù)責(zé), 應(yīng)用層的處理由應(yīng)用線程負(fù)責(zé),防止由于應(yīng)用處理耗時(shí)而阻塞 Netty 的 I/O 線程。

          ?

          Netty線程模型是基于NIO的Reactor模式。

          ?

          gRPC-Java線程模型
          ?

          Netty是基于NIO構(gòu)建的通信框架。

          ?

          在 Java NIO 中最重要的概念就是多路復(fù)用器 Selector,它是 Java NIO 編程的基礎(chǔ)。Selector提供了選擇已經(jīng)就緒的任務(wù)的能力。

          ?

          簡(jiǎn)單來(lái)講,Selector 會(huì)不斷地輪詢注冊(cè)在其上的 Channel,如果某個(gè) Channel 上面有新的 TCP 連接接入、讀和寫事件,這個(gè) Channel 就處于就緒狀態(tài),會(huì)被 Selector 輪詢出來(lái),然后通過(guò)SelectionKey 可以獲取就緒 Channel 的集合,進(jìn)行后續(xù)的 I/O 操作。

          ?

          一般來(lái)說(shuō),一個(gè) I/O 線程會(huì)聚合一個(gè) Selector,一個(gè) Selector 可以同時(shí)注冊(cè) N 個(gè) Channel, 這樣單個(gè)

          I/O 線程就可以同時(shí)并發(fā)處理多個(gè)客戶端連接。

          又由于 I/O 操作是非阻塞的,因此也不會(huì)受限于網(wǎng)絡(luò)速度和對(duì)方端點(diǎn)的處理時(shí)延,可靠性和效率都得到了很大提升。

          gRPC客戶端如何請(qǐng)求服務(wù)端?

          ?

          作為RPC框架,至少有客戶端和服務(wù)端兩個(gè)角色,對(duì)于gRPC而言,客戶端請(qǐng)求服務(wù)端的調(diào)用過(guò)程如圖所示。

          ?

          客戶端請(qǐng)求服務(wù)端

          具體過(guò)程:

          • 【Stub生成】客戶端生成Stub ,通過(guò)Stub發(fā)起 RPC遠(yuǎn)程服務(wù)調(diào)用 ;
          • 【負(fù)載均衡】客戶端獲取服務(wù)端的地址信息(列表),使用默認(rèn)的 LoadBalancer 策略,選擇一個(gè)具體的 gRPC 服務(wù)端進(jìn)行調(diào)用;
          • 【建立鏈接】如果客戶端與服務(wù)端之間沒(méi)有可用的連接,則創(chuàng)建 NettyClientTransport 和 NettyClientHandler,建立 HTTP/2 連接;
          • 【客戶端請(qǐng)求序列化】對(duì)請(qǐng)求使用 PB(Protobuf)序列化,并通過(guò) HTTP/2 Stream 發(fā)送給 gRPC 服務(wù)端;
          • 【服務(wù)端反序列化】服務(wù)端接收到響應(yīng)之后,使用 PB(Protobuf)做反序列化。
          • 【請(qǐng)求響應(yīng)】回調(diào) GrpcFuture 的 set(Response) 方法,喚醒阻塞的客戶端調(diào)用線程,獲取 RPC 響應(yīng)數(shù)據(jù)。

          gRPC性能到底有多強(qiáng)?

          ?

          沒(méi)有對(duì)比就沒(méi)有發(fā)言權(quán)。

          ?

          在不同的操作系統(tǒng),不同請(qǐng)求數(shù)量下,對(duì)gRPC與Rest請(qǐng)求進(jìn)行對(duì)比的結(jié)論如下:

          ?

          官網(wǎng)也給出了權(quán)威性的比對(duì),具體比對(duì)gRPC+ProtoBuf與Http+JSON方式請(qǐng)求的差異。

          ?

          官方性能比對(duì)結(jié)果

          「實(shí)測(cè)結(jié)果顯示GRpc的通訊方案, 性能有32%的提升, 資源占用降低30%左右?!?/strong>

          gRPC-Java 服務(wù)調(diào)用實(shí)戰(zhàn)

          ?

          按照慣例,我們提供一個(gè)簡(jiǎn)單的訂單案例展示gRPC在實(shí)際開(kāi)發(fā)中如何使用。

          該案例在實(shí)際中的意義為:提供一個(gè)報(bào)價(jià)服務(wù),客戶端發(fā)送下單請(qǐng)求到服務(wù)端進(jìn)行報(bào)價(jià),服務(wù)端對(duì)用戶報(bào)價(jià)單進(jìn)行匯總計(jì)算,并提供查詢接口供客戶端查詢。

          主要提供批量下單及查詢用戶訂單能力。

          ?

          流程圖大致如下:

          流程圖

          工程結(jié)構(gòu)如下:

          |==>?grpc-demo??????父級(jí)工程,?管理依賴相關(guān)
          ?????|==>grpc-demo-sdk?????通用jar依賴,生成protobuf對(duì)象與gRPC?Service,供提供方與調(diào)用方使用
          ?????|==>grpc-server-demo??服務(wù)端,提供下單及訂單查詢服務(wù)
          ?????|==>grpc-client-demo??客戶端,負(fù)責(zé)調(diào)用gRPC服務(wù)

          grpc-demo父工程

          ?

          父工程相對(duì)比較簡(jiǎn)單,管理了子工程及依賴版本。

          ?

          <project?xmlns="http://maven.apache.org/POM/4.0.0"
          ?????????xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          ?????????xsi:schemaLocation="http://maven.apache.org/POM/4.0.0?http://maven.apache.org/xsd/maven-4.0.0.xsd">

          ????<modelVersion>4.0.0modelVersion>

          ????<groupId>com.snowalkergroupId>
          ????<artifactId>grpc-demoartifactId>
          ????<version>1.0-SNAPSHOTversion>
          ????<modules>
          ????????<module>grpc-server-demomodule>
          ????????<module>grpc-client-demomodule>
          ????????<module>grpc-demo-sdkmodule>
          ????modules>
          ????<packaging>pompackaging>

          ????<properties>
          ????????<maven.compiler.source>8maven.compiler.source>
          ????????<maven.compiler.target>8maven.compiler.target>
          ????????<grpc-version>1.44.1grpc-version>
          ????properties>

          ????<dependencyManagement>
          ????????<dependencies>
          ????????????<dependency>
          ????????????????<groupId>io.grpcgroupId>
          ????????????????<artifactId>grpc-netty-shadedartifactId>
          ????????????????<version>${grpc-version}version>
          ????????????dependency>
          ????????????<dependency>
          ????????????????<groupId>io.grpcgroupId>
          ????????????????<artifactId>grpc-protobufartifactId>
          ????????????????<version>${grpc-version}version>
          ????????????dependency>
          ????????????<dependency>
          ????????????????<groupId>io.grpcgroupId>
          ????????????????<artifactId>grpc-stubartifactId>
          ????????????????<version>${grpc-version}version>
          ????????????dependency>
          ????????????<dependency>
          ????????????????<artifactId>lombokartifactId>
          ????????????????<groupId>org.projectlombokgroupId>
          ????????????????<version>1.18.22version>
          ????????????????<scope>providedscope>
          ????????????dependency>
          ????????dependencies>
          ????dependencyManagement>
          project>

          grpc-demo-sdk

          ?

          grpc-demo-sdk是較為關(guān)鍵的公共依賴,主要基于proto對(duì)服務(wù)進(jìn)行定義,生成java代碼并打包供服務(wù)提供方與消費(fèi)方使用。

          ?

          pom.xml

          ?

          sdk的pom文件如下:

          ?

          <project?xmlns="http://maven.apache.org/POM/4.0.0"
          ?????????xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          ?????????xsi:schemaLocation="http://maven.apache.org/POM/4.0.0?http://maven.apache.org/xsd/maven-4.0.0.xsd">

          ????<parent>
          ????????<artifactId>grpc-demoartifactId>
          ????????<groupId>com.snowalkergroupId>
          ????????<version>1.0-SNAPSHOTversion>
          ????parent>

          ????<name>grpc-demo-sdkname>
          ????<modelVersion>4.0.0modelVersion>
          ????<artifactId>grpc-demo-sdkartifactId>
          ????<packaging>jarpackaging>

          ????<properties>
          ????????<maven.compiler.source>8maven.compiler.source>
          ????????<maven.compiler.target>8maven.compiler.target>
          ????????<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
          ????properties>

          ????<dependencies>
          ????????<dependency>
          ????????????<groupId>io.grpcgroupId>
          ????????????<artifactId>grpc-netty-shadedartifactId>
          ????????dependency>
          ????????<dependency>
          ????????????<groupId>io.grpcgroupId>
          ????????????<artifactId>grpc-protobufartifactId>
          ????????dependency>
          ????????<dependency>
          ????????????<groupId>io.grpcgroupId>
          ????????????<artifactId>grpc-stubartifactId>
          ????????dependency>
          ????dependencies>

          ????<build>

          ????????<resources>
          ????????????<resource>
          ????????????????<directory>src/main/resourcesdirectory>
          ????????????????<excludes>
          ????????????????????<exclude>**exclude>
          ????????????????excludes>
          ????????????resource>
          ????????????<resource>
          ????????????????<directory>src/main/protodirectory>
          ????????????????<targetPath>prototargetPath>
          ????????????????<filtering>falsefiltering>
          ????????????resource>
          ????????resources>

          ????????<extensions>
          ????????????<extension>
          ????????????????<groupId>kr.motd.mavengroupId>
          ????????????????<artifactId>os-maven-pluginartifactId>
          ????????????????<version>1.6.2version>
          ????????????extension>
          ????????extensions>

          ????????<plugins>
          ????????????<plugin>
          ????????????????<groupId>org.xolstice.maven.pluginsgroupId>
          ????????????????<artifactId>protobuf-maven-pluginartifactId>
          ????????????????<version>0.6.1version>
          ????????????????<configuration>
          ????????????????????<protocArtifact>com.google.protobuf:protoc:3.19.1:exe:${os.detected.classifier}protocArtifact>
          ????????????????????<pluginId>grpc-javapluginId>
          ????????????????????<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.43.1:exe:${os.detected.classifier}pluginArtifact>
          ????????????????configuration>
          ????????????????<executions>
          ????????????????????<execution>
          ????????????????????????<goals>
          ????????????????????????????<goal>compilegoal>
          ????????????????????????????<goal>compile-customgoal>
          ????????????????????????goals>
          ????????????????????execution>
          ????????????????executions>
          ????????????plugin>
          ????????plugins>
          ????build>
          project>
          ?

          重點(diǎn)關(guān)注一下plugin,我們使用protobuf-maven-plugin作為protobuf的編譯工具,有了該插件,我們?cè)趫?zhí)行mvn clean compile命令時(shí)便可以實(shí)現(xiàn)將proto編譯為java代碼的目的。

          同樣,執(zhí)行mvn clean package命令可以實(shí)現(xiàn)將proto編譯為java代碼并打包為jar包的目的。

          可以說(shuō)是極為方便了。

          ?

          編寫proto文件,定義服務(wù)接口

          ?

          編寫OrderService.proto,定義服務(wù)接口,主要定義了查詢用戶訂單,批量下單接口,及對(duì)應(yīng)的各種實(shí)體和枚舉。

          ?
          syntax = "proto3";

          option java_multiple_files = true;
          option java_package = "com.snowalker.grpc.sdk";
          option java_outer_classname = "OrderServiceProto";

          // 訂單服務(wù)IDL定義
          service OrderService {
          // 查詢用戶訂單列表
          rpc queryUserOrders (QueryUserOrderRequest) returns (QueryUserOrderResponse) {
          }

          // 下單
          rpc placeOrder(PlaceOrderRequest) returns (PlaceOrderRequestResponse) {
          }
          }

          // 查詢訂單請(qǐng)求
          message QueryUserOrderRequest {
          int32 userId = 1;
          }

          // 查詢訂單響應(yīng)
          message QueryUserOrderResponse {
          int32 userId = 1;
          string totalPrice = 2;
          repeated UserOrder userOrder = 3;
          }

          // 批量下單請(qǐng)求
          message PlaceOrderRequest {
          int32 userId = 1;
          repeated PlaceUserOrderParam placeUserOrderParam = 2;
          }

          // 批量下單響應(yīng)
          message PlaceOrderRequestResponse {
          int32 userId = 1;
          ResultCode resultCode = 2;
          }

          // 訂單查詢?cè)斍?br>message UserOrder {
          int64 orderId = 1;
          string orderPrice = 2;
          string orderAmount = 3;
          int32 productId = 4;
          }

          // 下單請(qǐng)求詳情
          message PlaceUserOrderParam {
          string orderPrice = 1; // 單價(jià)
          string orderAmount = 2; // 數(shù)量
          int32 productId = 3; // 商品id
          }

          // 結(jié)果枚舉:成功/失敗
          enum ResultCode {
          SUCCESS = 0;
          FAILURE = 1;
          UNKNOWN = 2;
          }

          ?

          如下為protobuf與java、c++對(duì)應(yīng)關(guān)系,

          更多protobuf的使用,請(qǐng)參考官網(wǎng)文檔:https://developers.google.com/protocol-buffers/docs/javatutorial

          ?

          「protobuf」「屬性」

          「C++」「屬性」

          「java」「屬性」

          「?jìng)渥ⅰ?/strong>

          double

          double

          double

          固定8個(gè)字節(jié)

          float

          float

          float

          固定4個(gè)字節(jié)

          int32

          int32

          int32

          使用變長(zhǎng)編碼,對(duì)于負(fù)數(shù)編碼效率較低,如果經(jīng)常使用負(fù)數(shù),建議使用sint32

          int64

          int64

          int64

          使用變長(zhǎng)編碼,對(duì)于負(fù)數(shù)編碼效率較低,如果經(jīng)常使用負(fù)數(shù),建議使用sint64

          uint32

          uint32

          int

          使用變長(zhǎng)編碼

          uint64

          uint64

          long

          使用變長(zhǎng)編碼

          sint32

          int32

          int

          采用zigzag壓縮,對(duì)負(fù)數(shù)編碼效率比int32高

          sint64

          int64

          long

          采用zigzag壓縮,對(duì)負(fù)數(shù)編碼效率比int64高

          fixed32

          uint32

          int

          總是4字節(jié),如果數(shù)據(jù)>2^28,編碼效率高于unit32

          fixed64

          uint64

          long

          總是8字節(jié),如果數(shù)據(jù)>2^56,編碼效率高于unit32

          sfixed32

          int32

          int

          總是4字節(jié)

          sfixed64

          int64

          long

          總是8字節(jié)

          bool

          bool

          boolean


          string

          string

          String

          一個(gè)字符串必須是utf-8編碼或者7-bit的ascii編碼的文本

          bytes

          string

          ByteString

          可能包含任意順序的字節(jié)數(shù)據(jù)

          編譯打包grpc-demo-sdk工程

          ?

          編寫完proto文件后,對(duì)grpc-demo-sdk工程執(zhí)行打包編譯

          ?
          mvn?clean?install?-DskipTests

          編寫服務(wù)端grpc-server-demo

          ?

          接著編寫服務(wù)端

          ?

          pom.xml

          ?

          服務(wù)端pom內(nèi)容如下

          ?

          <project?xmlns="http://maven.apache.org/POM/4.0.0"
          ?????????xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          ?????????xsi:schemaLocation="http://maven.apache.org/POM/4.0.0?http://maven.apache.org/xsd/maven-4.0.0.xsd">

          ????<parent>
          ????????<artifactId>grpc-demoartifactId>
          ????????<groupId>com.snowalkergroupId>
          ????????<version>1.0-SNAPSHOTversion>
          ????parent>
          ????<modelVersion>4.0.0modelVersion>

          ????<artifactId>grpc-server-demoartifactId>

          ????<properties>
          ????????<maven.compiler.source>8maven.compiler.source>
          ????????<maven.compiler.target>8maven.compiler.target>
          ????????<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
          ????properties>

          ????<dependencies>
          ????????<dependency>
          ????????????<artifactId>grpc-demo-sdkartifactId>
          ????????????<groupId>com.snowalkergroupId>
          ????????????<version>1.0-SNAPSHOTversion>
          ????????dependency>
          ????????<dependency>
          ????????????<artifactId>lombokartifactId>
          ????????????<groupId>org.projectlombokgroupId>
          ????????????<scope>providedscope>
          ????????dependency>
          ????dependencies>
          project>

          除了lombok外,其余的依賴由grpc-demo-sdk間接引入。

          編寫OrderServiceImpl實(shí)現(xiàn)核心業(yè)務(wù)邏輯

          ?

          首先編寫OrderServiceImpl,實(shí)現(xiàn)核心的下單與查訂單業(yè)務(wù)邏輯。

          ?
          /**
          ?*?@author?snowalker
          ?*?@version?1.0
          ?*?@date?2022/3/12?23:47
          ?*?@className
          ?*?@desc
          ?*/

          public?class?OrderServiceImpl?extends?OrderServiceGrpc.OrderServiceImplBase?{

          ?private?static?final?Logger?logger?=?Logger.getLogger(OrderServiceImpl.class.getName());

          ?private?static?final?Map>?USER_MEMORY_ORDER_BOOK?=?Maps.newConcurrentMap();

          ?/**
          ??*?

          ??*?查詢用戶訂單列表
          ??*?

          ??*
          ??*?@param?request
          ??*?@param?responseObserver
          ??*/

          ?@Override
          ?public?void?queryUserOrders(QueryUserOrderRequest?request,?StreamObserver?responseObserver)?{
          ??int?userId?=?request.getUserId();
          ??//?查詢訂單
          ??List?orders?=?USER_MEMORY_ORDER_BOOK.getOrDefault(userId,?Lists.newLinkedList());

          ??//?計(jì)算總價(jià)
          ??String?totalPrice?=?calculateTotalPrice(orders);

          ??//?組裝response
          ??QueryUserOrderResponse?queryUserOrderResponse?=?QueryUserOrderResponse.newBuilder()
          ????.setUserId(userId)
          ????.addAllUserOrder(orders)
          ????.setTotalPrice(totalPrice)
          ????.build();

          ??logger.info("[Server]?queryUserOrders,?request:"?+?request.toString()?+?"\n"?+?"response:"?+?queryUserOrderResponse.toString());

          ??//?響應(yīng)
          ??responseObserver.onNext(queryUserOrderResponse);
          ??responseObserver.onCompleted();
          ?}

          ?private?String?calculateTotalPrice(List?orders)?{
          ??Optional?count?=?orders.stream()
          ????.map(order?->?new?BigDecimal(order.getOrderAmount()).multiply(new?BigDecimal(order.getOrderPrice())))
          ????.reduce(BigDecimal::add);
          ??return?count.orElseGet(()?->?BigDecimal.ZERO).toPlainString();
          ?}

          ?/**
          ??*?

          ??*?下單
          ??*?

          ??*
          ??*?@param?request
          ??*?@param?responseObserver
          ??*/

          ?@Override
          ?public?void?placeOrder(PlaceOrderRequest?request,?StreamObserver?responseObserver)?{

          ??ThreadLocalRandom?orderIdGenerator?=?ThreadLocalRandom.current();

          ??PlaceOrderRequestResponse.Builder?placeOrderRequestResponse?=?PlaceOrderRequestResponse.newBuilder();

          ??int?userId?=?request.getUserId();

          ??if?(request.getPlaceUserOrderParamCount()?<=?0)?{
          ???placeOrderRequestResponse.setUserId(userId).setResultCode(ResultCode.FAILURE).build();
          ???responseObserver.onNext(placeOrderRequestResponse.build());
          ???responseObserver.onCompleted();
          ??}

          ??//?獲取用戶訂單列表
          ??LinkedList?userOrderList?=?USER_MEMORY_ORDER_BOOK.getOrDefault(userId,?Lists.newLinkedList());

          ??if?(userOrderList.size()?==?0)?{
          ???USER_MEMORY_ORDER_BOOK.put(userId,?Lists.newLinkedList());
          ??}

          ??int?orderId?=?getOrderId(orderIdGenerator);

          ??//?本次訂單
          ??List?userOrders?=?request.getPlaceUserOrderParamList().stream().map(
          ????param?->?UserOrder.newBuilder()
          ??????.setOrderId(orderId)
          ??????.setOrderAmount(param.getOrderAmount())
          ??????.setOrderPrice(param.getOrderPrice())
          ??????.setProductId(param.getProductId())
          ??????.build()).collect(Collectors.toList());

          ??//?追加訂單列表
          ??userOrderList.addAll(userOrders);

          ??USER_MEMORY_ORDER_BOOK.put(userId,?userOrderList);

          ??//?響應(yīng)
          ??responseObserver.onNext(placeOrderRequestResponse.setUserId(userId).setResultCode(ResultCode.SUCCESS).build());
          ??responseObserver.onCompleted();
          ?}

          ?private?int?getOrderId(ThreadLocalRandom?orderIdGenerator)?{
          ??int?orderId?=?orderIdGenerator.nextInt();
          ??if?(orderId?0)?{
          ???orderId?*=?-1;
          ??}
          ??return?orderId;
          ?}
          }

          這里的代碼是完整的代碼,讀者可以自行復(fù)制并直接使用,簡(jiǎn)單解釋下代碼:

          1. placeOrder為下單服務(wù),核心邏輯就是解析用戶下單請(qǐng)求PlaceOrderRequest,將用戶訂單增量添加到內(nèi)存訂單簿USER_MEMORY_ORDER_BOOK中。
            1. 核心的數(shù)據(jù)結(jié)構(gòu)為:*Map>*,在實(shí)戰(zhàn)中,通用會(huì)持久化訂單到redis、MySQL、RocksDB等存儲(chǔ)設(shè)施中;
          2. queryUserOrders為查詢訂單服務(wù),核心邏輯為解析用戶查詢訂單請(qǐng)求QueryUserOrderRequest,取出用戶id(userId),并在訂單簿中匹配當(dāng)前用戶的訂單列表。

          服務(wù)端啟動(dòng)類OrderServerBoot

          ?

          有了服務(wù)端業(yè)務(wù)代碼之后,重點(diǎn)關(guān)注一下服務(wù)端啟動(dòng)類的編寫。

          ?
          /**
          ?*?@author?snowalker
          ?*?@version?1.0
          ?*?@date?2022/3/12?23:46
          ?*?@desc?服務(wù)端啟動(dòng)類
          ?*/

          public?class?OrderServerBoot?{

          ?private?static?final?Logger?logger?=?Logger.getLogger(OrderServerBoot.class.getName());

          ?private?Server?server;

          ?@SneakyThrows
          ?private?void?startServer()?{
          ??int?serverPort?=?10880;
          ??server?=?ServerBuilder.forPort(serverPort)
          ????.addService(new?OrderServiceImpl())
          ????.build();
          ??server.start();

          ??logger.info("OrderServerBoot?started,?listening?on:"?+?serverPort);

          ??//?優(yōu)雅停機(jī)
          ??addGracefulShowdownHook();
          ?}

          ?private?void?addGracefulShowdownHook()?{
          ??Runtime.getRuntime().addShutdownHook(new?Thread(()?->?{
          ???//?Use?stderr?here?since?the?logger?may?have?been?reset?by?its?JVM?shutdown?hook.
          ???System.err.println("***?shutting?down?gRPC?server?since?JVM?is?shutting?down");
          ???OrderServerBoot.this.stop();
          ???System.err.println("***?server?shut?down");
          ??}));
          ?}

          ?/**
          ??*?服務(wù)關(guān)閉
          ??*/

          ?private?void?stop()?{
          ??if?(server?!=?null)?{
          ???server.shutdown();
          ??}
          ?}

          ?/**
          ??*?由于 grpc 庫(kù)使用守護(hù)線程,因此在主線程上等待終止。
          ??*/

          ?private?void?blockUntilShutdown()?throws?InterruptedException?{
          ??if?(server?!=?null)?{
          ???server.awaitTermination();
          ??}
          ?}

          ?@SneakyThrows
          ?public?static?void?main(String[]?args)?{
          ??OrderServerBoot?boot?=?new?OrderServerBoot();
          ??//?啟動(dòng)服務(wù)
          ??boot.startServer();
          ??//?主線程等待終止
          ??boot.blockUntilShutdown();
          ?}
          }

          解釋下代碼:

          • 核心邏輯為main方法,首先定義OrderServerBoot,通過(guò)startServer()啟動(dòng)服務(wù),并通過(guò)blockUntilShutdown()讓主線程等待終止。
          • 「startServer()」 方法核心邏輯,啟動(dòng)一個(gè)服務(wù)端進(jìn)程并綁定到對(duì)應(yīng)的端口,這里使用10880,并添加優(yōu)雅停機(jī)鉤子;
          • 「stop()」 邏輯為服務(wù)關(guān)閉邏輯;
          • 「blockUntilShutdown()」 :由于grpc使用守護(hù)線程,因此需要在主線程上等待終止。

          編寫客戶端grpc-client-demo

          ?

          有了服務(wù)端,我們接著看下客戶端工程的編寫。

          ?

          pom.xml

          ?

          客戶端pom如下

          ?

          <project?xmlns="http://maven.apache.org/POM/4.0.0"
          ?????????xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          ?????????xsi:schemaLocation="http://maven.apache.org/POM/4.0.0?http://maven.apache.org/xsd/maven-4.0.0.xsd">

          ????<parent>
          ????????<artifactId>grpc-demoartifactId>
          ????????<groupId>com.snowalkergroupId>
          ????????<version>1.0-SNAPSHOTversion>
          ????parent>
          ????<modelVersion>4.0.0modelVersion>

          ????<artifactId>grpc-client-demoartifactId>

          ????<properties>
          ????????<maven.compiler.source>8maven.compiler.source>
          ????????<maven.compiler.target>8maven.compiler.target>
          ????????<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
          ????properties>


          ????<dependencies>
          ????????<dependency>
          ????????????<artifactId>grpc-demo-sdkartifactId>
          ????????????<groupId>com.snowalkergroupId>
          ????????????<version>1.0-SNAPSHOTversion>
          ????????dependency>
          ????????<dependency>
          ????????????<artifactId>lombokartifactId>
          ????????????<groupId>org.projectlombokgroupId>
          ????????????<scope>providedscope>
          ????????dependency>
          ????dependencies>
          project>

          與服務(wù)端相同,除了lombok外,其余的依賴由grpc-demo-sdk間接引入。

          編寫客戶端服務(wù)調(diào)用代理OrderClientAgent

          ?

          客戶端調(diào)用遠(yuǎn)程服務(wù),需要借助proto生成的stub樁,作為客戶端而言,常常會(huì)對(duì)該stub進(jìn)行包裝,這里我們通過(guò)一個(gè)OrderClientAgent作為stub的包裝類。

          ?
          public?class?OrderClientAgent?{

          ?private?static?final?Logger?logger?=?Logger.getLogger(OrderClientAgent.class.getName());

          ?private?final?ManagedChannel?channel;

          ?//?客戶端請(qǐng)求服務(wù)端的樁
          ?private?final?OrderServiceGrpc.OrderServiceBlockingStub?orderServiceBlockingStub;

          ?public?OrderClientAgent(String?host,?int?port)?{
          ??this(ManagedChannelBuilder.forAddress(host,?port)
          ????//使用非安全機(jī)制傳輸,默認(rèn)情況下,通道是安全的(通過(guò)SSLTLS)
          ????.usePlaintext()
          ????.build());
          ?}

          ?OrderClientAgent(ManagedChannel?channel)?{
          ??this.channel?=?channel;
          ??orderServiceBlockingStub?=?OrderServiceGrpc.newBlockingStub(channel);
          ?}

          ?public?void?shutdown()?throws?InterruptedException?{
          ??channel.shutdown().awaitTermination(5,?TimeUnit.SECONDS);
          ?}

          ?/**
          ??*?下單
          ??*?@param?request
          ??*/

          ?public?PlaceOrderRequestResponse?placeOrder(PlaceOrderRequest?request)?{
          ??logger.info("client?placeOrder?start.?request:"?+?request.toString());
          ??PlaceOrderRequestResponse?placeOrderRequestResponse;
          ??try?{
          ???placeOrderRequestResponse?=?orderServiceBlockingStub.placeOrder(request);
          ??}?catch?(Exception?e)?{
          ???e.printStackTrace();
          ???return?null;
          ??}
          ??return?placeOrderRequestResponse;
          ?}

          ?/**
          ??*?訂單查詢
          ??*?@param?request
          ??*?@return
          ??*/

          ?public?QueryUserOrderResponse?queryOrders(QueryUserOrderRequest?request)?{
          ??logger.info("client?queryOrders?start.?request:"?+?request.toString());
          ??QueryUserOrderResponse?queryUserOrderResponse;
          ??try?{
          ???queryUserOrderResponse?=?orderServiceBlockingStub.queryUserOrders(request);
          ??}?catch?(Exception?e)?{
          ???e.printStackTrace();
          ???return?null;
          ??}
          ??return?queryUserOrderResponse;
          ?}
          }

          簡(jiǎn)單解釋下代碼:

          • 通過(guò)構(gòu)造方法傳入主機(jī)名,服務(wù)端端口,構(gòu)造客戶端與服務(wù)端間的鏈接通過(guò)ManagedChannel
          • 通過(guò)OrderServiceGrpc.newBlockingStub(channel)生成客戶端訪問(wèn)的stub實(shí)例,這里使用的是阻塞型Stub,即同步等待服務(wù)端返回所有結(jié)果;
          • placeOrder方法通過(guò)stub訪問(wèn)服務(wù)端的下單服務(wù);
          • queryOrders方法通過(guò)stub訪問(wèn)服務(wù)端的查詢訂單服務(wù)。

          編寫客戶端啟動(dòng)類

          /**
          ?*?@author?snowalker
          ?*?@version?1.0
          ?*?@date?2022/3/12?23:56
          ?*?@desc?客戶端啟動(dòng)類
          ?*/

          public?class?OrderClientBoot?{

          ???private?static?final?Logger?logger?=?Logger.getLogger(OrderClientBoot.class.getName());

          ???@SneakyThrows
          ???public?static?void?main(String[]?args)?{
          ??????int?port?=?10880;
          ??????OrderClientAgent?orderClientAgent?=?new?OrderClientAgent("127.0.0.1",?port);

          ??????try?{
          ?????????int?userId?=?10086;

          ?????????//?下單
          ?????????doPlaceOrder(orderClientAgent,?userId);

          ?????????//?查訂單
          ?????????doQueryOrder(orderClientAgent,?userId);

          ??????}?finally?{
          ?????????orderClientAgent.shutdown();
          ??????}
          ???}

          ???private?static?void?doQueryOrder(OrderClientAgent?orderClientAgent,?int?userId)?{
          ??????QueryUserOrderRequest?queryUserOrderRequest?=?QueryUserOrderRequest.newBuilder()
          ????????????.setUserId(userId)
          ????????????.buildPartial();
          ??????QueryUserOrderResponse?queryUserOrderResponse?=?orderClientAgent.queryOrders(queryUserOrderRequest);
          ??????logger.info("client?queryOrders?end.?response:"?+?queryUserOrderResponse.toString());
          ???}

          ???private?static?void?doPlaceOrder(OrderClientAgent?orderClientAgent,?int?userId)?{

          ??????PlaceUserOrderParam?orderParam0?=?PlaceUserOrderParam.newBuilder()
          ????????????.setProductId(1)
          ????????????.setOrderAmount("15.00")
          ????????????.setOrderPrice("12.50")
          ????????????.build();

          ??????PlaceUserOrderParam?orderParam1?=?PlaceUserOrderParam.newBuilder()
          ????????????.setProductId(2)
          ????????????.setOrderAmount("2.00")
          ????????????.setOrderPrice("10.00")
          ????????????.build();

          ??????PlaceOrderRequest?placeOrderRequest?=?PlaceOrderRequest.newBuilder()
          ????????????.setUserId(userId)
          ????????????.addAllPlaceUserOrderParam(Lists.newArrayList(orderParam0,?orderParam1))
          ????????????.buildPartial();

          ??????PlaceOrderRequestResponse?placeOrderRequestResponse?=?orderClientAgent.placeOrder(placeOrderRequest);
          ??????logger.info("client?placeOrder?end.?response:"?+?placeOrderRequestResponse.toString()?+?",resultCode:"?+?placeOrderRequestResponse.getResultCode());
          ???}
          }

          重點(diǎn)關(guān)注main方法:

          • 聲明服務(wù)端端口,這里注意務(wù)必與服務(wù)端暴露服務(wù)端口保持一致;
          • 通過(guò)構(gòu)造方法創(chuàng)建客戶端訪問(wèn)服務(wù)端的agent實(shí)例,即上面提到的OrderClientAgent;
          • 通過(guò)實(shí)例化的OrderClientAgent執(zhí)行下單、查詢訂單操作
          • 調(diào)用完成后,關(guān)閉OrderClientAgent,關(guān)閉客戶端與服務(wù)端之間的鏈接。
          • 「實(shí)際生產(chǎn)中,客戶端往往會(huì)與服務(wù)端保持鏈接開(kāi)啟,而不會(huì)頻繁創(chuàng)建、關(guān)閉服務(wù)。」

          測(cè)試

          ?

          sdk、客戶端、服務(wù)端均編寫完畢,我們啟動(dòng)服務(wù)進(jìn)行測(cè)試。

          ?

          首先編譯打包sdk

          在grpc-demo-sdk根目錄下執(zhí)行:

          mvn?clean?install?-DskipTests

          啟動(dòng)服務(wù)端

          運(yùn)行OrderServerBoot的main方法,日志打印如下:

          三月?13,?2022?10:50:29?上午?OrderServerBoot?startServer
          信息:?OrderServerBoot?started,?listening?on:10880

          啟動(dòng)客戶端

          運(yùn)行OrderClientBoot的main方法,啟動(dòng)客戶端并發(fā)起服務(wù)調(diào)用

          ?

          首先進(jìn)行下單:

          ?
          三月?13,?2022?10:54:57?上午?agent.OrderClientAgent?placeOrder
          信息:?client?placeOrder?start.?request:userId:?10086
          placeUserOrderParam?{
          ??orderPrice:?"12.50"
          ??orderAmount:?"15.00"
          ??productId:?1
          }
          placeUserOrderParam?{
          ??orderPrice:?"10.00"
          ??orderAmount:?"2.00"
          ??productId:?2
          }

          三月?13,?2022?10:54:58?上午?OrderClientBoot?doPlaceOrder
          信息:?client?placeOrder?end.?response:userId:?10086
          ,resultCode:SUCCESS

          下單成功,接著發(fā)起查詢訂單操作:

          三月?13,?2022?12:20:55?下午?agent.OrderClientAgent?queryOrders
          信息:?client?queryOrders?start.?request:userId:?10086

          三月?13,?2022?12:20:55?下午?OrderClientBoot?doQueryOrder
          信息:?client?queryOrders?end.?response:userId:?10086
          totalPrice:?"207.5000"
          userOrder?{
          ??orderId:?510807688
          ??orderPrice:?"12.50"
          ??orderAmount:?"15.00"
          ??productId:?1
          }
          userOrder?{
          ??orderId:?510807688
          ??orderPrice:?"10.00"
          ??orderAmount:?"2.00"
          ??productId:?2
          }

          可以看到,下單成功,且通過(guò)查詢訂單調(diào)用,將用戶10086下的兩個(gè)訂單獲取到了。

          觀察服務(wù)端日志

          ?

          服務(wù)端日志打印如下

          ?
          三月?13,?2022?12:20:55?下午?service.OrderServiceImpl?queryUserOrders
          信息:?[Server]?queryUserOrders,?request:userId:?10086

          response:userId:?10086
          totalPrice:?"207.5000"
          userOrder?{
          ??orderId:?510807688
          ??orderPrice:?"12.50"
          ??orderAmount:?"15.00"
          ??productId:?1
          }
          userOrder?{
          ??orderId:?510807688
          ??orderPrice:?"10.00"
          ??orderAmount:?"2.00"
          ??productId:?2
          }

          服務(wù)端完成下單之后,對(duì)用戶訂單總價(jià)值進(jìn)行計(jì)算

          ?

          totalPrice = 12.5*15 + 10 *2 = 207.5

          ?

          小結(jié)

          本文我們對(duì)gRPC進(jìn)行了如下介紹:

          • gRPC特性介紹
          • gRPC-java線程模型
          • gRPC客戶端請(qǐng)求服務(wù)端方式
          • gRPC與REST性能比對(duì)

          并通過(guò)一個(gè)完整的demo展示了基于gRPC實(shí)現(xiàn)的報(bào)價(jià)服務(wù),全景展示了gRPC在實(shí)戰(zhàn)中如何進(jìn)行使用。

          到此我們對(duì)gRPC應(yīng)當(dāng)有了大致的了解和認(rèn)知,后續(xù)我們將繼續(xù)從入門到放棄的學(xué)習(xí)之路。

          預(yù)告:接下來(lái)將對(duì)gRPC的底層機(jī)制進(jìn)行講解,并會(huì)為我們的報(bào)價(jià)服務(wù)添加服務(wù)發(fā)現(xiàn)能力,整合Nacos提供服務(wù)注冊(cè)與發(fā)現(xiàn),降低客戶端與服務(wù)端之間的耦合,敬請(qǐng)期待。


          瀏覽 115
          點(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 | 亚洲欧洲中文 | 青草视频网站 | 国产高清视频在线观看 | 亚洲无码精品视频 |