gRPC学习笔记

gRPC框架学习笔记

一.什么是RPC

1.简介:

RPC:Remote Procedure Call,远程过程调用。简单来说就是两个进程之间的数据交互。
正常服务端的接口服务是提供给用户端(在Web开发中就是浏览器)或者自身调用的,也就是本地过程调用。
和本地过程调用相对的就是:假如两个服务端不在一个进程内怎么进行数据交互?使用RPC。
尤其是现在微服务的大量实践,服务与服务之间的调用不可避免,RPC更显得尤为重要。

2.原理:

计算机的世界中不管使用哪种技术,核心都是对数据的操作。RPC不过是将数据的操作垮了一个维度而已。
解决的问题本质上只是数据在不同进程间的传输。我们所说的RPC一般是指在传输层使用TCP协议进行的数据交互,
也有很多基于HTTP的成熟框架。

在这里插入图片描述

上图描述了一个RPC的完整调用流程:
1:client向client stub发起方法调用请求。
2:client stub接收到请求后,将方法名,请求参数等信息进行编码序列化。
3:client stub通过配置的ip和端口使用socket通过网络向远程服务器server发起请求。
4:远程服务器server接收到请求,解码反序列化请求信息。
5:server将请求信息交给server stub,server stub找到对应的本地真实方法实现。
6:本地方法处理调用请求并将返回的数据交给server stub。
7:server stub 将数据编码序列化交给操作系统内核,使用socket将数据返回。
8:client端socket接收到远程服务器的返回信息。
9:client stub将信息进行解码反序列化。
10:client收到远程服务器返回的信息。

上图中有一个stub(存根)的概念。
stub负责接收本地方法调用,并将它们委托给各自的具体实现对象。
server端stub又被称为skeleton(骨架)。可以理解为代理类。而实际上基于Java的RPC框架stub基本上也都是使用动态代理。
我们所说的client端和server端在RPC中一般也都是相对的概念。
而所谓的RPC框架也就是封装了上述流程中2-9的过程,让开发者调用远程方法就像调用本地方法一样。

二.常用的RPC框架选型

Dubbo

  • 阿里开源的基于TCP的RPC框架,基本上是国内生产环境应用最广的开发框架了。使用zookeeper做服务的注册与发现,使用Netty做网络通信。遗憾的是不能跨语言,目前只支持Java。

Thrift

  • Facebook开源的跨语言的RPC框架,通过IDL来定义RPC的接口和数据类型,使用thrift编译器生成不同语言的实现。据说是目前性能最好的RPC框架,只是暂没使用过。

gRPC

  • 这个是我们今天要聊的重点。gRPC是Google的开源产品,是跨语言的通用型RPC框架,使用Go语言编写。 Java语言的应用同样使用了Netty做网络通信,Go采用了Goroutine做网络通信。序列化方式采用了Google自己开源的Protobuf。请求的调用和返回使用HTTP2的Stream。

SpringCloud

  • SpringCloud并不能算一个RPC框架,它是Spring家族中一个微服务治理的解决方案,是一系列框架的集合。但在这个方案中,微服务之间的通信使用基于HTTP的Restful API,使用Eureka或Consul做服务注册与发现,使用声明式客户端Feign做服务的远程调用。这一系列的功能整合起来构成了一套完整的远程服务调用。

如何选择

如果公司项目使用Java并不牵扯到跨语言,且规模并没有大到难以治理,我推荐Dubbo。如果项目规模大,服务调用错综复杂,我推荐SpringCloud。

如果牵扯到跨语言,我推荐gRPC,这也是目前我司的选择。即使Thrift性能是gRPC的2倍,但没办法,它有个好爹,现在我们的开发环境考虑最多的还是生态,不得不向Google爸爸臣服。

三.gRPC原理

在这里插入图片描述

一个RPC框架必须有两个基础的组成部分:数据的序列化和进程数据通信的交互方式。

对于序列化gRPC采用了自家公司开源的Protobuf。什么是Protobuf?先看一句网络上 大部分的解释:

Google Protocol Buffer(简称 Protobuf)是一种轻便高效的结构化数据存储格式,平台无关、语言无关、可扩展,可用于通讯协议和数据存储等领域。

