SpringBoot之Grpc

标签: 无 分类: 未分类 创建时间:2024-10-23 03:20:44 更新时间:2024-11-15 10:49:43

1.前言

在java和python通信章节里面,我写了如何使用java开发grpc的客户端和服务端。但是在springboot里面,要是同时使用 springboot 和 grpc 服务,似乎不行。因为 awaitTermination 会阻塞程序运行,如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@Component
public class AiServer extends ManageAiGrpc.ManageAiImplBase {

// 本地服务启动端口
@Value("${grpc.localport}")
private int localport;

/**
*
* @param request
* @param responseObserver
*/
@Override
public void addManageAi(ManageAiRequest request, StreamObserver<ManageAiResponse> responseObserver) {


ManageAiResponse response = ManageAiResponse.newBuilder()
.setCode(200)
.setSuccess(true)
.build();

// Use responseObserver to send a single response back
responseObserver.onNext(response);

// When you are done, you must call onCompleted.
responseObserver.onCompleted();
}



@PostConstruct
public void initServer() {
try {
// Create a new server to listen on port 8080
Server server = ServerBuilder.forPort(localport)
.addService(new AiServer())
.build();

// Start the server
server.start();
// Server threads are running in the background.
log.info("Grpc Server started");
// Don't exit the main thread. Wait until server is terminated.
server.awaitTermination();
}catch (Exception e){
log.error(e.getMessage(), e);
}
}
}

参考文章:
【1】.springboot项目同时启动web服务和grpc服务 引入spring-boot-starter-web依赖和grpc-client-spring-boot-starter依赖
【2】.手把手带你写一个SpringBoot + gRPC服务 这里用了 org.lognet 的 grpc-spring-boot-starter,创建了一个服务器,创建一个HelloWorldClient类,实例化stub对象,并发送rpc请求的方法,这个没有用 @GrpClient 的地方。
【3】.使用注解@GrpcService在Spring Boot 中开始使用gRPC 在使用Spring Boot构建独立、生产级基于Spring的应用程序的广泛框架时,通过使用注解简化了将gRPC集成到应用程序中的步骤。@GrpcService注解是gRPC Spring Boot启动器库的一部分,提供了gRPC和Spring Boot之间的无缝集成。它允许开发人员在Spring Boot应用程序中轻松定义gRPC服务,自动处理大部分样板代码和配置。

2.添加依赖

我的是 spring boot 3.1.x版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.58.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.58.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.58.0</version>
</dependency>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-spring-boot-starter</artifactId>
<version>2.15.0.RELEASE</version>
</dependency>
</dependencies>

