初识

Spring Cloud 微服务一般采用feign调用,实际是http+json方式传输数据, 但这种方案从性能和稳定性来讲并不理想, 有没更好的微服务通讯方案?答案是有的, 如果你需要频繁的传输交互, 对性能要求较高, 且需要和各种平台(golang, c++等)对接, 那么SpringBoot + gRpc + Protobuf + Netty 这种通讯方案则更为合适

脉络

  • 什么是gRpc
  • gRpc工作机制
  • Spring Boot与GRPC集成使用
  • 拓展:如何在微服务环境中使用

    知行

    什么是GRPC

    gRPC 是Google开源的高性能、通用的RPC框架。客户端与服务端约定接口调用, 可以在各种环境中运行,具有跨语言特性, 适合构建分布式、微服务应用。
    image.png
    对于开发者而言:
  1. 需要使用protobuf定义接口,即.proto文件
  2. 然后使用compile工具生成特定语言的执行代码,比如JAVA、C/C++、Python等。类似于thrift,为了解决跨语言问题。
  3. 启动一个Server端,server端通过侦听指定的port,来等待Client链接请求,通常使用Netty来构建,GRPC内置了Netty的支持。
  4. 启动一个或者多个Client端,Client也是基于Netty,Client通过与Server建立TCP常链接,并发送请求;Request与Response均被封装成HTTP2的stream Frame,通过Netty Channel进行交互。

为什么使用gRpc?

  1. 性能优异, 采用http2协议, Proto Buffer作序列化传输, 对比JSON与XML有数倍提升。
  2. 多语言支持,多客户端接入, 支持C++/GO/Ruby等语言。
  3. 支持负载均衡、跟踪、健康检查和认证。

    proto3简介

    Protocol Buffers是一个跨语言、跨平台的具有可扩展机制的序列化数据工具。也就是说,我在ubuntu下用python语言序列化一个对象,并使用http协议传输到使用java语言的android客户端,java使用对用的代码工具进行反序列化,也可以得到对应的对象。听起来好像跟json没有多大区别。。。其实区别挺多的。

Google说protobuf是smaller,faster,simpler,我们使用google规定的proto协议定义语言,之后使用proto的工具对代码进行“编译”,生成对应的各个平台的源代码,我们可以使用这些源代码进行工作。

Proto2与proto3:
Proto2和proto3有些区别,包括proto语言的规范,以及生成的代码,proto3更好用,更简便,所以我们直接存proto3开始。

GRPC工作机制

Grpc线程模型:

image.png
gRPC 的线程模型遵循 Netty 的线程分工原则,协议层消息的接收和编解码由 Netty 的 I/O(NioEventLoop) 线程负责, 应用层的处理由应用线程负责,防止由于应用处理耗时而阻塞 Netty 的 I/O 线程。BIO线程模型采用了线程池,但是后端的应用处理线程仍然采用同步阻塞的模型,阻塞的时间取决对方I/O处理的速度和网络I/O传输的速度。

客户端调用流程:

image.png

  1. 客户端 Stub 调用 发起 RPC 调用 远程服务。
  2. 获取服务端的地址信息(列表),使用默认的 LoadBalancer 策略,选择一个具体的 gRPC 服务端。
  3. 如果与服务端之间没有可用的连接,则创建 NettyClientTransport 和 NettyClientHandler,建立 HTTP/2 连接。
  4. 对请求使用 PB(Protobuf)序列化,通过 HTTP/2 Stream 发送给 gRPC 服务端。
  5. 服务端接收到响应之后,使用 PB(Protobuf)做反序列化。
  6. 回调 GrpcFuture 的 set(Response) 方法,唤醒阻塞的客户端调用线程,获取 RPC 响应数据。

    Spring Boot与GRPC集成使用

    下面讲解gRpc与Spring Boot的集成使用

    服务设计

    image.png

    工程结构

    image.png
  • grpc-demo : 父级工程。
  • grpc-client: 客户端工程,负责调用gRPC服务, 提供HTTP服务触发。
  • grpc-server: 股票服务端工程, 提供股票价格接口。
  • grpc-lib: 公用工程,生成为protobuf对象与gRPC Service。

    引入gRpc依赖

    在grpc-demo父级工程下面增加依赖
    1. <dependencies>
    2. <!-- spring boot grpc 相关依赖 -->
    3. <dependency>
    4. <groupId>net.devh</groupId>
    5. <artifactId>grpc-client-spring-boot-starter</artifactId>
    6. <version>2.12.0.RELEASE</version>
    7. </dependency>
    8. <dependency>
    9. <groupId>net.devh</groupId>
    10. <artifactId>grpc-server-spring-boot-starter</artifactId>
    11. <version>2.12.0.RELEASE</version>
    12. </dependency>
    13. </dependencies>
    这里引入的是第三方封装的gRpc组件。

    定义Protobuf结构体

    在grpci-lib工程下, 创建StockService.proto文件 ``` syntax = “proto3”;

option java_multiple_files = true; option java_package = “com.mirson.grpc.lib”; option java_outer_classname = “StockServiceProto”;

// The stock service definition. service StockService { // get stock price by name rpc GetStockPrice (StockServiceRequest) returns (StockServiceReply) { } }

// The request message message StockServiceRequest { string name = 1; }

// The response message message StockServiceReply { string message = 1; }


- 定义一个接口GetStockPrice , 用于查询股票价格。
- 定义请求结构体StockServiceRequest, 里面定义请求参数。
- 定义返回结构体StockServiceReply, 里面定义返回数据属性。

2. 增加POM依赖:
com.google.protobuf protobuf-java 4.0.0-rc-2 io.grpc grpc-all 1.39.0 io.netty netty-all 4.1.25.Final io.netty netty-tcnative-boringssl-static 2.0.8.Final

kr.motd.maven os-maven-plugin 1.6.2 org.xolstice.maven.plugins protobuf-maven-plugin 0.5.0 com.google.protobuf:protoc:3.5.1:exe:${os.detected.classifier} grpc-java io.grpc:protoc-gen-grpc-java:1.11.0:exe:${os.detected.classifier} ${project.basedir}/src/main/proto ${project.basedir}/src/main/java false compile compile compile-custom


点击maven的compile进行编译,即可生成Grpc源码<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1732354/1626933350502-8273c8d7-3e5e-4175-8087-545b73645069.png#clientId=u6677b579-23e0-4&from=paste&id=u2797db3e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=444&originWidth=360&originalType=binary&ratio=1&size=18747&status=done&style=none&taskId=u74b8ac54-f47d-4e18-838e-67d004a582a)
<a name="Nlkc7"></a>
#### 创建gRpc服务
接下来, 我们要提供gRpc服务, 在grpc-server服务工程, 创建GrpcStockService实现类:
```java
@GrpcService
public class GrpcStockService extends StockServiceGrpc.StockServiceImplBase {