上句有几个关键点:它是一种数据存储格式,跨语言,跨平台,用于通讯协议和数据存储。

这么看和我们熟悉的JSON类似,但其实着重点有些本质的区别:

JSON主要是用于数据的传输,因为它轻量级,可读性好,解析简单。

Protobuf主要是用于跨语言的IDL,它除了和JSON、XML一样能定义结构体之外,还可以使用自描述格式定于出接口的特性,

​ 并可以使用针对不同语言的protocol编译器产生不同语言的stub类。所以天然的适用于跨语言的RPC框架中。

而关于进程间的通讯,无疑是Socket。Java方面gRPC同样使用了成熟的开源框架Netty。使用Netty Channel作为数据通道。传输协议使用了HTTP2。

通过以上的分析,我们可以将一个完整的gRPC流程总结为以下几步:

通过.proto文件定义传输的接口和消息体。
通过protocol编译器生成server端和client端的stub程序。
将请求封装成HTTP2的Stream。
通过Channel作为数据通信通道使用Socket进行数据传输。

四.gRPC传输格式protobuf

protobuf (Google Protocol Buffers) 是Google提供- -个具有高效的协议
数据交换格式工具库(类似Json),Protobuf有 更高的转化效率,时间效率
和空间效率都是JSON的3-5倍。

在这里插入图片描述

五、实例备战

news服务端

1、依赖

 <dependencies>        <!-- https://mvnrepository.com/artifact/io.grpc/grpc-netty-shaded -->        <dependency>            <groupId>io.grpc</groupId>            <artifactId>grpc-netty-shaded</artifactId>            <version>1.42.0</version>        </dependency>        <!-- https://mvnrepository.com/artifact/io.grpc/grpc-protobuf -->        <dependency>            <groupId>io.grpc</groupId>            <artifactId>grpc-protobuf</artifactId>            <version>1.42.0</version>        </dependency>        <!-- https://mvnrepository.com/artifact/io.grpc/grpc-stub -->        <dependency>            <groupId>io.grpc</groupId>            <artifactId>grpc-stub</artifactId>            <version>1.42.0</version>        </dependency>        <!-- https://mvnrepository.com/artifact/org.apache.tomcat/annotations-api -->        <dependency>            <groupId>org.apache.tomcat</groupId>            <artifactId>annotations-api</artifactId>            <version>6.0.53</version>            <scope>provided</scope>        </dependency></dependencies>    <build>        <extensions>            <extension>                <groupId>kr.motd.maven</groupId>                <artifactId>os-maven-plugin</artifactId>                <version>1.6.2</version>            </extension>        </extensions>        <plugins>            <plugin>                <groupId>org.xolstice.maven.plugins</groupId>                <artifactId>protobuf-maven-plugin</artifactId>                <version>0.6.1</version>                <configuration>                    <protocArtifact>com.google.protobuf:protoc:3.21.4:exe:${os.detected.classifier}</protocArtifact>                    <pluginId>grpc-java</pluginId>                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.42.0:exe:${os.detected.classifier}</pluginArtifact>                </configuration>                <executions>                    <execution>                        <goals>                            <goal>compile</goal>                            <goal>compile-custom</goal>                        </goals>                    </execution>                </executions>            </plugin>        </plugins>    </build>

2、创建proto(服务定义脚本)

3、利用protobuf:compile 生成存根类protobuf:compile-custom 生成grpc通讯类

4、服务端核心业务service

public class NewsService extends NewsServiceGrpc.NewsServiceImplBase {
    @Override
    public void list(NewsProto.NewsRequest request, StreamObserver<NewsProto.NewsResponse> responseObserver) {
       String date =request.getDate();
       NewsProto.NewsResponse newList=null;
       try {
           NewsProto.NewsResponse.Builder newListBuilder=NewsProto.NewsResponse.newBuilder();
           for (int i = 0; i <=100; i++) {
               NewsProto.News news = NewsProto.News.newBuilder().setId(i)
                       .setContent(i)
                       .setTitle("新闻标题"+i)
                       .setCreateTime(System.currentTimeMillis())
                       .build();
               newListBuilder.addNews(news);

           }
           newList=newListBuilder.build();
       }catch (Exception e){
           responseObserver.onError(e);
       }finally {
           responseObserver.onNext(newList);
       }
       responseObserver.onCompleted();
    }
}

