SpringBoot集成gRPC微服务工程搭建实践

栏目: Java · 发布时间: 5年前

内容简介:本文将使用本文假设读者已经了解以下相关知识:由于是初步实现微服务,不会考虑过多的细节,现阶段只需要能够使用gRPC正常通信,后续计划会发布到

本文将使用 MavengRPCProtocol buffersDockerEnvoy工具 构建一个简单微服务工程,笔者所使用的示例工程是以前写的一个 Java 后端工程,因为最近都在 学习微服务相关的知识,所以利用起来慢慢的把这个工程做成微服务化应用。在实践过程踩过很多坑,主要是经验不足对微服务还是停留在萌新阶段,通过本文 记录创建微服务工程碰到一些问题,此次实践主要是解决以下问题:

  • 如何解决、统一服务工程依赖管理
  • SpringBoot集成gRPC
  • 管理Protocol buffers文件
  • 使用Envoy代理访问gRPC
  • 部署到Docker

本文假设读者已经了解以下相关知识:

  • Maven
  • Envoy
  • gRPC
  • Protocol buffers
  • SpringBoot
  • Docker

由于是初步实现微服务,不会考虑过多的细节,现阶段只需要能够使用gRPC正常通信,后续计划会发布到 k8s 中,使用 istio 实现来服务网格。

使用Maven

现在比较流行的构建工具有 MavenGradle ,现阶段后端开发大多数都是用的Maven所以本工程也使用Maven来构建项目,当然使用Gradle也可以两者概念大都想通,不同的地方大多是实现和配置方式不一致。

使用项目继承

根据 Maven 的POM文件继承特性,将工程分不同的模块,所有的模块都继承父 pom.xml依赖插件 等内容,这样就可以实现统一管理,并方便以后管理、维护。先看一下大概的项目结构:

AppBubbleBackend            (1)
├── AppBubbleCommon
├── AppBubbleSmsService     (2)
├── AppBubbleUserService
├── docker-compose.yaml     (3)
├── pom.xml
├── protos                  (4)
│   ├── sms
│   └── user
└── scripts                 (5)
    ├── docker
    ├── envoy
    ├── gateway
    └── sql

复制代码

以下是各个目录的用处简述,详细的用处文章后面都会提到,先在这里列出个大概:

  1. 工程主目录
  2. 单个服务工程目录(模块)
  3. docker-compose发布文件
  4. 存放.proto文件
  5. 发布、编译时用到的脚本文件

知道大概的项目工程结构后我们创建一个父 pom.xml 文件,放在 AppBubbleBackend 目录下面:

<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.bubble</groupId>
    <artifactId>bubble</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>
    
    <modules>
        <module>AppBubbleSmsService</module>
        <module>AppBubbleCommon</module>
        <module>AppBubbleUserService</module>
    </modules>

    <!-- 省略其他部分 -->
</project>

复制代码

因为使用 SpringBoot 构架,所以主 pom.xml 文件继承自 SpringBoot 的POM文件。 有了主 pom.xml 后然后使每个模块的 pom.xml 都继承自 主 pom.xml 文件:

<?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>
	<parent>
		<groupId>com.bubble</groupId>
		<artifactId>bubble</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
 	<artifactId>sms</artifactId>
	<version>0.0.1-SNAPSHOT</version>

   <!-- 省略其他部分 -->
</project>
复制代码

经过上面的配置后,所有的模块都会继承 AppBubbleBackend 中的 pom.xml 文件,这样可以很方便的更改依赖、配置等信息。

依赖管理

Maven提供依赖中心化的管理机制,通过项目继承特性所有对 AppBubbleBackend/pom.xml 所做的更改都会对其他模块产生影响,详细的依赖管理 内容可查看官方文档。

<dependencyManagement>
        <dependencies>
            <!-- gRPC -->
            <dependency>
                <groupId>io.grpc</groupId>
                <artifactId>grpc-netty-shaded</artifactId>
                <version>${grpc.version}</version>
            </dependency>
        </dependencies>
