基于 Spring Cloud 微服务框架的应用开发治理

本文将以 Spring Cloud 为例,讲述 Erda 中构建微服务架构应用的最佳实践。

微服务架构相较于传统的单体应用,最大的变化在于服务拆分,具体来说是根据业务领域进行业务组件拆分,例如采用领域驱动设计(DDD),按照业务领域划分为多个微服务,服务之间相互协同完成业务功能。

基于 Spring Cloud 微服务框架的应用开发治理 - 图1

微服务架构解决了众多单体应用的问题,同时也增加了架构的复杂性。下文将针对技术架构的改变,介绍如何在 Erda 上完成微服务应用的研发和治理,主要包括以下内容:

  • 服务的发现和调用
  • 服务的配置集中管理
  • 服务的 API 开放和测试
  • 服务的可观测治理

tip 提示 微服务应用天然属于分布式应用,其涉及的分布式架构解决方案,例如分布式缓存、队列、事务等,本文不作讨论。

微服务设计

首先创建一个微服务项目名为 bestpractice(分配 4 核 CPU 和 8 GB 内存),并创建微服务应用 echo-web 和 echo-service(应用类型为 业务应用,仓库选择 使用内部仓库)。

echo-web 模拟业务聚合层,对内调用 echo-service 完成服务功能,对外通过 Erda 的 API 网关提供 Open API,其功能包括:

  • 提供 /api/content API 调用 echo-service 实现对 content 资源的增删改查。
  • 提供 /api/error API 调用 echo-service 制造异常。

echo-service 模拟单一业务领域服务层,处理领域内业务逻辑,并完成持久化,其功能包括:

  • 提供 /echo/content API 实现对 content 资源的增删改查。
  • 提供 /echo/error API 制造异常。

echo-web 和 echo-service 通过 Erda 的微服务组件注册中心实现服务接口的注册与发现,通过微服务组件配置中心实现配置统一管理和热加载。

API 设计

进入 echo-web 应用 > API > 新建文档,选择分支 feature/api,名称为 echo-web。

tip 提示 echo-web 为 Service 名称,与 dice.yml 中的服务名称保持一致。

数据类型

  1. content: 参数名称 content”, 类型 String
  2. ContentRequest: 参数名称 ContentRequest”, 类型 Object”, 其参数引用类型 content
  3. ContentResponse: 参数名称 ContentResponse”, 类型 Object”, 其参数引用类型 content

APIs

  • echo web 应用 API

    1. 1. /api/content
    2. 1. Method: GET
    3. Response
    4. MediaType: application/json
    5. 类型: ContentResponse
    6. 2. Method: PUT
    7. Body
    8. MediaType: application/json
    9. 类型: ContentRequest
    10. 3. Method: POST
    11. Body
    12. MediaType: application/json
    13. 类型: ContentRequest
    14. 4. Method: DELETE
    15. Response
    16. MediaType: application/json
    17. 类型:Object
    18. 参数名称: deleted, 类型: Boolean
    19. 2. /api/error
    20. 1. MethodPOST
    21. Body
    22. MediaType: application/json
    23. 类型:Object
    24. 参数名称: type, 类型: String

    点击 发布,填写 API 名称为 Echo 应用 API,API ID 为 echo-web,发布版本为 1.0.0。

    基于 Spring Cloud 微服务框架的应用开发治理 - 图2

  • echo service 应用 API

    1. 1. /echo/content
    2. 1. Method: GET
    3. Response
    4. MediaType: application/json
    5. 类型: ContentResponse
    6. 2. Method: PUT
    7. Body
    8. MediaType: application/json
    9. 类型: ContentRequest
    10. 3. Method: POST
    11. Body
    12. MediaType: application/json
    13. 类型: ContentRequest
    14. 4. Method: DELETE
    15. Response
    16. MediaType: application/json
    17. 类型:Object
    18. 参数名称: deleted, 类型: Boolean
    19. 1. /echo/error
    20. 1. Method: POST
    21. Body
    22. MediaType: application/json
    23. 类型:Object
    24. 参数名称: type, 类型: String

    进入 DevOps 平台 > API 管理 > API 集市 查看、管理 API。

    基于 Spring Cloud 微服务框架的应用开发治理 - 图3

    tip 提示

    • 发布后的 API 文档默认为 私有,仅关联项目应用下的成员可查看。
    • 若为企业级 Open API,可设置为 共有,便于组织下所有用户查看。