<build>
<resources>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
</resource>
<resource>
<directory>${project.basedir}/src/main/java</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.json</include>
<include>**/*.html</include>
</includes>
</resource>
</resources>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.2</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>16</source>
<target>16</target>
</configuration>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.0</version>
<configuration>
<!-- 指定代码生成到 src 下-->
<!-- <outputDirectory>src/main/java/com/dji/sample/ai/grpc/proto</outputDirectory>-->
<protoSourceRoot>src/main/proto</protoSourceRoot><!--默认的proto文件路径-->
<!--Protobuf compiler artifact specification, in groupId:artifactId:version[:type[:classifier]] format. When this parameter is set, the plugin attempts to resolve the specified artifact as protoc executable.-->
<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>
<!--设置是否在生成java文件之前清空outputDirectory的文件,默认值为true,设置为false时也会覆盖同名文件-->
<clearOutputDirectory>true</clearOutputDirectory>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal> <!--按照指定的插件进行编译,即按照GRPC协议编译protob文件-->
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
参考文章:
【1】.Spring Boot下grpc最佳实践 通过教程中例子,你可以学会以下技能:如何定义服务接口、如何生成服务器和客户端代码、如何集成spring boot 实现一个简单的客户端和服务器。

3.修改启动类

这一步非常的重要,否则就会报错,主要就是增加了 @ImportAutoConfiguration 注解,添加了 grpc 的相关配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Slf4j
@SpringBootApplication
@ImportAutoConfiguration({
JustAuthAutoConfiguration.class,
net.devh.boot.grpc.common.autoconfigure.GrpcCommonCodecAutoConfiguration.class,
net.devh.boot.grpc.common.autoconfigure.GrpcCommonTraceAutoConfiguration.class,
net.devh.boot.grpc.server.autoconfigure.GrpcMetadataConsulConfiguration.class,
net.devh.boot.grpc.server.autoconfigure.GrpcMetadataEurekaConfiguration.class,
net.devh.boot.grpc.server.autoconfigure.GrpcMetadataNacosConfiguration.class,
net.devh.boot.grpc.server.autoconfigure.GrpcServerAutoConfiguration.class,
net.devh.boot.grpc.server.autoconfigure.GrpcServerFactoryAutoConfiguration.class,
net.devh.boot.grpc.server.autoconfigure.GrpcServerMetricAutoConfiguration.class,
net.devh.boot.grpc.server.autoconfigure.GrpcServerSecurityAutoConfiguration.class,
net.devh.boot.grpc.server.autoconfigure.GrpcServerTraceAutoConfiguration.class
}) // spring boot 3.x justauth 兼容性处理
//@EnableAutoConfiguration(exclude={MongoAutoConfiguration.class})
@ComponentScan(basePackages = {"com.dji.*","org.jeecg.*"})
@MapperScan(basePackages = {"com.dji.sample.*.dao","com.dji.sample.drone.mapper"})
@EnableScheduling
public class JeecgSystemApplication extends SpringBootServletInitializer {

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(JeecgSystemApplication.class);
}

public static void main(String[] args) throws UnknownHostException {
ConfigurableApplicationContext application = SpringApplication.run(JeecgSystemApplication.class, args);
Environment env = application.getEnvironment();
String ip = InetAddress.getLocalHost().getHostAddress();
String port = env.getProperty("server.port");
String path = oConvertUtils.getString(env.getProperty("server.servlet.context-path"));
}

}

2.客户端

(1)编写配置文件
这个 grpc-server 是服务的名字,可以自定义。

1
2
3
4
5
6
7
8
9
10
# grpc 配置
grpc:
# 远程服务端
client:
grpc-ai:
address: static://localhost:50052 # gRPC服务地址
negotiation-type: plaintext
enableKeepAlive: true # 是否启用KeepAlive机制
keepAliveTime: 30s # KeepAlive时间
keepAliveTimeout: 5s # 是否在没有gRPC调用时保持KeepAlive的连接

(2) 使用 @GrpcClient
Sub在第一步使用proto生成的时候,已经有了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@GrpcClient("grpc-ai")
private AiGrpc.AiBlockingStub blockingStub;
/**
* 开启 AI 识别.
*/
public AiReply start(String sn,String videoid) {
AiLiveRequest request = AiLiveRequest.newBuilder().setSn(sn).setVideoid(videoid).build();
AiReply response;
try {
response = blockingStub.start(request);
return response;
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
}
return AiReply.newBuilder().build();
}
参考文章:
【1】.Spring Boot starter module for gRPC framework.
【2】.【Java】gRPC与Spring boot继承实现示例
【3】.在 Spring Boot 应用中使用 Grpc 进行通信
【4】.grpc-spring-boot-starter-simple 在 spring boot 应用中,通过 @GrpcService 自动配置并运行一个嵌入式的 gRPC 服务.使用 @GrpcClient 自动创建和管理您的 gRPC Channels 和 stubs.
【5】.SpringBoot集成gRPC
【6】.SpringBoot整合高性能微服务框架 gRPC 这里从grpc的集成,到最后使用讲了一遍,还提到了grpc-spring-boot-starter

3.服务端

(1) 编写配置

1
2
3
4
5
grpc:
server:
port: 9999
security:
enabled: false

(2) 实现服务
在需要的类上增加 @GrpcService 就可以启动一个 grpc 的server服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
@Slf4j
@Component
@GrpcService
public class AiServer extends ManageAiGrpc.ManageAiImplBase {
/**
*
* @param responseObserver
* @return
*/
@Override
public StreamObserver<FileUploadRequest> fileUpload(StreamObserver<ManageAiResponse> responseObserver){
return new StreamObserver<FileUploadRequest>() {

// 实现数据接收
@Override
public void onNext(FileUploadRequest stock) {
log.info(stock.toString());
}

// 数据接收完成,返回信息
@Override
public void onCompleted() {
responseObserver.onNext(ManageAiResponse.newBuilder()
.setCode(200)
.setSuccess(true)
.build());
responseObserver.onCompleted();
}
// 数据接收错误
@Override
public void onError(Throwable t) {
log.error(t.getMessage(), t);
}
};
}

/**
*
* @param request
* @param responseObserver
*/
@Override
public void addManageAi(ManageAiRequest request, StreamObserver<ManageAiResponse> responseObserver) {


ManageAiResponse response = ManageAiResponse.newBuilder()
.setCode(200)
.setSuccess(true)
.build();

// Use responseObserver to send a single response back
responseObserver.onNext(response);

// When you are done, you must call onCompleted.
responseObserver.onCompleted();
}
}
参考文章:
【1】.在SpringBoot中集成grpc-server-spring-boot-starter实现gRPC服务 这里我们使用的spring-boot版本与grpc-client-spring-boot-starter和grpc-server-spring-boot-starter使用的版本不同,所以后面我们需要在grpc的starter排除掉默认的spring-boot版本。
【2】.使用 @GrpcClient 实现客户端 使用 @GrpcClient 注解的场景通常是在 Spring Boot 中使用 gRPC 客户端时,因为 Spring Boot 已经为我们提供了自动配置功能,可以简化 gRPC 客户端的配置和使用。在这种场景下,我们只需要在应用程序中添加 @GrpcClient 注解,然后在配置文件中添加 gRPC 客户端的配置信息,就可以方便地使用 gRPC 客户端了。
【3】.Spring Tips: GRPC 这个就是一个视频,好像是官方的说明,但是我用的总是不太好看。
【4】.配置 本节描述您如何配置您的 grpc-spring-boot-starter 客户端。
【5】.手把手教你把gRPC集成到SpringBoot3框架中 增加@ImportAutoConfiguration内容,让SpringBoot3识别启动gRPC服务器。这里解决了我在 springboot 3.x.x 的框架中增加 grpc 的问题,解决了我的问题。

