SpringBoot整合grpc

目录

一、简介

二、下图是通信流程模型 

三、准备

四、环境搭建

1、服务端server

1)pom文件

2)创建proto用于生成proto目标文件

 3)服务端yml配置

4)创建GrpcServerService实现服务端接口

2、客户端client

1)pom文件

2)创建proto用于生成proto目标文件

 3)客户端yml配置

4)创建GrpcClientService客户端类

5)创建SimpleGrpcController

五、启动/测试

1、服务端测试

2、客户端测试 

六、问题

七、源码验证解析

客户端

服务端 


一、简介

之所以会说grpc是高性能框架,默认情况下,gRPC基于Netty进行服务端和客户端互通,使用Protocol Buffers进行传输,这是Google用于序列化结构化数据的成熟开源机制,基于proto3情况下它还是一个跨语言的RPC框架(目前支持Java、c++、Dart、Python、Objective-C、c#、lite-runtime (Android Java)、Ruby和JavaScript(来自协议缓冲区GitHub repo),以及来自golang/protobuf官方包的Go语言生成器) 

如果对grpc不够了解可以参考Introduction to gRPC | gRPC

二、下图是通信流程模型 

  • 客户端(gRPC Stub)调用 A 方法,发起 RPC 调用。
  • 对请求信息使用 Protobuf 进行对象序列化压缩(IDL)。
  • 服务端(gRPC Server)接收到请求后,解码请求体,进行业务逻辑处理并返回。
  • 对响应结果使用 Protobuf 进行对象序列化压缩(IDL)。
  • 客户端接受到服务端响应,解码请求体。回调被调用的 A 方法,唤醒正在等待响应(阻塞)的客户端调用并返回响应结果。

三、准备

1、PROTOC下载及安装

下载地址

2、准备两个springboot项目,作为服务端和客户端

3、下载Apifox软件,用于直连服务端(可选)

下载地址

四、环境搭建

1、服务端server

1)pom文件
<!--部分配置-->
<dependencies>      
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>${grpc.version}</version>
        </dependency>

        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>${grpc.version}</version>
        </dependency>
        <!--gRPC服务端-->
        <dependency>
            <groupId>net.devh</groupId>
            <artifactId>grpc-server-spring-boot-starter</artifactId>
            <version>2.13.1.RELEASE</version>
        </dependency>


    </dependencies>
    <properties>
        <grpc.version>1.42.1</grpc.version>
        <protobuf.version>3.7.1</protobuf.version>
    </properties>
    <build>
        <finalName>${project.artifactId}-${project.version}</finalName>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.6.2</version>
            </extension>
        </extensions>

        <plugins>
            <!--            Grpc coding plug-in-->
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <!--suppress UnresolvedMavenProperty -->
                    <!--&lt;!&ndash; ${os.detected.classifier} 变量由${os.detected.name} 和 ${os.detected.arch} 组成-->
                    <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
<!--                   protoSourceRoot 默认src/main/proto-->
<!--                    <protoSourceRoot>src/java/main/proto</protoSourceRoot>-->
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
2)创建proto用于生成proto目标文件

注意:proto文件需要创建在src/main/proto下,因为pom使用的是protoSourceRoot默认路径

创建Simple.proto 

syntax = "proto3"; // 协议版本

// 选项配置
option java_multiple_files = true;
//生成位置
option java_package = "com.na.model.proto";
option java_outer_classname = "SimpleProto";

service Simple {
  // 简单gRPC
  rpc OneToOne (MyRequest) returns (MyResponse) {
  }
}

message MyRequest {
  string name = 1;

  int32 value = 2;
}

message MyResponse {
  string message = 1;

  int64 result = 2;
}

 生成方式有两种一种是通过命令,这里使用maven插件生成compile和compile-custom

 编译后可在target下看到生成的相应的java类

 3)服务端yml配置

这里只贴出关键代码,其他配置根据实际情况来

# gRPC有关的配置,这里只需要配置服务端口号默认9090
grpc:
  server:
    port: 19898
spring:
  application:
    name: nacos-grpc