应用开发

echo service 应用

基于 Spring Boot 开发框架创建应用

使用 IDEA 创建 Maven 项目(Java 1.8)并配置 Spring Boot 框架,目录结构如下:

  1. ├── pom.xml
  2. └── src
  3. ├── main
  4. ├── java/io/terminus/erda/bestpractice/echo
  5. ├── Application.java
  6. └── controller
  7. └── DefaultController.java
  8. └── resources
  9. └── application.yml
  10. └── test
  11. └── java

编辑 pom.xml 文件:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <groupId>io.terminus.erda.bestpractice.echo</groupId>
  7. <artifactId>echo-service</artifactId>
  8. <version>1.0-SNAPSHOT</version>
  9. <properties>
  10. <maven.compiler.source>8</maven.compiler.source>
  11. <maven.compiler.target>8</maven.compiler.target>
  12. </properties>
  13. <parent>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter-parent</artifactId>
  16. <version>2.1.4.RELEASE</version>
  17. <relativePath/>
  18. </parent>
  19. <dependencies>
  20. <dependency>
  21. <groupId>org.springframework.boot</groupId>
  22. <artifactId>spring-boot-starter-web</artifactId>
  23. </dependency>
  24. </dependencies>
  25. <dependencyManagement>
  26. <dependencies>
  27. <dependency>
  28. <groupId>org.springframework.cloud</groupId>
  29. <artifactId>spring-cloud-dependencies</artifactId>
  30. <version>Greenwich.SR1</version>
  31. <type>pom</type>
  32. <scope>import</scope>
  33. </dependency>
  34. </dependencies>
  35. </dependencyManagement>
  36. <build>
  37. <finalName>echo-service</finalName>
  38. <plugins>
  39. <plugin>
  40. <groupId>org.springframework.boot</groupId>
  41. <artifactId>spring-boot-maven-plugin</artifactId>
  42. <configuration>
  43. <executable>true</executable>
  44. <mainClass>io.terminus.erda.bestpractice.echo.Application</mainClass>
  45. </configuration>
  46. <executions>
  47. <execution>
  48. <goals>
  49. <goal>repackage</goal>
  50. </goals>
  51. </execution>
  52. </executions>
  53. </plugin>
  54. </plugins>
  55. </build>
  56. </project>

tip 提示

pom.xml 中 build 部分使用 spring-boot-maven-plugin 构建 Fat JAR,并会在后续制品中作为可执行的 JAR 文件使用。

编辑 Application.java 文件:

  1. package io.terminus.erda.bestpractice.echo;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class Application {
  6. public static void main(String[] args) {
  7. SpringApplication.run(Application.class, args);
  8. }
  9. }

编辑 DefaultController.java 文件增加健康检查 API:

  1. package io.terminus.erda.bestpractice.echo.controller;
  2. import org.springframework.web.bind.annotation.RequestMapping;
  3. import org.springframework.web.bind.annotation.RequestMethod;
  4. import org.springframework.web.bind.annotation.RestController;
  5. @RestController
  6. @RequestMapping(value = "/api")
  7. public class DefaultController {
  8. @RequestMapping(value = "/healthy", method = RequestMethod.GET)
  9. public boolean healthy() {
  10. return true;
  11. }
  12. }

tip 提示

健康检查 API 用于 dice.yml 中,提供给 Kubernetes 进行 liveness 和 readiness 检查,需保证其返回 200 时服务健康可用。

关联 Erda Git 远程仓库并推送代码

  1. git remote add erda https://erda.cloud/trial/dop/bestpractice/echo-web
  2. git push -u erda --all
  3. git push -u erda --tags

echo web 应用

参考上文 echo service 应用的开发过程,完成以下内容:

  1. 基于 Spring Boot 开发框架创建应用。
  2. 关联 Erda Git 远程仓库并推送代码。

CI/CD 流水线

下文以 echo-service 应用为例编排流水线,可供 echo-web 应用参考。

pipeline.yml