问题

1.micrometer-core不兼容

在使用 3.1.0 版本的时候,出现了这个问题,The following method did not exist,’io.micrometer.core.instrument.Meter$MeterProvider io.micrometer.core.instrument.Counter$Builder.withRegistry(io.micrometer.core.instrument.MeterRegistry)’

【解决方案】
主要的解决方案就是降低 版本,从 3.1.0.RELEASE,降到了 2.15.0.RELEASE

1
2
3
4
5
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-spring-boot-starter</artifactId>
<version>2.15.0.RELEASE</version>
</dependency>
参考文章:
【1】. micrometer

2.Error creating bean with name ‘grpcChannelHealthIndicator’ defined in class path resource

解决了版本问题,然后启动之后,出现了新的问题,好像启动不了。 “Factory method ‘shadedNettyGrpcChannelFactory’ threw exception with message: io/grpc/internal/AbstractManagedChannelImplBuilder。” “Unsatisfied dependency expressed through method ‘grpcChannelHealthIndicator’ parameter 0”

【尝试方案】
(1) 增加了 grpc-server-spring-boot-autoconfigure 依赖,无效。

1
2
3
4
5
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-autoconfigure</artifactId>
<version>2.15.0.RELEASE</version>
</dependency>

(2)排除 grpc-netty-shaded 无效。

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-spring-boot-starter</artifactId>
<version>2.15.0.RELEASE</version>
<exclusions>
<exclusion>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
</exclusion>
</exclusions>
</dependency>

【解决方案】
最后的解决方案就是在启动类上增加

1
2
3
4
5
6
7
8
9
10
11
12
13
@ImportAutoConfiguration({
JustAuthAutoConfiguration.class,
net.devh.boot.grpc.common.autoconfigure.GrpcCommonCodecAutoConfiguration.class,
net.devh.boot.grpc.common.autoconfigure.GrpcCommonTraceAutoConfiguration.class,
net.devh.boot.grpc.server.autoconfigure.GrpcMetadataConsulConfiguration.class,
net.devh.boot.grpc.server.autoconfigure.GrpcMetadataEurekaConfiguration.class,
net.devh.boot.grpc.server.autoconfigure.GrpcMetadataNacosConfiguration.class,
net.devh.boot.grpc.server.autoconfigure.GrpcServerAutoConfiguration.class,
net.devh.boot.grpc.server.autoconfigure.GrpcServerFactoryAutoConfiguration.class,
net.devh.boot.grpc.server.autoconfigure.GrpcServerMetricAutoConfiguration.class,
net.devh.boot.grpc.server.autoconfigure.GrpcServerSecurityAutoConfiguration.class,
net.devh.boot.grpc.server.autoconfigure.GrpcServerTraceAutoConfiguration.class
})
参考文章:
【1】.spring异常Unsatisfied dependency expressed through constructor parameter 0 由于在类里面定义了“有参构造方法”,这样的话就会覆盖原先的“无参构造方法”,所以会出现此异常,所以在类里面加上“无参构造方法”就完了
【2】.版本 这里是官方版本的说明。
小额赞助
本人提供免费与付费咨询服务,感谢您的支持!赞助请发邮件通知,方便公布您的善意!
**光 3.01 元
Sun 3.00 元
bibichuan 3.00 元
微信公众号
广告位
诚心邀请广大金主爸爸洽谈合作
每日一省
isNaN 和 Number.isNaN 函数的区别?

1.函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。

2.函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。

每日二省
为什么0.1+0.2 ! == 0.3,如何让其相等?

一个直接的解决方法就是设置一个误差范围,通常称为“机器精度”。对JavaScript来说,这个值通常为2-52,在ES6中,提供了Number.EPSILON属性,而它的值就是2-52,只要判断0.1+0.2-0.3是否小于Number.EPSILON,如果小于,就可以判断为0.1+0.2 ===0.3。

每日三省
== 操作符的强制类型转换规则?

1.首先会判断两者类型是否**相同,**相同的话就比较两者的大小。

2.类型不相同的话,就会进行类型转换。

3.会先判断是否在对比 null 和 undefined,是的话就会返回 true。

4.判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number。

5.判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断。

6.判断其中一方是否为 object 且另一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断。

每日英语
Happiness is time precipitation, smile is the lonely sad.
幸福是年华的沉淀,微笑是寂寞的悲伤。