4)创建GrpcServerService实现服务端接口
package com.na.grpc.server;
import com.na.model.proto.MyRequest;
import com.na.model.proto.MyResponse;
import com.na.model.proto.SimpleGrpc;
import io.grpc.stub.StreamObserver;
import lombok.extern.slf4j.Slf4j;
import net.devh.boot.grpc.server.service.GrpcService;


/**
 * GrpcServerService.java中有几处需要注意:
 *
 * 是使用@GrpcService注解,再继承SimpleImplBase,这样就可以借助grpc-server-spring-boot-starter库将oneToOne暴露为gRPC服务;
 *
 * SimpleImplBase是前文中根据maven compile编译 proto文件自动生成的java代码,
 *
 * oneToOne方法中处理完毕业务逻辑后,调用responseObserver.onNext方法填入返回内容;
 *
 * 调用responseObserver.onCompleted方法表示本次gRPC服务完成;
 */
@GrpcService
@Slf4j
public class GrpcServerService extends SimpleGrpc.SimpleImplBase {

    @Override
    public void oneToOne(MyRequest request, StreamObserver<MyResponse> responseObserver) {
        log.info("接收客户端数据{}", request);
        MyResponse response = MyResponse.newBuilder().setMessage( request.getName()).build();
        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }

}

2、客户端client

1)pom文件
<!--部分配置-->
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>  

        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>${grpc.version}</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>${grpc.version}</version>
        </dependency>
        <!--gRPC客户端-->
        <dependency>
            <groupId>net.devh</groupId>
            <artifactId>grpc-client-spring-boot-starter</artifactId>
            <version>2.14.0.RELEASE</version>
        </dependency>
    </dependencies>

    <properties>
        <grpc.version>1.42.1</grpc.version>
        <protobuf.version>3.7.1</protobuf.version>
    </properties>
    <build>
        <finalName>${project.artifactId}-${project.version}</finalName>

        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.6.2</version>
            </extension>
        </extensions>

        <plugins>
            <!--            Grpc coding plug-in-->
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <!--suppress UnresolvedMavenProperty -->
                    <!--&lt;!&ndash; ${os.detected.classifier} 变量由${os.detected.name} 和 ${os.detected.arch} 组成-->
                    <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
<!--                   protoSourceRoot 默认src/main/proto-->
<!--                    <protoSourceRoot>src/java/main/proto</protoSourceRoot>-->
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
2)创建proto用于生成proto目标文件

与服务端操作一样,这里省略。。。。。。

 3)客户端yml配置

这里只贴出关键代码,其他配置根据实际情况来

server:
  port: 11278
  servlet:
    context-path: /
# grpc配置
grpc:
  # grpc clienT相关配置
  client:
    # 服务名(不同服务名可对应不同配置)
    # nacos-grpc是服务端配置的名字,GrpcClient注解会用到
    nacos-grpc:
#       gRPC服务端地址
#      address: 'dns://127.0.0.1:19898'
      address: 'static://127.0.0.1:19898'
      # 是否开启保持连接(长连接)
      enableKeepAlive: true
      # 保持连接时长(默认20s)
      keepAliveTimeout: 20s
      # 没有RPC调用时是否保持连接(默认false,可禁用避免额外消耗CPU)
      keepAliveWithoutCalls: false
      # 客户端负载均衡策略(round_robin(默认), pick_first)
      defaultLoadBalancingPolicy: round_robin
      # 通信类型
      # plaintext | plaintext_upgrade | tls
      # 明文通信且http/2 | 明文通信且升级http/1.1为http/2 | 使用TLS(ALPN/NPN)通信
      negotiationType: plaintext
4)创建GrpcClientService客户端类
package com.na.grpc.client;

import com.na.model.proto.MyRequest;
import com.na.model.proto.MyResponse;
import com.na.model.proto.SimpleGrpc;
import io.grpc.StatusRuntimeException;
import lombok.extern.slf4j.Slf4j;
import net.devh.boot.grpc.client.inject.GrpcClient;
import org.springframework.stereotype.Service;

