• 开始食用grpc(之一)


    开始食用grpc(之一)

    转载请注明出处:https://www.cnblogs.com/funnyzpc/p/9501353.html

    ```

       记一次和一锅们压马路,路过一咖啡厅(某巴克),随口就问随行的锅门:你能从那咖啡厅看到什么?

         当时的那家某巴克处于闹市,也正值周末,屋外屋内喝咖啡的人几近乎十分的安静,使用电脑的,刷手机的、做作业的。。。而且大都是年轻人和中年人。

       锅门撂了句:一群屌丝呗 (;¬_¬) 

      。。。白了他一眼(¬_¬)

         ( ...其实想教唆他进去看看美女,歇歇脚来着 ๑乛◡乛๑ )

      .......

         许久之后,也就是最近看到诗人余秀华的一句话后忽有所感,原句是:

        “反正是背负慢慢凋残的孤独,耀眼的孤独,义无反顾的孤独

      这才明白他们是在消费孤独,也为孤独所消费; 他们是,周围的人是,还有 ( ∙̆ .̯ ∙̆ )

      那~ 孤独的结果是什么呢 ?

    ```

      这次讲下大系统通讯必备的一项组件:rpc,rpc有很多 如 dubbo、thirft、feign、motan、grpc 等~,这其中有字符串方式的也有二进制流方式的;整体来说二进制方式的一般会较字符串方式的快许多,字符形式的的慢,但是简单;而二进制方式的 序列化和跨平台较为麻烦些;我个人选取rpc时一般综合考虑一下几点:

      A>传输方式是否是二进制

      B>是否长期支持

      C>是否支持跨平台,开源组件是否丰富

      C+>是否支持异步调用

      D>有无明显的bug或缺点

      E>维护和开发是否有好

        综合下来,个人坚定之选择grpc,这个初出茅庐(2015年发布)的东东,十分强大:

          >> http2流方式

      >> 自带异步特性

      >> 跨平台(支持11种语言)

      >> 无需zookeeper这类单独的服务中心

      >> 对开发人员比较友好

          >> 服务调用方可设置请求头

    当然缺点也是存在的:需要单独写proto文件(生成目标语言的一套语法定义)、变量为null时会赋予默认初始值、链式调用(还好调用接口较为单一,只是语法较为怪异)...

      如果您在意以上缺点,可绕过本文哈~

          ok,现在开始我开始讲grpc,内容大致有四:

        A->grpc的简单配置 (本节)

        A>简单grpc编写 (本节)

        B>复杂grpc proto服务文件编写 (本节)

        C>双向流式调用方法及注意事项 (下一节)

        D>grpc安全问题及拦截器 (下一节)

    grpc的配置:

           这里我的工程是基于springboot,同时为简化开发起见,我使用 grpc-spring-boot-starter ,开始之前先感谢这位开发者为简化grpc的java平台简化了太多的开发,同时也为springcloud融合做了太多共享,非常感谢~! 

       这里,首先得准备三个springboot模块,这三个模块包含:grpc proto3文件生成模块、grpc 客户端、grpc 服务端,我的工程结构大致是这样子的(工程是多模块的):

    这里面的三个模块一看就懂,就不细讲啦~,准备好这三个模块后,依次配置依赖包及参数:

    服务端(preview-grpc-server):

      pom.xml中依赖包配置>

            <dependency>
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-autoconfigure</artifactId>
             </dependency>
             <dependency>
                 <groupId>net.devh</groupId>
                 <artifactId>grpc-client-spring-boot-autoconfigure</artifactId>
                 <version>RELEASE</version>
             </dependency>
        <!-- 由于我的工程是多模块的,若不作为jar包引入,也可以将preview-grpc-lib中的java文件拷贝到当前工程内也可 -->
         <dependency>
         <groupId>com.github.carvechris</groupId>
         <artifactId>preview-grpc-lib</artifactId>
         <version>1.0-SNAPSHOT</version>
         </dependency>
     

      配置文件yml(如果是properties文件也可参照此配置)

    1 grpc:
    2   server:
    3     port: 2804
    4 
    5 spring:
    6     application:
    7         name: preview-grpc-server

    (注意:一定要定义应用名称,在调用的时候会用到应用名称的,这里是:preview-grpc-server)

     客户端(preview-grpc-client):

      pom.xml文件依赖包配置>

    <dependency>
            <groupId>net.devh</groupId>
            <artifactId>grpc-client-spring-boot-starter</artifactId>
            <version>1.4.0.RELEASE</version>
    </dependency>

    <!-- 由于我的工程是多模块的,若不作为jar包引入,也可以将preview-grpc-lib中的java文件拷贝到当前工程内也可 -->
    <dependency>
     <groupId>com.github.carvechris</groupId>
     <artifactId>preview-grpc-lib</artifactId>
     <version>1.0-SNAPSHOT</version>
    </dependency>
     

      yml配置文件参数:

    1 grpc:
    2   client:
    3     preview-grpc-server:
    4       host:
    5         - 127.0.0.1
    6       port:
    7         - 2804
    8       enableKeepAlive: true
    9       keepAliveWithoutCalls: true

    proto文件生成模块(preview-grpc-lib)配置:

      pom.xml文件依赖包配置:

     1 <!--依赖配置-->
     2 <dependencies>
     3         <dependency>
     4             <groupId>io.grpc</groupId>
     5             <artifactId>grpc-netty</artifactId>
     6             <version>${grpc.version}</version>
     7         </dependency>
     8         <dependency>
     9             <groupId>io.grpc</groupId>
    10             <artifactId>grpc-protobuf</artifactId>
    11             <version>${grpc.version}</version>
    12         </dependency>
    13         <dependency>
    14             <groupId>io.grpc</groupId>
    15             <artifactId>grpc-stub</artifactId>
    16             <version>${grpc.version}</version>
    17         </dependency>
    18  </dependencies>
    19 
    20 <!--proto3文件生成java代码插件配置-->
    21     <build>
    22         <extensions>
    23             <extension>
    24                 <groupId>kr.motd.maven</groupId>
    25                 <artifactId>os-maven-plugin</artifactId>
    26                 <version>${os.plugin.version}</version>
    27             </extension>
    28         </extensions>
    29         <plugins>
    30             <plugin>
    31                 <groupId>org.xolstice.maven.plugins</groupId>
    32                 <artifactId>protobuf-maven-plugin</artifactId>
    33                 <version>${protobuf.plugin.version}</version>
    34                 <configuration>
    35                     <protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}</protocArtifact>
    36                     <pluginId>grpc-java</pluginId>
    37                     <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
    38                 </configuration>
    39             </plugin>
    40         </plugins>
    41     </build>

      配置完成,这里需要特别说明一下:server模块和client模块的web服务各占一个端口,另外,server模块还会给grpc单独分配一个端口,就我的preview-grpc-server来说:

        服务名称(name)是:preview-grpc-server

        服务占用的端口是:2804

      切记,不论是web服务还是grpc服务的端口都不能重复,同时一定要理清楚web服务和grpc服务所占用的端口和ip

    简单grpc服务(helloworld.proto)编写

      这里我先展示下我的生成模块的大致样子

      需要说明的是:编写的proto文件均在proto目录下java目录下是proto文件生成的java代码,这里的java文件是从target目录总复制到java目录下的,包名一定要与proto里面声明的包名一致!

      java代码生成模块proto3服务文件(helloworld.proto)的编写

     1 syntax = "proto3";
     2 
     3 // 是否拆分类文件
     4 option java_multiple_files = true;
     5 // 生成的文件所在的包
     6 option java_package = "com.funnyzpc.xxx.grpc.lib.hello";
     7 //  输出类主文件(此配置可选)
     8 option java_outer_classname = "HelloWorldProto";
     9 
    10 // 定义一个服务
    11 service Simple {
    12     // Sends a greeting
    13     rpc SayHello (HelloRequest) returns (HelloReply) {
    14     }
    15 }
    16 
    17 // 请求体定义
    18 message HelloRequest {
    19     string name = 1;
    20 }
    21 
    22 // 响应体定义
    23 message HelloReply {
    24     string message = 1;
    25 }

      现在开始使用idea提供的快捷功能生成客户端和服务端java文件(当然也可以使用mvn命令手动生成)

      将生成的java文件复制到应用目录

      (注意:复制后一定要清理target目录,不然文件重复会报错!)

      在客户端(preview-grpc-client)编写一个grpc服务请求类(GrpcSimpleService.java):

     1 @Service
     2 public class GrpcSimpleService {
     3 
     4     @GrpcClient("preview-grpc-server")
     5     private Channel serverChannel;
     6 
     7     public String sendMessage(String name) {
     8         SimpleGrpc.SimpleBlockingStub stub = SimpleGrpc.newBlockingStub(serverChannel);
     9         HelloReply response = stub.sayHello(HelloRequest.newBuilder().setName(name).build());
    10         return response.getMessage();
    11     }
    12 }

      在服务端(preview-grpc-server)编写对应的grpc的服务类:

     1 /**
     2  * 简单grpc服务类
     3  */
     4 @GrpcService(SimpleGrpc.class)
     5 public class GrpcServerService extends SimpleGrpc.SimpleImplBase {
     6     private  static final Logger LOG=LoggerFactory.getLogger(GrpcServerService.class);
     7 
     8    @Override
     9     public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
    10         HelloReply reply = HelloReply.newBuilder().setMessage("Hello ===> " + req.getName()).build();
    11         responseObserver.onNext(reply);
    12         responseObserver.onCompleted();
    13     }
    14 
    15 }

      上面的@GrpcService是grpc组件的注解,注解中必须声明所使用的grpc(生成的类中的)服务类,同时还可以声明所使用的拦截器(可选) 

     OK,现在添加一个控制器(在preview-grpc-client中编写一个控制器),试试看

      完美,。。。可能有人proto文件一知半解,接下来进入下一节。

    复杂grpc proto服务文件编写:

       首先,我先推荐两个官方网站,若能理解官网内容,可绕过本节

         grpc java平台api及样例>

          https://grpc.io/docs/quickstart/java.html

             protocol buffers,proto3文件语法>

          https://developers.google.com/protocol-buffers/docs/proto3?hl=zh-cn

      一般在跨应用调用时,所传递的参数有时候为复杂对象,比如这样{page_no:1,page_size:20,data:{name:XXX,type:2}},这里就写好的复杂层级对象讲解下

    (MultiObject.proto)

     1 syntax = "proto3";
     2 
     3 // option java_multiple_files = true;
     4 option java_package = "com.github.carvechris.security.grpc.lib.multi";
     5 
     6 service MultiObjService{
     7     rpc queryObj (MultiObjReq) returns (MultiObjResp) {
     8 
     9     }
    10 
    11 }
    12 
    13 message MultiObjReq{
    14     int32 page_no=1;
    15     int32 page_size=2;
    16     MultiObjDataReq data=3;
    17 }
    18 
    19 message MultiObjDataReq{
    20     string name=1;
    21     int32 type=2;
    22 }
    23 
    24 message MultiObjResp{
    25     string req_str=1;
    26     MultiObjFirstResp first=2;
    27 }
    28 message MultiObjFirstResp{
    29     string f_content=1;
    30     MultiObjNextResp next=2;
    31 
    32 }
    33 message MultiObjNextResp{
    34     string n_content =1;
    35 }

    文件中定义一个服务 必须以关键字 service 开始,上面的 MultiObjReq 与 MultiObjResp 分别为服务的请求和响应对象,这两个对象在生成java文件后,每个请求对象都是一个单独的类(可在一个java中也可不在,若不在需要定义:option java_multiple_files = true;),不管是请求对象还是响应对象,都需要单独声明这个对象以及对象中的变量类型及所处的位置,就像这样

    1 message MultiObjReq{
    2     int32 page_no=1;
    3     int32 page_size=2;
    4     MultiObjDataReq data=3;
    5 }

    自定义类型需要在单独定义,比如"MultiObjDataReq";在上面这个例子中,定义的请求对象MultiObjReq的第一个字段为 page_no,第一个为page_size,第三个为定义的一个对象,每个参数开始需标明当前字段类型,如果这个字段是自定义类型时无需定义数据类型(但是一定要有参数序号,当然也可以定义一个map类型);另外,通用字段类型同go语言的数据类型(参照以上链接);注意,请求或响应对象定义时必须以关键字message开始。

    另外,请注意,如果某个字段是个列表(java中的List),需要在字段或者对象前添加关键字 repeated ,这样:

    //返回体数据定义
    message GrpcResp {
    
        string sign=3;
    
        string msg=4;
    
        repeated datas detail=5;
    }
    
    message datas{
    
        uint64 id=1;
    
        string address=2;
    
        double isadmin=3;
    }

      现在展示上面的MultiObject.proto文件 所编写的客户端和服务端类:

      客户端(preview-grpc-client):

      (GrpcMultiObjClientService.java)

     1 @Service
     2 public class GrpcMultiObjClientService {
     3 
     4     @GrpcClient("preview-grpc-service")
     5     private Channel serverChannel;
     6 
     7     public  Object testMultiObj() {
     8 
     9         MultiObject.MultiObjDataReq reqData=MultiObject.MultiObjDataReq.newBuilder()
    10                 .setName("queryName")
    11                 .setType(33)
    12                 .build();
    13 
    14         MultiObject.MultiObjReq req=MultiObject.MultiObjReq.newBuilder()
    15                 .setPageNo(11)
    16                 .setPageSize(22)
    17                 .setData(reqData)
    18                 .build();
    19 
    20         MultiObjServiceGrpc.MultiObjServiceBlockingStub stb=MultiObjServiceGrpc.newBlockingStub(serverChannel);
    21         MultiObject.MultiObjResp resp=stb.queryObj(req);
    22 
    23         Map<String,Object> reMap=new HashMap<>();
    24         reMap.put("getFContent",resp.getFirst().getFContent());
    25         reMap.put("getNContent",resp.getFirst().getNext().getNContent());
    26         reMap.put("getReqStr",resp.getReqStr());
    27         return reMap;
    28     }
    29 
    30 }

      服务端(preview-grpc-server):

      (GrpcMultiObjService.java)

     1 @GrpcService(MultiObjServiceGrpc.class)
     2 public class GrpcMultiObjService extends MultiObjServiceGrpc.MultiObjServiceImplBase{
     3     private static final Logger LOG=LoggerFactory.getLogger(GrpcMultiObjService.class);
     4 
     5     @Override
     6     public void queryObj(MultiObject.MultiObjReq request,StreamObserver<MultiObject.MultiObjResp> responseObserver) {
     7 
     8         LOG.info("MultiObjServiceGrpc>start");
     9         Map<String,Object> reqData=new HashMap<String,Object>();
    10         reqData.put("getPageNo",request.getPageNo());
    11         reqData.put("getPageSize",request.getPageSize());
    12         reqData.put("getName",request.getData().getName());
    13         reqData.put("getType",request.getData().getType());
    14 
    15         MultiObject.MultiObjNextResp next=MultiObject.MultiObjNextResp.newBuilder().setNContent("n_content").build();
    16         MultiObject.MultiObjFirstResp first=MultiObject.MultiObjFirstResp.newBuilder().setFContent("f_content").setNext(next).build();
    17         MultiObject.MultiObjResp resp=MultiObject.MultiObjResp.newBuilder().setReqStr(JSON.toJSONString(reqData)).setFirst(first).build();
    18 
    19         responseObserver.onNext(resp);
    20         responseObserver.onCompleted();
    21         LOG.info("MultiObjServiceGrpc>end");
    22     }
    23 
    24 }

    现在是 2018-08-26 02:13:42 

      由于在双向流编写及测试环节碰到些问题,耽搁了许久,此次会将双向流和安全及拦截器放在下一篇讲,各位,晚安 (=。=)  

  • 相关阅读:
    前端js(一)
    前端CSS
    前端HTML
    视图等
    Navicat使用
    查询语句
    SpringBoot不能直接访问templates下的静态资源
    Mybatis什么时候用${}
    thymeleaf使用restul风格URL
    SpringBoot使用PageHelper
  • 原文地址:https://www.cnblogs.com/funnyzpc/p/9501353.html
Copyright © 2020-2023  润新知