5、开发启动器

public class GrpcServer {
    private static final int port =9999;
    public static void main(String[] args) throws IOException, InterruptedException {
        Server server = ServerBuilder.forPort(port).addService(new NewsService()).build().start();
        System.out.println(String.format("GRPC服务端启动成功,端口号:%d",port));
        server.awaitTermination();
    }
}
news客户端

1、2、3同上

4、客户端类发起远程调用

public class NewsClient {
    private static final String host ="localhost";
    private static final int serverPort =9999;

    public static void main(String[] args) {
        ManagedChannel channel = ManagedChannelBuilder.forAddress(host, serverPort).usePlaintext()
                //无需加密或认证
                .build();

        try {
            NewsServiceGrpc.NewsServiceBlockingStub blockingStub = NewsServiceGrpc.newBlockingStub(channel);
            NewsProto.NewsRequest request = NewsProto.NewsRequest.newBuilder().setDate("20220801").build();
            NewsProto.NewsResponse newsResponse = blockingStub.list(request);
            List<NewsProto.News> newsList = newsResponse.getNewsList();
            for (NewsProto.News news:newsList
                 ) {
                System.out.println(news);
            }
        } finally {
            //关闭
            channel.shutdown();
        }
    }

}

六、ProtoBuf语法

// 指定的当前proto语法的版本,有2和3
syntax = "proto3";
// 生成多个类
option java_multiple_files = false;
//生成java类所在的包
option java_package = "com.lim.news.proto";
//生成外层类包
option java_outer_classname = "NewsProto";
//包名.proto
package news;
//定义RPC服务RouteGuide
service NewsService{
//    list 方法名,NewsRequest表示传入的参数,
    rpc list (NewsRequest) returns (NewsResponse) {}
}
//        消息是gRPC描述信息的基本单位,类似Java的"实体类"
//        消息的名字,对应于生成代码后的类名
//        每一个消息都对应生成一个类, 根据java_ multiple_ files 设置不同文件数量也不同
//        java_ multiple_ files=true, protobuf为每一个 消息自动生成一个java文件
//        java_ multiple_ files=false, protobuf会生成一个大类, 消息作为子类集中在一个java 文件
message NewsRequest{
//        宇段:类型名称=索引值(id)
//        每个字段都要定义唯- -的索引值, 这些数字是用来在消息的二进制格式中识别各个字段的。
//        一旦 开始使用就不能够再改变,//最 小的标识号可以从1开始,最大到2^29 - 1, or 536,870, 911。
//        不可以使用其中的[19000 - 19999]的标识号,Protobuf 协议实现中对这些进行J预留。
//        切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。
    string date = 1;
}
message NewsResponse{
    //repeated说明是一个集合(数组),数组的每一个元素都是News对象
    repeated News news = 1;// List<News> getNewsList();
}
//News新闻实体
message News{
    int32 id = 1; //int id = 0
    string title = 2;//String title =""
    string content = 3 ;
    int64 createTime =4;//long createTime= 0
}
类型对照表

存根类解析
在这里插入图片描述

七、通信模式介绍

1、一元RPC通信模式

概念:客户端向服务器发送单个请求并获得响应

实例(news-demo)

在这里插入图片描述

2、服务端流式RPC通信模式

概念:从窖户端发起1次请求,会产生从服务器的多次响应

在这里插入图片描述

服务端流式RPC proto文件

// 指定的当前proto语法的版本,有2和3
syntax = "proto3";
// 生成多个类
option java_multiple_files = false;
//生成java类所在的包
option java_package = "com.lim.sms.proto";
//生成外层类包
option java_outer_classname = "SmsProto";
//包名.proto
package sms;
service SmsService{
//    stream 流式请求响应
    rpc sendSms(SmsRequest) returns(stream SmsResponse);
}
message SmsRequest{
    repeated string phoneNumber=1;
    string content=2;
}
message SmsResponse{
    string result=1;
}