/**
 * GrpcClientService类有几处要注意的地方:
 * <p>
 * 用@Service将GrpcClientService注册为spring的普通bean实例;
 * <p>
 * 用@GrpcClient修饰SimpleBlockingStub,这样就可以通过grpc-client-spring-boot-starter库发起gRPC调用,被调用的服务端信息来自名为nacos-grpc服务端配置;
 * <p>
 * SimpleBlockingStub来自前文中根据helloworld.proto生成的java代码;
 * <p>
 * SimpleBlockingStub.oneToOne方法会远程调用nacos-grpc应用的gRPC服务;
 */
@Service
@Slf4j
public class GrpcClientService {

    @GrpcClient("nacos-grpc")
    private SimpleGrpc.SimpleBlockingStub simpleStub;

    public String oneToOne(final String name) {
        try {
            final MyResponse response = this.simpleStub.oneToOne(MyRequest.newBuilder().setName(name).build());
            return response.getMessage();
        } catch (final StatusRuntimeException e) {
            log.error("FAILED with " + e.getStatus().getCode().name() + ",and e:{}", e.getMessage());
            return "FAILED with " + e.getStatus().getCode().name() + ",and e:" + e.getMessage();
        }
    }
}

5)创建SimpleGrpcController

定义了两种连接方法getOneToOne和testForAddress

package com.na.controller;

import com.na.base.BaseResponse;
import com.na.grpc.client.GrpcClientService;
import com.na.model.proto.MyRequest;
import com.na.model.proto.SimpleGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Description 测试grpc接口
 * @Author kele
 * @Data 2023/9/6 15:35
 */
@RestController
@RequestMapping("grpc")
public class SimpleGrpcController {
    @Autowired
    private GrpcClientService service;

    @GetMapping("getOneToOne")
    public BaseResponse getOneToOne() {
        return new BaseResponse(service.oneToOne("客户端kele连接"));
    }


    @GetMapping("testForAddress")
    public BaseResponse testForAddress() {
        ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 19898)
                .usePlaintext()
                .build();
        MyRequest request = MyRequest.newBuilder().setName("kele的访问").build();
        SimpleGrpc.SimpleBlockingStub stub = SimpleGrpc.newBlockingStub(channel);
        return new BaseResponse(stub.oneToOne(request));
    }

}

五、启动/测试

1、服务端测试

1)先启动服务端启动的netty端口为19898

2)使用Apifox连接服务端

创建grpc操作文档

可以看到,在Apifox中导入了Simple.proto帮我自动生成了客户端连接操作,自己只需要改一下19898端口和请求的参数

2、客户端测试 

1)启动客户端web端口为11278

2)直接通过web访问

这里访问的是getOneToOne方法, 地址http://localhost:11278/grpc/getOneToOne

另外的testForAddress方法可以自己玩一下生产一般不会那样写

六、问题报错

1、出现无法访问com.google.protobuf.GeneratedMessageV3 找不到com.google.protobuf.GeneratedMessageV,在pom中添加以下依赖。

        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>${protobuf.version}</version>
        </dependency>
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java-util</artifactId>
            <version>${protobuf.version}</version>
        </dependency>

2、出现:io.grpc.StatusRuntimeException: UNAVAILABLE

1、检查下IP是否能ping通,IP、端口 是否正确

2、Server是否打开

3、连接中如果有证书,证书是否有效

4、无证书的,是否写了明文连接,例如:

5、以上都没问题可以查看服务端是否添加了这个依赖,grpc-netty-shaded包中可能会存在core的冲突

        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
            <version>${grpc.version}</version>
        </dependency>

七、源码验证解析

客户端

1、客户端序列化,可以查看MyRequest

 2、在客户端调用SimpleGrpc.SimpleBlockingStub.oneToOne方法时,已经将name序列化传输了

3、再往下可以看到客户端ClientCalls中,采用的是异步的方式进行发送二进制数据,等结束执行后再进行判断是否执行结束,

 waitAndDrain()即为等待,直到有一个Runnable,然后执行它和所有在它之后排队的Runnables。一次只能由一个线程调用。poll()方法用于从队列中取出并返回头部的元素,如果队列为空,则返回null。之后又将当前线程赋给了waiter

 用的是线程池,在服务端断点的情况下,后续请求会进入等待队列。

服务端 

待更新。。。。。。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值