Spring Boot 2+gRPC 学习系列2:搭建Spring Cloud +gRPC集群项目

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/alinyua/article/details/83043823

本项目基于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

一 项目结构

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目录下
lib

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)运行。
最终的工程目录应该如下
eureka-server

(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模块实现接口

最终的工程目录应该如下
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

简单启动一个消费者即可

最终运行情况应该如下
Run Dashboard

五 测试

打开eureka-server1,即http://localhost:8781/,如下图,可以看到所有的服务实例到注册到服务中心了
server1
打开eureka-server2,即http://localhost:8782/,如下图,可以看到服务实例信息还没有eureka-server1完整
server2
这个时候再打开consumer,即http://localhost:49236/,如下图,运行正常
consumer
把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.csdn.net/alinyua/article/details/83030149
Spring Boot 2+gRPC 学习系列2:搭建Spring Cloud +gRPC集群项目:https://blog.csdn.net/alinyua/article/details/83043823

Spring Boot 2+Dubbo 学习系列1:使用Docker部署zookeeper:https://blog.csdn.net/alinyua/article/details/81016734
Spring Boot 2+Dubbo 学习系列2:搭建Spring Boot 2+Dubbo+Zookeeper集群:https://blog.csdn.net/alinyua/article/details/81019925
Spring Boot 2+Dubbo 学习系列3:dubbo-ops 之 Dubbo Admin:https://blog.csdn.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

猜你喜欢

转载自blog.csdn.net/alinyua/article/details/83043823
今日推荐