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

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

    微服务架构解决了众多单体应用的问题,同时也增加了架构的复杂性。下文将针对技术架构的改变,介绍如何在 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 的微服务组件注册中心实现服务接口的注册与发现,通过微服务组件配置中心实现配置统一管理和热加载。

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

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

    APIs

    • echo web 应用 API

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

      ::: 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. <artifactId>spring-boot-starter-parent</artifactId>
    15. <version>2.1.4.RELEASE</version>
    16. <relativePath/>
    17. </parent>
    18. <dependencies>
    19. <dependency>
    20. <groupId>org.springframework.boot</groupId>
    21. <artifactId>spring-boot-starter-web</artifactId>
    22. </dependency>
    23. </dependencies>
    24. <dependencyManagement>
    25. <dependencies>
    26. <dependency>
    27. <groupId>org.springframework.cloud</groupId>
    28. <artifactId>spring-cloud-dependencies</artifactId>
    29. <version>Greenwich.SR1</version>
    30. <type>pom</type>
    31. <scope>import</scope>
    32. </dependency>
    33. </dependencies>
    34. </dependencyManagement>
    35. <build>
    36. <finalName>echo-service</finalName>
    37. <plugins>
    38. <plugin>
    39. <groupId>org.springframework.boot</groupId>
    40. <artifactId>spring-boot-maven-plugin</artifactId>
    41. <configuration>
    42. <executable>true</executable>
    43. <mainClass>io.terminus.erda.bestpractice.echo.Application</mainClass>
    44. </configuration>
    45. <executions>
    46. <execution>
    47. <goals>
    48. <goal>repackage</goal>
    49. </goals>
    50. </execution>
    51. </executions>
    52. </plugin>
    53. </plugins>
    54. </build>
    55. </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:

    ::: tip 提示

    健康检查 API 用于 erda.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 service 应用的开发过程,完成以下内容:

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

    CI/CD 流水线

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

    pipeline.yml

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

    1. ...
    2. erda.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 保持一致,用于替换 erda.yml 中的服务名需与 erda.yml 保持一致。

    :::

    erda.yml

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

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

    ::: tip 提示

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

    :::

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

    服务注册与发现

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

    echo service

    Erda 的注册中心基于 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 注解表明此应用需开启服务注册与发现功能。

    开发 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. }
    22. @RequestMapping(method = RequestMethod.POST)
    23. public void echoPost(@RequestBody Content content) {
    24. if (c != content.content) {
    25. c = content.content;
    26. }
    27. }
    28. public void echoDelete() {
    29. c = "";
    30. }
    31. @RequestMapping(method = RequestMethod.GET)
    32. public String echoGet() {
    33. return c;
    34. }
    35. }

    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. }

    erda.yml

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

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

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

    点击 注册中心,或进入 微服务治理平台 > 项目列表 > bestpractice 开发,查看 HTTP 协议

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

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

    关联应用

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

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

    ::: tip 提示

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

    :::

    创建管理条目

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

    ::: tip 提示

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

    :::

    申请调用并测试

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

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

    配置中心使用

    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. }

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

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

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

    echo web 增加 API

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

    验证

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

    在 echo-service 应用的 部署中心 点击 配置中心,或进入 微服务治理平台 > 项目列表 > bestpractice 开发 > 配置中心,设置 echo.slowtime 的值:

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

    前提条件

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

    • 调用 /api/contnet 接口实现对资源的增删改查。
    • 调用 /api/error 接口且 type=exception 的情况接口模拟异常。

    ::: tip 提示

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

    :::

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

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

    事务分析

    进入 应用监控 > 服务列表 > echo web > 事务分析,点击 慢事务追踪 中的 /api/error,可查看异常链路的信息。

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

    异常分析

    进入 应用监控 > 服务列表 > echo service > 异常分析,可查看程序异常的信息。

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

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