进入 echo-service 应用新建流水线,选择 java-boot-maven-erda 模板,切换代码模式开始编辑:

  1. ...
  2. dice.yml中的服务名:
  3. cmd: java -jar /target/jar包的名称
  4. copys:
  5. - ${java-build:OUTPUT:buildPath}/target/jar包的名称:/target/jar包的名称
  6. image: registry.cn-hangzhou.aliyuncs.com/terminus/terminus-openjdk:v11.0.6
  7. ...

将模版中上述部分修改为:

  1. echo-service:
  2. cmd: java ${java-build:OUTPUT:JAVA_OPTS} -jar /target/echo-service
  3. copys:
  4. - ${java-build:OUTPUT:buildPath}/target/echo-service.jar:/target/echo-service.jar
  5. - ${java-build:OUTPUT:buildPath}/spot-agent:/
  6. image: registry.cn-hangzhou.aliyuncs.com/terminus/terminus-openjdk:v11.0.6

tip 提示

pipeline.yml 中用于替换 JAR 包的名称需与 echo-service 应用 pom.xml 的 build.finalName 保持一致,用于替换 dice.yml 中的服务名需与 dice.yml 保持一致。

dice.yml

在代码仓库添加 dice.yml 文件并进行编辑,新增节点后按照图示填写配置:

基于 Spring Cloud 微服务框架的应用开发治理 - 图4

tip 提示

  • dice.yml 中的服务名需与 pipeline.yml 保持一致。
  • 健康检查端口需与应用监听的端口保持一致,Spring Boot 框架内置的 Tomcat 服务器默认监听 8080 端口。

完成应用开发后,可通过执行流水线任务实现应用的构建发布。

基于 Spring Cloud 微服务框架的应用开发治理 - 图5

服务注册与发现

下文将基于 Spring Cloud 和 Erda 的注册中心开发服务接口的注册与发现。

echo service

Erda 的注册中心基于 Nacos 实现(具体请参见 使用 Nacos 云服务),需在 pom.xml 文件中添加 Nacos 依赖:

  1. <dependency>
  2. <groupId>com.alibaba.cloud</groupId>
  3. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  4. <version>2.1.0.RELEASE</version>
  5. </dependency>

同时在 src/main/resources/application.yml 配置注册中心:

  1. ```
  2. spring:
  3. application.name: echo-service
  4. cloud:
  5. nacos:
  6. discovery:
  7. server-addr: ${NACOS_ADDRESS:127.0.0.1:8848}
  8. namespace: ${NACOS_TENANT_ID:}
  9. ```

tip 提示

application.name 需与代码中保持一致。

io.terminus.erda.bestpractice.echo.Application 类增加 @EnableDiscoveryClient 注解表明此应用需开启服务注册与发现功能。

  1. ```
  2. package io.terminus.erda.bestpractice.echo;
  3. @SpringBootApplication
  4. @EnableDiscoveryClient
  5. public class Application {
  6. public static void main(String[] args) {
  7. SpringApplication.run(Application.class, args);
  8. }
  9. }
  10. ```

开发 io.terminus.erda.bestpractice.echo.controller.EchoController 类,并实现功能 API:

  1. package io.terminus.erda.bestpractice.echo.controller;
  2. import org.springframework.web.bind.annotation.RequestBody;
  3. import org.springframework.web.bind.annotation.RequestMapping;
  4. import org.springframework.web.bind.annotation.RequestMethod;
  5. import org.springframework.web.bind.annotation.RestController;
  6. class Content {
  7. public String content;
  8. public void setContent(String content) {
  9. this.content = content;
  10. }
  11. public String getContent() {
  12. return content;
  13. }
  14. }
  15. @RestController
  16. @RequestMapping(value = "/echo/content")
  17. public class EchoController {
  18. private String c = "";
  19. @RequestMapping(method = RequestMethod.PUT)
  20. public void echoPut(@RequestBody Content content) {
  21. c = content.content;
  22. }
  23. @RequestMapping(method = RequestMethod.POST)
  24. public void echoPost(@RequestBody Content content) {
  25. if (c != content.content) {
  26. c = content.content;
  27. }
  28. }
  29. @RequestMapping(method = RequestMethod.DELETE)
  30. public void echoDelete() {
  31. c = "";
  32. }
  33. @RequestMapping(method = RequestMethod.GET)
  34. public String echoGet() {
  35. return c;
  36. }
  37. }

echo web

pom.xml 和 application.yml 参考 echo service 部分。