    @Override
    public void getStockPrice(StockServiceRequest request, StreamObserver<StockServiceReply> responseObserver) {
        String msg = "股票名称:" + request.getName() + ", 股票价格:" + (new Random().nextInt(100-20)+20);
        StockServiceReply reply =StockServiceReply.newBuilder().setMessage(msg).build();
        responseObserver.onNext(reply);
        responseObserver.onCompleted();
    }
}



重写在定义的gRpc接口, getStockPrice获取股票价格。这里做测试, 我们随机生成股票价格。
创建GrpcServiceStartup启动类:

@SpringBootApplication
public class GrpcServiceStartup {
    public static void main(String[] args) {
        SpringApplication.run(GrpcServiceStartup.class,args);
    }
}

工程配置信息:

spring:
  application:
    name: grpc-server
grpc:
  server:
    port: 9999

定义服务名称和端口。

创建gRpc客户端

有了gRpc服务, 客户端如何调用? 在grpc-client工程中, 创建GrpcClientService类:

@Service
public class GrpcClientService {

    @GrpcClient("grpc-server")
    private StockServiceGrpc.StockServiceBlockingStub stockServiceStub;

    public String getStockPrice(final String name) {
        try {
            final StockServiceReply response = stockServiceStub.getStockPrice(StockServiceRequest.newBuilder().setName(name).build());
            return response.getMessage();
        } catch (final StatusRuntimeException e) {
            return "error!";
        }
    }
}

看到这里, 应该可以明白, 我们引入了生成的StockServiceGrpc, 采用StockServiceBlockingStub阻塞式调用。

通过GrpcClient注解, 指定了具体的grpc服务信息, 这里名称要与配置文件中定义的名称一致。

再创建启动类:

@SpringBootApplication
@RestController
public class GrpcClientApplication {

    @Autowired
    private GrpcClientService grpcClientService;

    public static void main(String[] args) {
        SpringApplication.run(GrpcClientApplication.class, args);
    }

    @RequestMapping("/")
    public String getStockPrice(@RequestParam(defaultValue = "中国平安") String name) {
        return grpcClientService.getStockPrice(name);
    }
}

为了便于测试, 我们通过Spring Boot 提供了一个web接口。

接下来, 就是客户端的工程配置文件, 重点是gRpc的配置信息:

server:
  port: 9000
spring:
  application:
    name: grpc-client

grpc:
  client:
    grpc-server:
      address: 'static://127.0.0.1:9999'
      enableKeepAlive: true
      keepAliveWithoutCalls: true
      negotiationType: plaintext

定义了一个名为grpc-server的远程服务,采用静态方式调用, 指定了IP和PORT, 当然, 也可以采用服务名称方式调用, 这个要结合服务注册发现组件来使用, 后面我们会讲如何与Nacos集成, 在微服务中使用。

启动验证

可以看到, 能够成功访问。

springboot整合gprc 传输对象


参考链接

微服务高效通讯方案之SpringBoot + gRpc + Protobuf + Netty
springboot 集成 grpc 和 protobuf(二) | 在实际项目中使用 grpc 和 protobuf
springboot整合gprc 传输对象