Spring Boot 2+gRPC 学习系列2:搭建Spring Cloud +gRPC集群项目
本项目基于Spring Boot 2.0.5+yidongnan/grpc-spring-boot-starter 2.0.1.RELEASE+SpringCloud Finchley.SR1,通过2个grpc-eureka-server模拟Eureka集群,多个可横向弹性扩容的grpc-spring-cloud-provider负责提供gRPC服务,再用grpc-spring-cloud-consumer作为服务消费者来调用gRPC服务,服务提供者和消费者通过grpc-springboot-cloud-lib模块来完成接口约定。
本项目已上传至GitHub:https://github.com/linshenkx/grpc-springboot-cloud
文章目录
- 一 项目结构
- 二 项目搭建
- 0 编写根项目pom文件
- 1 创建grpc-springboot-cloud-lib模块
- 2 创建grpc-eureka-server模块
- 3 创建grpc-spring-cloud-provider模块
- 3 创建grpc-spring-cloud-consumer模块
- 三 功能实现
- 1 在grpc-springboot-cloud-lib模块利用proto文件生成java代码
- 2 编写grpc-eureka-server
- (0)添加@EnableEurekaServer支持
- (1)编写application.yml文件
- (2)编写application-slave1.yml文件
- (2)编写application-slave2.yml文件
- 3 在grpc-spring-cloud-provider模块实现接口
- 4 在grpc-spring-cloud-consumer模块调用服务
- 四 项目启动
- 0 install工程
- 1 多profiles启动grpc-eureka-server
- 2 任意实例数量启动grpc-spring-cloud-provider
- 3 启动grpc-spring-cloud-consumer
- 五 测试
- 六 其他
一 项目结构
1 项目结构
根项目(com.linshen:grpc-springboot-cloud:1.0-SNAPSHOT)下有4个子模块:
- grpc-eureka-server:Eureka服务器
- grpc-springboot-cloud-lib:gRPC接口,包含原始proto文件,并负责将其转换为java代码
- grpc-spring-cloud-provider:Eureka客户端,同时也是gRPC服务提供者
- grpc-spring-cloud-consumer:Eureka客户端,同时也是gRPC服务消费者
2 模块功能
- 相互关系
根项目负责依赖版本管理,通过grpc-eureka-server实现Eureka集群,通过grpc-spring-cloud-provider负责提供gRPC服务,再用grpc-spring-cloud-consumer作为服务消费者来调用gRPC服务,服务提供者和消费者都作为Eureka客户端注册在Eureka集群中,消费者通过服务名发现提供者并通过grpc-springboot-cloud-lib模块来完成接口约定实现RPC。 - 对应MVC关系
- grpc-springboot-cloud-lib就是service(接口,为提供实现)
- grpc-spring-cloud-provider就相当于serviceImpl(service的实现类)
- grpc-spring-cloud-consumer就相当于controller
- 框架依赖
- grpc-eureka-server:除了必要的eureka-server依赖外再依赖actuator方便监控
- grpc-springboot-cloud-lib需要将proto文件转换成java类,故需依赖于protobuf-maven-plugin插件,另外还需要io.grpc下的grpc-all,否则生成的java类会提示缺少依赖
- grpc-spring-cloud-provider 要注册到eureka服务中心,需要web依赖,否则会报错
- grpc-spring-cloud-consumer 基本和provider一样
二 项目搭建
0 编写根项目pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.linshen</groupId>
<artifactId>grpc-springboot-cloud</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
</parent>
<modules>
<module>grpc-springboot-cloud-lib</module>
<module>grpc-eureka-server</module>
<module>grpc-spring-cloud-provider</module>
<module>grpc-spring-cloud-consumer</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-boot.version>${parent.version}</spring-boot.version>
<net-devh-grpc.version>2.0.1.RELEASE</net-devh-grpc.version>
<spring-cloud.version>Finchley.SR1</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--公共grpc模块-->
<dependency>
<groupId>com.linshen</groupId>
<artifactId>grpc-springboot-cloud-lib</artifactId>
<version>${project.version}</version>
</dependency>
<!--核心grpc-spring-boot依赖-->
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
<version>${net-devh-grpc.version}</version>
</dependency>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>${net-devh-grpc.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>
1 创建grpc-springboot-cloud-lib模块
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>grpc-springboot-cloud</artifactId>
<groupId>com.linshen</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>grpc-springboot-cloud-lib</artifactId>
<packaging>jar</packaging>
<properties>
<os.plugin.version>1.6.0</os.plugin.version>
<grpc.version>1.15.1</grpc.version>
<protoc.version>3.6.1</protoc.version>
<protobuf.plugin.version>0.6.1</protobuf.plugin.version>
</properties>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-all</artifactId>
<version>${grpc.version}</version>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>${os.plugin.version}</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>${protobuf.plugin.version}</version>
<extensions>true</extensions>
<configuration>
<protocArtifact>com.google.protobuf:protoc:${protoc.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>${project.basedir}/src/main/proto</protoSourceRoot>
<!--默认值-->
<!--<outputDirectory>${project.build.directory}/generated-sources/protobuf/java</outputDirectory>-->
<outputDirectory>${project.basedir}/src/main/java</outputDirectory>
<!--设置是否在生成java文件之前清空outputDirectory的文件,默认值为true,设置为false时也会覆盖同名文件-->
<clearOutputDirectory>false</clearOutputDirectory>
<!--更多配置信息可以查看https://www.xolstice.org/protobuf-maven-plugin/compile-mojo.html-->
</configuration>
<executions>
<execution>
<!--在执行mvn compile的时候会执行以下操作-->
<phase>compile</phase>
<goals>
<!--生成OuterClass类-->
<goal>compile</goal>
<!--生成Grpc类-->
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
2 创建grpc-eureka-server模块
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>grpc-eureka-server</artifactId>
<packaging>jar</packaging>
<name>grpc-eureka-server</name>
<parent>
<artifactId>grpc-springboot-cloud</artifactId>
<groupId>com.linshen</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3 创建grpc-spring-cloud-provider模块
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>grpc-spring-cloud-provider</artifactId>
<packaging>jar</packaging>
<name>grpc-spring-cloud-provider</name>
<parent>
<artifactId>grpc-springboot-cloud</artifactId>
<groupId>com.linshen</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>com.linshen</groupId>
<artifactId>grpc-springboot-cloud-lib</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3 创建grpc-spring-cloud-consumer模块
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>grpc-spring-cloud-consumer</artifactId>
<packaging>jar</packaging>
<name>grpc-spring-cloud-consumer</name>
<parent>
<artifactId>grpc-springboot-cloud</artifactId>
<groupId>com.linshen</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>com.linshen</groupId>
<artifactId>grpc-springboot-cloud-lib</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
三 功能实现
1 在grpc-springboot-cloud-lib模块利用proto文件生成java代码
(1) 编写proto文件
还是在src/main/proto目录编写下一个greeter.proto文件,如下
syntax = "proto3";
option java_package = "com.linshen.grpc.cloud.lib";
service Greeter {
rpc SayHello ( HelloRequest) returns ( HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
(2) 生成java代码
行protobuf插件的compile和compile-custom功能,生成java代码到src/main/java目录下
2 编写grpc-eureka-server
这里要实现两个Eureka-Server集群实现服务中心的高可用(两个确实很少,谈不上高可用,这里只是演示一下),需要通过不同的profile来启动不同的Eureka-Server,故需要application.yml、application-slave1.yml和application-slave2.yml三个文件。application.yml定义了公共属性和actuator配置,并且配置了默认active-profile是slave1。其中slave1运行在端口8781上,slave2运行在端口8782上,两者的域名(也可以设置成ip)通过环境变量传入,不传的话默认是在本地(localhost)运行。
最终的工程目录应该如下
(0)添加@EnableEurekaServer支持
(1)编写application.yml文件
spring:
application:
name: grpc-eureka-server
profiles:
active: ${grpc-eureka-profile:slave1}
# actuator management
management:
endpoint:
health:
show-details: always
endpoints:
web:
exposure:
include: '*'
# actuator info
info:
app:
encoding:UTF-8
java.source:1.8
java.traget:1.8
(2)编写application-slave1.yml文件
server:
port: 8781
eureka:
client:
service-url:
defaultZone: http://${cloudServerSlave2:localhost}:8782/eureka/
instance:
hostname: ${cloudServerSlave1:localhost}
server:
enable-self-preservation: false
(2)编写application-slave2.yml文件
server:
port: 8782
eureka:
client:
service-url:
defaultZone: http://${cloudServerSlave1:localhost}:8781/eureka/
instance:
hostname: ${cloudServerSlave2:localhost}
server:
enable-self-preservation: false
3 在grpc-spring-cloud-provider模块实现接口
最终的工程目录应该如下
(0)添加@EnableDiscoveryClient支持
(1) 编写application文件
如下,server.port和grpc.server.port都为0,这样启动的时候服务端口和grpc端口都会随机分配,就不会重复占用了,eureka.instance.instance-id也是通过应用名+uuid的方式来避免重复
server:
port: 0
grpc:
server:
port: 0
spring:
application:
name: grpc-spring-cloud-provider
eureka:
client:
service-url:
defaultZone: http://${cloudServerSlave1:localhost}:8781/eureka/,http://${cloudServerSlave2:localhost}:8782/eureka/
instance:
instance-id: ${spring.application.name}:${random.uuid}
logging:
level:
org.springframework.web.servlet.DispatcherServlet: DEBUG
# actuator management
management:
endpoint:
health:
show-details: always
endpoints:
web:
exposure:
include: '*'
# actuator info
info:
app:
encoding:UTF-8
java.source:1.8
java.traget:1.8
name:grpc-spring-cloud-provider
(2) 编写GreeterService类
package com.linshen.grpcspringcloudprovider;
import com.linshen.grpc.cloud.lib.GreeterGrpc;
import com.linshen.grpc.cloud.lib.GreeterOuterClass;
import io.grpc.stub.StreamObserver;
import lombok.extern.slf4j.Slf4j;
import net.devh.springboot.autoconfigure.grpc.server.GrpcService;
@Slf4j
@GrpcService(GreeterOuterClass.class)
public class GreeterService extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(GreeterOuterClass.HelloRequest request, StreamObserver<GreeterOuterClass.HelloReply> responseObserver) {
String message = "Hello " + request.getName();
final GreeterOuterClass.HelloReply.Builder replyBuilder = GreeterOuterClass.HelloReply.newBuilder().setMessage(message);
responseObserver.onNext(replyBuilder.build());
responseObserver.onCompleted();
log.info("Returning " +message);
}
}
4 在grpc-spring-cloud-consumer模块调用服务
最终的工程目录应该如下
(0)添加@EnableDiscoveryClient支持
(1) 编写application文件
不需要配置grpc.server.port,其他的和provider大致相同
(2)编写GrpcClientService服务类
注意这里的@GrpcClient的value是provider的spring.application.name
package com.linshen.grpcspringcloudconsumer;
import com.linshen.grpc.cloud.lib.GreeterGrpc;
import com.linshen.grpc.cloud.lib.GreeterOuterClass;
import io.grpc.Channel;
import net.devh.springboot.autoconfigure.grpc.client.GrpcClient;
import org.springframework.stereotype.Service;
@Service
public class GrpcClientService {
@GrpcClient("grpc-spring-cloud-provider")
private Channel serverChannel;
public String sendMessage(String name) {
GreeterGrpc.GreeterBlockingStub stub= GreeterGrpc.newBlockingStub(serverChannel);
GreeterOuterClass.HelloReply response = stub.sayHello(GreeterOuterClass.HelloRequest.newBuilder().setName(name).build());
return response.getMessage();
}
}
(3)编写GrpcClientController类
package com.linshen.grpcspringcloudconsumer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GrpcClientController {
@Autowired
private GrpcClientService grpcClientService;
@RequestMapping("/")
public String printMessage(@RequestParam(defaultValue = "LinShen") String name) {
return grpcClientService.sendMessage(name);
}
}
四 项目启动
0 install工程
在根工程下运行mvn install -Dmaven.test.skip=true
,这一步将项目install到本地仓库,目的是使插件能正常工作,避免出现Failed to collect dependencies
问题,如果只是在本地运行的话没遇到依赖问题,这一步也可以省略。
1 多profiles启动grpc-eureka-server
分别指定active-profiles为slave1和slave2启动grpc-eureka-server
2 任意实例数量启动grpc-spring-cloud-provider
重复启动grpc-spring-cloud-provider,这里我启动了两次,获得两个实例
3 启动grpc-spring-cloud-consumer
简单启动一个消费者即可
最终运行情况应该如下
五 测试
打开eureka-server1,即http://localhost:8781/,如下图,可以看到所有的服务实例到注册到服务中心了
打开eureka-server2,即http://localhost:8782/,如下图,可以看到服务实例信息还没有eureka-server1完整
这个时候再打开consumer,即http://localhost:49236/,如下图,运行正常
把Grpc-Eureka-Server-Slave1杀掉,再查看eureka-server2,可以发现注册信息已经同步到slave2了,这个时候consumer仍可正常使用
六 其他
1 相关文章
gRPC-Java指导:https://grpc.io/docs/tutorials/basic/java.html
gRPC 官方文档中文版1.0:https://doc.oschina.net/grpc
Maven Protocol Buffers Plugin 插件指南:https://www.xolstice.org/protobuf-maven-plugin/index.html
Spring Boot 2+gRPC 学习系列1:搭建Spring Boot 2+gRPC本地项目:https://blog.****.net/alinyua/article/details/83030149
Spring Boot 2+gRPC 学习系列2:搭建Spring Cloud +gRPC集群项目:https://blog.****.net/alinyua/article/details/83043823
Spring Boot 2+Dubbo 学习系列1:使用Docker部署zookeeper:https://blog.****.net/alinyua/article/details/81016734
Spring Boot 2+Dubbo 学习系列2:搭建Spring Boot 2+Dubbo+Zookeeper集群:https://blog.****.net/alinyua/article/details/81019925
Spring Boot 2+Dubbo 学习系列3:dubbo-ops 之 Dubbo Admin:https://blog.****.net/alinyua/article/details/81034023
2 GitHub项目
linshenkx/grpc-springboot-cloud(Spring Cloud +gRPC集群项目):https://github.com/linshenkx/grpc-springboot-cloud
linshenkx/grpc-springboot-lin(Spring Boot 2+gRPC本地项目):https://github.com/linshenkx/grpc-springboot-lin
linshenkx/spring-cloud-lin(Spring Boot 2整合Spring Cloud):https://github.com/linshenkx/spring-cloud-lin
gRPC-Java的GitHub地址:https://github.com/grpc/grpc-java
yidongnan/grpc-spring-boot-starter的GitHub地址:https://github.com/yidongnan/grpc-spring-boot-starter