创建 echo service 的接口类 io.terminus.erda.bestpractice.echo.controller.EchoService:

  1. package io.terminus.erda.bestpractice.echo.controller;
  2. import org.springframework.cloud.openfeign.FeignClient;
  3. import org.springframework.web.bind.annotation.RequestBody;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.RequestMethod;
  6. @FeignClient(name="echo-service")
  7. @RequestMapping(value = "/echo")
  8. public interface EchoService {
  9. @RequestMapping(value = "/content", method = RequestMethod.PUT)
  10. void echoPut(@RequestBody Content content);
  11. @RequestMapping(value = "/content", method = RequestMethod.POST)
  12. void echoPost(@RequestBody Content content);
  13. @RequestMapping(value = "/content", method = RequestMethod.GET)
  14. String echoGet();
  15. @RequestMapping(value = "/content", method = RequestMethod.DELETE)
  16. void echoDelete();
  17. }

在 io.terminus.erda.bestpractice.echo.controller.EchoController 实现对资源 content 的增删改查:

  1. @RestController
  2. @RequestMapping(value = "/api")
  3. public class EchoController {
  4. @Autowired
  5. private EchoService echoService;
  6. @RequestMapping(value = "/content", method = RequestMethod.PUT)
  7. public void echoPut(@RequestBody Content content) {
  8. echoService.echoPut(content);
  9. }
  10. @RequestMapping(value = "/content", method = RequestMethod.POST)
  11. public void echoPost(@RequestBody Content content) {
  12. echoService.echoPost(content);
  13. }
  14. @RequestMapping(value = "/content", method = RequestMethod.GET)
  15. public Content echoGet() {
  16. Content content = new Content();
  17. String c = echoService.echoGet();
  18. content.setContent(c);
  19. return content;
  20. }
  21. @RequestMapping(value = "/content", method = RequestMethod.DELETE)
  22. public void echoDelete () {
  23. echoService.echoDelete();
  24. }
  25. @RequestMapping(value = "/healthy", method = RequestMethod.GET)
  26. public Boolean health() {
  27. return true;
  28. }
  29. }
  30. class Content {
  31. private String content;
  32. public void setContent(String content) {
  33. this.content = content;
  34. }
  35. public String getContent() {
  36. return content;
  37. }
  38. }

dice.yml

编辑 echo-web 和 echo-service 两个应用的 dice.yml,增加 Addon 注册中心。

基于 Spring Cloud 微服务框架的应用开发治理 - 图6

完成以上代码后,再次执行 echo-web 和 echo-service 的流水线,随后即可在 环境部署 看到 注册中心

基于 Spring Cloud 微服务框架的应用开发治理 - 图7

点击 注册中心,或进入 微服务治理平台 > 服务治理 > 注册中心,查看 HTTP 协议

基于 Spring Cloud 微服务框架的应用开发治理 - 图8

echo-service 和 echo-web 已分别完成服务的注册和发现。

API 访问测试

关联应用

进入 DevOps 平台 > API 管理 > API 集市,点击 echo 应用 API管理 选项。

  • 关联关系:选择项目名称为 bestpractice,应用名称为 echo-web。
  • 版本管理:选择服务名称为 echo-web,部署分支未 feature/echo-web,关联实例为 echo-web-xxx.svc.cluster.local:8080。

基于 Spring Cloud 微服务框架的应用开发治理 - 图9

tip 提示

  • 应用下可包含多个服务,本示例中应用名称与服务名称均为 echo-web,仅是一种同名的情况。
  • 关联实例是一个 VIP 域名地址(Kubernetes Service 域名地址),由于服务可部署多个 Pod 实例,Erda 通过 Kubernetes 的 Service 实现对多个 Pod 的负载分配。

创建管理条目

进入 DevOps 平台 > API 管理 > 访问管理,创建管理条目。

基于 Spring Cloud 微服务框架的应用开发治理 - 图10

tip 提示

  • 绑定域名 需绑定已解析到 Erda 平台的公网入口 IP 方可从公网访问服务。
  • 若尚未创建 API 网关,请根据提示先行创建 API 网关。

申请调用并测试

进入 DevOps 平台 > API 管理 > API 集市 > Echo 应用 API > 申请调用。若无合适的客户端,请根据提示先行完成创建。保存系统提示的 Client ID 和 Client Secret,用于后续测试。

