SpringBoot整合grpc
Introduction to gRPC | gRPC
一、简介
之所以会说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 -->
-
<!--<!– ${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服务完成;
-
*/
-
-
4j
-
public class GrpcServerService extends SimpleGrpc.SimpleImplBase {
-
-
-
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 -->
-
<!--<!– ${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服务;
-
*/
-
-
-
public class GrpcClientService {
-
-
-
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
-
*/
-
-
-
public class SimpleGrpcController {
-
-
private GrpcClientService service;
-
-
-
public BaseResponse getOneToOne() {
-
return new BaseResponse(service.oneToOne("客户端kele连接"));
-
}
-
-
-
-
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连接服务端
可以看到,在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,

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

服务端
待更新。。。。。。


浙公网安备 33010602011771号