服务端流式 客户端调用类

public class SmsClient {
    private static final String host ="localhost";
    private static final int serverPort =9999;
    public static void main(String[] args) {
        //建立网络通道
        ManagedChannel channel = ManagedChannelBuilder.forAddress(host, serverPort).usePlaintext()
                //无需加密或认证
                .build();
        //BlockingStub支持流式通信
        try {
            SmsServiceGrpc.SmsServiceBlockingStub blockingStub = SmsServiceGrpc.newBlockingStub(channel);
            Iterator<SmsProto.SmsResponse> sendSms = blockingStub.sendSms(SmsProto.SmsRequest.newBuilder()
                    .setContent("下午三点开会")
                    .addPhoneNumber("130248622581")
                    .addPhoneNumber("130248682581")
                    .addPhoneNumber("130248681581")
                    .addPhoneNumber("130248686581")
                    .build()
            );
            while (sendSms.hasNext()){
                SmsProto.SmsResponse next = sendSms.next();
                System.out.println(next.getResult());
            }
        } finally {
            channel.shutdown();
        }

    }
}
3、 客户端流式RPC通信模式

概念:从客户端发起不定次请求,产生从服务器的1次响应

客户端流式RPC proto文件

service SmsService{
//    stream 流式请求响应
    rpc createPhone(stream PhoneNumberRequest) returns (PhoneNumberResponse);
}

服务端逻辑层