</dependencyManagement>
复制代码

通过 dependencyManagement 标签来配置依赖,这样可以就可以实现统一依赖的管理,并且还可以添加公共依赖。

插件管理

使用 pluginManagement 可以非常方便的配置插件,因为项目中使用了 Protocol buffers 需要集成相应的插件来生成Java源文件:

<pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.xolstice.maven.plugins</groupId>
                    <artifactId>protobuf-maven-plugin</artifactId>
                    <version>0.5.1</version>
                    <executions>
                        <execution>
                            <goals>
                                <goal>compile</goal>
                                <goal>compile-custom</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
</pluginManagement>
复制代码

Protocol buffers 插件的完整配置参数,可以这这里找到。

Profile

使用 Profile 的目的是为了区分生成 Docker 镜像时的一些特殊配置,示例工程只配置了一个 docker-build 的profile:

<profiles>
        <profile>
            <id>docker-build</id>
            <properties>
                <jarName>app</jarName>
            </properties>
        </profile>
    </profiles>

     <properties>
        <jarName>${project.artifactId}-${project.version}</jarName>
    </properties>
    
    <build>
        <finalName>${jarName}</finalName>
    </build>
复制代码

如果使用 mvn package -P docker-build 命令生成jar包时,相应的输出文件名是 app.jar 这样可以方便在 Dockerfile 中引用文件,而不需要使用 ${project.artifactId}-${project.version} 的形式来查找输出的jar这样可以省去了解析 pom.xml 文件。如果还需要特殊的参数可以或者不同的行为,可以添加多个Profile,这样配置起来非常灵活。

Protocol buffers文件管理

因为是使用微服务开发,而且RPC通信框架是使用的gRPC,所以每个服务工程都会使用 .proto 文件。服务工程之间又会有使用同一份 .proto 文件的需求,比如在进行RPC通信时服务提供方返回的消息 Test 定义在 a.proto 文件中,那么在使用方在解析消息时也同样需要 a.proto 文件来将接收到的消息转换成 Test 消息,因此管理 .proto 文件也有一些小麻烦。关于 Protocol buffers 的使用可参考官方文档。

Protocol buffers文件管理规约

在我们的示例项目中使用集中管理的方式,即将所有的.proto文件放置在同一个目录(AppBubbleBackend/protos)下并按服务名称来划分:

├── sms
│   ├── SmsMessage.proto
│   └── SmsService.proto
└── user
    └── UserMessage.proto
复制代码

还可以将整个目录放置在一个单独的git仓库中,然后在项目中使用 git subtree 来管理文件。

Protocol buffers 插件配置

有了上面的目录结构后,就需要配置一下 Protocol buffers 的编译插件来支持这种 .proto 文件的组织结构。在讲解如何配置插件解决.proto文件的编译问题之前,推荐读者了解一下插件的配置文档: Xolstice Maven Plugins 。在我们的工程中使用如下配置:

<plugin>
        <groupId>org.xolstice.maven.plugins</groupId>
        <artifactId>protobuf-maven-plugin</artifactId>
        <version>0.5.1</version>
        <configuration >
            <protocArtifact>com.google.protobuf:protoc:3.5.1-1:exe:${os.detected.classifier}</protocArtifact>
            <pluginId>grpc-java</pluginId>
            <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.17.1:exe:${os.detected.classifier}</pluginArtifact>
            <additionalProtoPathElements combine.children="append" combine.self="append">
                <additionalProtoPathElement>${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis</additionalProtoPathElement>
                <additionalProtoPathElement>${GOPATH}/src</additionalProtoPathElement>
            </additionalProtoPathElements>
            <protoSourceRoot>${protos.basedir}</protoSourceRoot>
            <writeDescriptorSet>true</writeDescriptorSet>
            <includeDependenciesInDescriptorSet>true</includeDependenciesInDescriptorSet>
        </configuration>
        <!-- ... -->
    </plugin>
复制代码

首先上面的插件配置使用 protoSourceRoot 标签将 Protocol buffers 的源文件目录更改成 AppBubbleBackend/protos 目录,因为工程中使用了 googleapis 来定义服务接口,所以需要使用添加 additionalProtoPathElement 标签添加额外的依赖文件。注意这个插件的配置是在 AppBubbleBackend/pom.xml 文件中的,服务工程都是继承此文件的。在父POM文件配置好以后,再看一下服务工程的插件配置:

<plugins>
		<plugin>
				<groupId>org.xolstice.maven.plugins</groupId>
				<artifactId>protobuf-maven-plugin</artifactId>
				<configuration>
					<includes>
						<include>${project.artifactId}/*.proto</include>
						<include>user/*.proto</include>
					</includes>
				</configuration>
			</plugin>
		</plugins>
复制代码

服务工程主要使用 includes 标签,将需要的 .proto 文件包含在编译脚本中, includes 标签中的 include 只是一个指定匹配 .proto 文件的匹配模式, <include>${project.artifactId}/*.proto</include> 意思是 AppBubbleBackend/protos/${project.artifactId} 目录下的所有以 .proto 文件结尾的文件,如果服务工程有多个依赖可以将需要依赖的文件也添加到编译服务中,如上面的 <include>user/*.proto</include> 就将 AppBubbleBackend/protos/user 中的 .proto 文件添加进来,然后进行整体的编译。

gRPC

gRPC是由Google开源的RPC通信框架,gRPC使用 Protocol buffers 定义服务接口并自动生成gRPC相关代码,有了这些代码后就可以非常方便的实现gRPC服务端和gPRC客户端,过多的细节就不细说了先看一下如何使用在 SpringBoot 中使用gRPC。

运行gRPC服务

利用 ApplicationRunner 接口,在 SprintBoot 中运行gRPC服非常方便,只需要像下面代码一样就可以运行一个简单的gRPC服务。

package com.bubble.sms.grpc;

@Component
public class GrpcServerInitializer implements ApplicationRunner {


    @Autowired
    private List<BindableService> services;

    @Value("${grpc.server.port:8090}")
    private int port;

    @Override
    public void run(ApplicationArguments args) throws Exception {

        ServerBuilder serverBuilder = ServerBuilder
                .forPort(port);

        if (services != null && !services.isEmpty()) {
            for (BindableService bindableService : services) {
                serverBuilder.addService(bindableService);
            }
        }
        Server server = serverBuilder.build();
        serverBuilder.intercept(TransmitStatusRuntimeExceptionInterceptor.instance());
        server.start();
        startDaemonAwaitThread(server);
    }


    private void startDaemonAwaitThread(Server server) {
        Thread awaitThread = new Thread(() -> {
            try {
                server.awaitTermination();
            } catch (InterruptedException ignore) {
                
            }
        });
        awaitThread.setDaemon(false);
        awaitThread.start();
    }
}

复制代码

Envoy代理

gRPC服务运行起来后就需要进行调试了,比如使用 curlchrome 等工具向gRPC服务发起Restful请求,实际上gRPC的调试并没有那么简单。一开始的方案是使用了 gRPC-gateway ,为每个服务都启动一个网关将 Http 1.x 请求转换并发送到gRPC服务。然而 gRPC-gateway 只有 go 语言的版本,并没有 Java 语言的版本,所有在编译和使用中比较困难,后来发现了 Envoy 提供了 envoy.grpc_json_transcoder 这个http过滤器,可以很方便的将 RESTful JSON API 转换成gRPC请求并发送给gRPC服务器。

envoy 的相关配置都放置在 AppBubbleBackend/scripts/envoy 目录中,里面的 envoy.yaml 是一份简单的配置文件:

static_resources:
 listeners:
 - name: grpc-8090
   address:
     socket_address: { address: 0.0.0.0, port_value: 8090 }
   filter_chains:
   - filters:
     - name: envoy.http_connection_manager
       config:
         stat_prefix: sms_http
         codec_type: AUTO
         # 省略部分配置
         http_filters:
         - name: envoy.grpc_json_transcoder
           config:
             proto_descriptor: "/app/app.protobin"
             services: ["sms.SmsService"]
             match_incoming_request_route: true
             print_options:
              add_whitespace: true
              always_print_primitive_fields: true
              always_print_enums_as_ints: false
              preserve_proto_field_names: false
# 省略部分配置              
复制代码

使用 envoy.grpc_json_transcoder 过滤器的主要配置是 proto_descriptor 选项,该选项指向一个 proto descriptor set 文件。 AppBubbleBackend/scripts/envoy/compile-descriptor.sh 是编译 proto descriptor set 的脚本文件, 运行脚本文件会在脚本目录下生成一个 app.protobin 的文件,将此文件设置到 envoy.grpc_json_transcoder 就可大致完成了 envoy 的代理配置。

使用Docker发布

经过上面的一系统准备工作之后,我们就可以将服务发布到docker中了,Docker相关的文件都放置中 AppBubbleBackend/scripts/docker 和一个 AppBubbleBackend/docker-compose.yaml 文件。在发布时使用单个 Dockerfile 文件来制作服务镜像:

FROM rcntech/ubuntu-grpc:v0.0.5
EXPOSE 8080
EXPOSE 8090

#将当前目录添加文件到/bubble
ARG APP_PROJECT_NAME
#复制父pom.xml
ADD /pom.xml /app/pom.xml
ADD /protos /app/protos
ADD $APP_PROJECT_NAME /app/$APP_PROJECT_NAME
ADD scripts/gateway /app/gateway
ADD scripts/docker/entrypoint.sh /app/entrypoint.sh
RUN chmod u+x /app/entrypoint.sh

ENTRYPOINT ["/app/entrypoint.sh"]
复制代码

有了 Dockerfile 文件后,在 docker-compose.yaml 里面做一些配置就能将服务打包成镜像:

sms:
  build:
    context: ./
    dockerfile: scripts/docker/Dockerfile
    args:
      APP_PROJECT_NAME: "AppBubbleSmsService"
  environment:
   APOLLO_META: "http://apollo-configservice-dev:8080"
   APP_PROJECT_NAME: "AppBubbleSmsService"
   ENV: dev
复制代码

同时编写了一个通用的 entrypoint.sh 脚本文件来启动服务器:

#!/bin/bash

export GOPATH=${HOME}/go
export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin

rootProjectDir="/app"
projectDir="${rootProjectDir}/${APP_PROJECT_NAME}"

cd ${rootProjectDir}/AppBubbleCommon
./mvnw install

cd $projectDir
#打包app.jar
./mvnw package -DskipTests -P docker-build
#编译proto文件
./mvnw protobuf:compile protobuf:compile-custom -P docker-build


# Run service
java -jar ${projectDir}/target/app.jar
复制代码

entrypoint.sh 脚本中将服务工程编译成 app.jar 包再运行服务。还有 envoy 代理也要启动起来这样我们就可以使用 curl 或其他工具直接进行测试了。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Vim实用技巧

Vim实用技巧

[英] Drew Neil / 杨源、车文隆 / 人民邮电出版社 / 2014-5-1 / 59.00元

vim是一款功能丰富而强大的文本编辑器,其代码补全、编译及错误跳转等方便编程的功能特别丰富,在程序员中得到非常广泛的使用。vim能够大大提高程序员的工作效率。对于vim高手来说,vim能以与思考同步的速度编辑文本。同时,学习和熟练使用vim又有一定的难度。 《vim实用技巧》为那些想要提升自己的程序员编写,阅读本书是熟练地掌握高超的vim技巧的必由之路。全书共21章,包括121个技巧。每一章......一起来看看 《Vim实用技巧》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码