前言
本文將使用maven、grpc、protocol buffers、docker、envoy等工具構建一個簡單微服務工程,筆者所使用的示例工程是以前寫的一個java后端工程,因為最近都在 學習微服務相關的知識,所以利用起來慢慢的把這個工程做成微服務化應用。在實踐過程踩過很多坑,主要是經驗不足對微服務還是停留在萌新階段,通過本文 記錄創建微服務工程碰到一些問題,此次實踐主要是解決以下問題:
- 如何解決、統一服務工程依賴管理
- springboot集成grpc
- 管理protocol buffers文件
- 使用envoy代理訪問grpc
- 部署到docker
本文假設讀者已經了解以下相關知識:
- maven
- envoy
- grpc
- protocol buffers
- springboot
- docker
由于是初步實現微服務,不會考慮過多的細節,現階段只需要能夠使用grpc正常通信,后續計劃會發布到k8s中,使用istio實現來服務網格。
使用maven
現在比較流行的構建工具有maven和gradle,現階段后端開發大多數都是用的maven所以本工程也使用maven來構建項目,當然使用gradle也可以兩者概念大都想通,不同的地方大多是實現和配置方式不一致。
使用項目繼承
根據maven的pom文件繼承特性,將工程分不同的模塊,所有的模塊都繼承父pom.xml的依賴、插件等內容,這樣就可以實現統一管理,并方便以后管理、維護。先看一下大概的項目結構:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
appbubblebackend ( 1 ) ├── appbubblecommon ├── appbubblesmsservice ( 2 ) ├── appbubbleuserservice ├── docker-compose.yaml ( 3 ) ├── pom.xml ├── protos ( 4 ) │ ├── sms │ └── user └── scripts ( 5 ) ├── docker ├── envoy ├── gateway └── sql |
以下是各個目錄的用處簡述,詳細的用處文章后面都會提到,先在這里列出個大概:
- 工程主目錄
- 單個服務工程目錄(模塊)
- docker-compose發布文件
- 存放.proto文件
- 發布、編譯時用到的腳本文件
知道大概的項目工程結構后我們創建一個父pom.xml文件,放在appbubblebackend目錄下面:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<?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文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<?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所做的更改都會對其他模塊產生影響,詳細的依賴管理 內容可查看官方文檔。
1
2
3
4
5
6
7
8
9
10
|
<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源文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<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)下并按服務名稱來劃分:
1
2
3
4
5
|
├── sms │ ├── smsmessage.proto │ └── smsservice.proto └── user └── usermessage.proto |
還可以將整個目錄放置在一個單獨的git倉庫中,然后在項目中使用git subtree來管理文件。
protocol buffers 插件配置
有了上面的目錄結構后,就需要配置一下protocol buffers的編譯插件來支持這種.proto文件的組織結構。在講解如何配置插件解決.proto文件的編譯問題之前,推薦讀者了解一下插件的配置文檔:xolstice maven plugins。在我們的工程中使用如下配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<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文件配置好以后,再看一下服務工程的插件配置:
1
2
3
4
5
6
7
8
9
10
11
12
|
<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服務。
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
|
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服務運行起來后就需要進行調試了,比如使用curl、chrome等工具向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是一份簡單的配置文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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文件來制作服務鏡像:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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里面做一些配置就能將服務打包成鏡像:
1
2
3
4
5
6
7
8
9
10
|
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腳本文件來啟動服務器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#!/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或其他工具直接進行測試了。
總結
搭建這個工程大概摸索了一周的時間,主要的時間是花在了protocol buffers文件的管理與使用envoy作為代理調試grpc服務上。文章中的示例工程已經傳到了github: appbubblebackend 后面會打算慢慢的完善這個應用,這是個簡單的短視屏應用除了服務器還包含了android和ios端,等到將后端微服務化為開源出來供學習交流使用。
參考引用
grpc官方文檔
protocol buffers maven 插件文檔
protocol buffers官方文檔
grpc 官方文檔中文版
grpc-json transcoder
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://juejin.im/post/5c3ed5a2f265da616f703399