完成审批后进入 API 集市 > Echo 应用 API > 认证,输入 ClientID 和 ClientSecret 后可选择任意 API 并点击测试。

基于 Spring Cloud 微服务框架的应用开发治理 - 图11

配置中心使用

echo service 应用配置热加载

在 pom.xml 文件中增加依赖:

  1. <dependency>
  2. <groupId>io.terminus.common</groupId>
  3. <artifactId>terminus-spring-boot-starter-configcenter</artifactId>
  4. <version>2.1.0.RELEASE</version>
  5. </dependency>

tip 提示

需使用端点二次开发的 Spring Boot Starter 适配 Erda 平台。

io.terminus.erda.bestpractice.echo.controller.ErrorController 增加 slowTime 变量模拟耗时请求,并通过配置中心实现配置热加载:

  1. @RefreshScope
  2. @RestController
  3. @RequestMapping(value = "/echo/error")
  4. public class ErrorController {
  5. @Value("${echo.slowtime:100}")
  6. private int slowTime;
  7. @RequestMapping(method = RequestMethod.POST)
  8. void errorGet(@RequestBody EchoError err) throws Exception {
  9. if (err.getType().equals("slow")) {
  10. Thread.sleep(slowTime);
  11. }
  12. else {
  13. throw new Exception("make exception");
  14. }
  15. }
  16. }

其中注解 @RefreshScope 和 @Value 实现配置 echo.slowtime 热加载。

在 dice.yml 的 Addon 部分增加配置中心:

基于 Spring Cloud 微服务框架的应用开发治理 - 图12

echo web 增加 API

编辑 io.terminus.erda.bestpractice.echo.controller.ErrorController 类,实现 /api/error API:

  1. @RefreshScope
  2. @RestController
  3. @RequestMapping(value = "/echo/error")
  4. public class ErrorController {
  5. @Value("${echo.slowtime:300}")
  6. private int slowTime;
  7. @RequestMapping(method = RequestMethod.POST)
  8. void errorPost(@RequestBody EchoError err) throws Exception {
  9. if (err.getType().equals("slow")) {
  10. Thread.sleep(slowTime);
  11. }
  12. else {
  13. throw new Exception("make exception");
  14. }
  15. }
  16. }
  17. class EchoError {
  18. private String type;
  19. public void setType(String type) {
  20. this.type = type;
  21. }
  22. public String getType() {
  23. return type;
  24. }
  25. }

验证

再次执行两个应用的工作流完成更新部署。

在 echo-service 应用的 环境部署 点击 配置中心,或进入 微服务治理平台 > 服务治理 > 配置中心,设置 echo.slowtime 的值:

基于 Spring Cloud 微服务框架的应用开发治理 - 图13

可逐步从小到大进行设置(例如 500、1000、1500),每次配置将被热加载实时生效,随后可在 API 测试界面上调用 /api/error API 进行访问。

服务治理

前提条件

为实践服务治理,需先制造一些请求和异常。

  • 调用 /api/contnet 接口实现对资源的增删改查。
  • 设置 echo.slowtime 设置为 1500 后,调用 /api/error 接口且 type=slow 的情况接口模拟超时。
  • 调用 /api/error 接口且 type=exception 的情况接口模拟异常。

tip 提示

由于 Feign 调用默认使用 Ribbon进行负载均衡,且 Ribbon 的默认超时时间为 1 秒,因此 echo.slowtime 设置超过 1 秒时接口可以模拟超时。

平台治理

进入 微服务治理平台 > 服务总览 > 拓扑,查看项目的微服务全景图,其中异常情况已用红色标明。

基于 Spring Cloud 微服务框架的应用开发治理 - 图14

进入 监控中心 > 服务监控 > 链路查询,选择 链路状态错误,可查看异常链路的信息。

基于 Spring Cloud 微服务框架的应用开发治理 - 图15

由上图可以看出,echo-service 的 /echo/error 接口耗时 500+ 毫秒导致慢请求。

更多相关信息,请参见 服务分析

至此,您已通过一个 echo 微服务项目实践了 Erda 上的 Spring Cloud 开发。整个过程涉及到微服务组件(注册中心、配置中心)的使用、CI/CD 工作流、API 设计和测试、服务异常观测等,本文中仅点到为止,详细使用请参见各平台的使用指南。