@Override
    public StreamObserver<SmsProto.PhoneNumberRequest> createPhone(StreamObserver<SmsProto.PhoneNumberResponse> responseObserver) {
//        异步通信基于responseObserver进行回调
        return new StreamObserver<SmsProto.PhoneNumberRequest>(){
            int i=0;
            @Override
            //接收到客户端发送过来的新的电话号码时,触发onNext
            public void onNext(SmsProto.PhoneNumberRequest request) {
                System.out.println(request.getPhoneNumber()+"手机号已注册");
                i+=1;
            }

            @Override
            public void onError(Throwable t) {
                t.printStackTrace();
            }
//客户端传输完毕,完成消息统计
            @Override
            public void onCompleted() {
                responseObserver.onNext(SmsProto
                        .PhoneNumberResponse.newBuilder()
                        .setResult("本次导入"+i+"个员工号码").build());
                responseObserver.onCompleted();
            }
        };

客户端调用

public class SmsClientAsync {
    private SmsServiceGrpc.SmsServiceStub asyncStub=null;
    private static final String host ="localhost";
    private static final int serverPort =9999;

    public static void main(String[] args) {
        SmsClientAsync smsClientAsync = new SmsClientAsync();
        //建立网络通道
        ManagedChannel channel = ManagedChannelBuilder.forAddress(host, serverPort).usePlaintext()
                //无需加密或认证
                .build();
        smsClientAsync.asyncStub=SmsServiceGrpc.newStub(channel);
        try {
            smsClientAsync.createPhone();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void createPhone() throws InterruptedException {
        //实例化RequestObject,发起请求
        StreamObserver<SmsProto.PhoneNumberRequest> requestObserver= asyncStub
                .createPhone(responseObserver);

        for (int i = 0; i <=10; i++) {
            SmsProto.PhoneNumberRequest request = SmsProto
                    .PhoneNumberRequest
                    .newBuilder()
                    .setPhoneNumber(String.valueOf(12345678 + i))
                    .build();
            requestObserver.onNext(request);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        requestObserver.onCompleted();
        Thread.sleep(1000);
    }
    //监听服务器响应
    StreamObserver<SmsProto.PhoneNumberResponse>responseObserver=new StreamObserver<SmsProto.PhoneNumberResponse>(){

        //
        @Override
        public void onNext(SmsProto.PhoneNumberResponse response) {
            System.out.println(response.getResult());
        }

        @Override
        public void onError(Throwable t) {
            t.printStackTrace();
        }

        @Override
        public void onCompleted() {
            System.out.println("处理完毕");
        }
    };
}
4 、双向流式RPC通信模式

概念:从客户端发起不定次请求,产生从服务器的不定次响应

双向流式RPC通信模式 proto文件

service SmsService{
//    stream 流式请求响应
    rpc createAndSendSms(stream PhoneNumberRequest) returns (stream PhoneNumberResponse);

}

服务端逻辑层

 @Override
    public StreamObserver<SmsProto.PhoneNumberRequest> createAndSendSms(StreamObserver<SmsProto.PhoneNumberResponse> responseObserver) {
        //        异步通信基于responseObserver进行回调
        return new StreamObserver<SmsProto.PhoneNumberRequest>(){
            @Override
            //接收到客户端发送过来的新的电话号码时,触发onNext,创建多个响应
            public void onNext(SmsProto.PhoneNumberRequest request) {

                //模拟写入操作
                System.out.println(request.getPhoneNumber()+"已完成注册");
                responseObserver.onNext(SmsProto.PhoneNumberResponse.newBuilder().setResult(request.getPhoneNumber()+"手机号已经完成注册,已发送给王经理").build());
                responseObserver.onNext(SmsProto.PhoneNumberResponse.newBuilder().setResult(request.getPhoneNumber()+"手机号已经完成注册,已发送给林总").build());
                responseObserver.onNext(SmsProto.PhoneNumberResponse.newBuilder().setResult(request.getPhoneNumber()+"手机号已经完成注册,已发送给叶总监").build());
            }

            @Override
            public void onError(Throwable t) {
                t.printStackTrace();
            }
            //客户传输完毕
            @Override
            public void onCompleted() {
                responseObserver.onCompleted();
            }
        };
    }

客户端调用( 与客户端流式相同)

八、Spring Boot集成 grpc

服务端

服务端pom

<dependency>
            <groupId>net.devh</groupId>
            <artifactId>grpc-server-spring-boot-starter</artifactId>
            <version>2.13.0.RELEASE</version>
</dependency>
 <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.6.2</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:3.17.3:exe:${os.detected.classifier</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.42.0:exe:${os.detected.classifier}</pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

服务端逻辑层

@GrpcService
public class NewsService extends NewsServiceGrpc.NewsServiceImplBase {
    @Override
    public void hello(NewsProto.StringRequest request, StreamObserver<NewsProto.StringResponse> responseObserver) {
        String name = request.getName();
        NewsProto.StringResponse response=NewsProto.StringResponse.newBuilder().setResult("你好,gRPC"+name).build();
        //调用onNext进行回传response
        responseObserver.onNext(response);
        //结束服务器端处理
        responseObserver.onCompleted();
    }
}

服务端配置文件

grpc:
  server:
    port: 9090
客户端

客户端pom

        <dependency>
            <groupId>net.devh</groupId>
            <artifactId>grpc-client-spring-boot-starter</artifactId>
            <version>2.13.0.RELEASE</version>
        </dependency>
 <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.6.2</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:3.17.3:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.42.0:exe:${os.detected.classifier}</pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

客户端配置文件

grpc:
  client:
    grpc-server:
      address: 'static://127.0.0.1:9090'
      negotiationType: plaintext

客户端控制层

@RestController
public class NewsController {
    @GrpcClient("grpc-server")
    private NewsServiceGrpc.NewsServiceBlockingStub blockingStub;
    @GetMapping ("sayhello/{name}")
    public String sayHello( @PathVariable String name){
        NewsProto.StringResponse response = blockingStub.hello(NewsProto.StringRequest.newBuilder().setName(name).build());
        return response.getResult();
    }
}
```

客户端配置文件

grpc:
  client:
    grpc-server:
      address: 'static://127.0.0.1:9090'
      negotiationType: plaintext

客户端控制层

@RestController
public class NewsController {
    @GrpcClient("grpc-server")
    private NewsServiceGrpc.NewsServiceBlockingStub blockingStub;
    @GetMapping ("sayhello/{name}")
    public String sayHello( @PathVariable String name){
        NewsProto.StringResponse response = blockingStub.hello(NewsProto.StringRequest.newBuilder().setName(name).build());
        return response.getResult();
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值