优秀的编程知识分享平台

网站首页 > 技术文章 正文

精通Spring Boot 3 : 13. Spring Cloud 与 Spring Boot (4)

nanyue 2025-01-09 15:09:55 技术文章 3 ℃

创建我的复古网关系统

您可以在 13-cloud/myretro-gateway 文件夹中找到这一部分的代码。如果您想使用 Spring Initializr (https://start.spring.io) 从头开始,请将 Group 字段设置为 com.apress,将 Artifact 和 Name 字段都设置为 myretro-gateway,并将 Actuator、Consul Configuration、Consul Discovery、Reactive Gateway 和 Resilience4J Circuit Breaker 作为依赖项添加。其他所有设置保持默认。然后,生成并下载项目,解压缩后导入到您喜欢的 IDE 中。

让我们来审查一下 build.gradle 文件。请参见列表 13-13。

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.2'
    id 'io.spring.dependency-management' version '1.1.4'
}
group = 'com.apress'
version = '0.0.1-SNAPSHOT'
java {
    sourceCompatibility = '17'
}
repositories {
    mavenCentral()
}
ext {
    set('springCloudVersion', "2023.0.0")
}
dependencies {
    // Actuator
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    // Gateway
    implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
    implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j'
    // Consul
    implementation 'org.springframework.cloud:spring-cloud-starter-consul-config'
    implementation 'org.springframework.cloud:spring-cloud-starter-consul-discovery'
    // Kubernetes
    implementation 'org.springframework.cloud:spring-cloud-starter-kubernetes-fabric8-all'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'io.projectreactor:reactor-test'
}
dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}
tasks.named('test') {
    useJUnitPlatform()
}

列表 13-13 的 build.gradle 文件

列表 13-13 显示我们需要包含依赖项 spring-cloud-starter-gateway、spring-cloud-starter-circuitbreaker-reactor-resilience4j(将在本章稍后讨论)和 spring-cloud-starter-kubernetes-fabric8-all(在最后一节“使用云平台:Kubernetes”中会提到)。此外,我们还添加了 Consul 的依赖项,以便它能够自动注册到 Consul 服务器。Spring Cloud Gateway 的一个很酷的功能是,如果您已经在使用 Consul,它可以无需任何编程就利用本地服务。

Spring Cloud Gateway 是专为 WebFlux 设计的,因此您可以在常规 MVC 或反应式 Web 应用程序中使用这项技术。

接下来,打开或创建 application.yaml 文件。请参阅列表 13-14。

spring:
  application:
    name: myretro-gateway
  config:
    import: optional:consul://
  cloud:
    gateway:
      routes:
        - id: users
          uri: lb://users-service
          predicates:
            - Path=/users/**
        - id: myretro
          uri: lb://my-retro-app
          predicates:
            - Path=/retros/**
server:
  port: ${PORT:8080}

列表 13-14 源文件:src/main/resources/application.yaml

让我们一起回顾一下 application.yaml 文件:

  • 请记住,当您想要使用服务发现时,始终需要这个属性。由于我们使用的是 Consul,这一点尤其重要。设置的值是 myretro-gateway,它会自动以这个名称注册到 Consul 中。
  • spring.config.import:在添加 spring-cloud-starter-consul-config 依赖时,即使我们不使用它,也需要指定这个属性。在我们的示例中,我们不会将 Consul 作为外部配置使用,因此可以将其声明为可选的,这就是我们在其值开头添加这个内容的原因。
  • spring.cloud.gateway.*: 这包括所有网关的声明配置。在这种情况下,我们定义了两个路由,一个指向 /users 端点,另一个指向 /retros 端点。用户路由的 uri 属性使用了服务名称(在这里是 users-service),并加上了 lb:// 前缀,这表示我们需要这样声明负载均衡器的使用;同时请注意,我们有两个 Users Services 应用实例在运行(分别在 8091 和 8092 端口)。我们为每个服务声明了 predicates.Path,值为 /users/** 和 /retros/**。 这意味着当我们通过 /users 访问网关时,它会重定向到 http://users-service/users(http://localhost:8091/users 或 http://localhost:8092/users,具体取决于负载均衡器,通常采用轮询方式)。

当然,还有更多的配置选项,但我们已经涵盖了与服务发现和负载均衡相关的基本选项。

就这样!不需要任何编程或其他操作。

启动我的复古网关

在运行 My Retro Gateway 之前,请确保所有服务(consul、vault、users-service 和 my-retro-app)都已启动并且在 Consul 中可见。

要运行 My Retro Gateway 应用,您可以使用您的 IDE 或者以下命令:

./gradlew bootRun

默认情况下,应用程序运行在 8080 端口。如果您打开 Consul UI(http://localhost:8500),可以查看所有服务。请参见图 13-16。

图 13-16 Consul UI 服务部分,显示所有服务的列表(http://localhost:8500/ui/dc1/services)

现在您已经成功启动了 My Retro Gateway,是时候进行测试了。您可以选择使用浏览器或终端。如果您使用终端,可以执行以下命令来查看用户:

curl -s http://localhost:8080/users | jq
[
  {
    "email": "ximena@email.com",
    "name": "Ximena",
    "gravatarUrl": "https://www.gravatar.com/avatar/23bb62a7d0ca63c9a804908e57bf6bd4?d=wavatar",
    "password": "aw2s0meR!",
    "userRole": [
      "USER"
    ],
    "active": true
  },
  {
    "email": "norma@email.com",
    "name": "Norma",
    "gravatarUrl": "https://www.gravatar.com/avatar/f07f7e553264c9710105edebe6c465e7?d=wavatar",
    "password": "aw2s0meR!",
    "userRole": [
      "USER",
      "ADMIN"
    ],
    "active": false
  }
]

如果你执行以下命令,你将获得回溯信息:

curl -s http://localhost:8080/retros | jq
[
  {
    "retroBoardId": "4efaf18e-2141-40c5-abff-0c0951d62938",
    "name": "Spring Boot 3 Retro",
    "cards": [
      {
        "cardId": "d9df505f-3564-4104-a339-415db9f4a29a",
        "comment": "Nice to meet everybody",
        "cardType": "HAPPY",
        "created": "2024-02-12 13:17:30",
        "modified": "2024-02-12 13:17:30"
      },
      {
        "cardId": "3906a3cb-69cc-412f-8965-42979ac91052",
        "comment": "When are we going to travel?",
        "cardType": "MEH",
        "created": "2024-02-12 13:17:30",
        "modified": "2024-02-12 13:17:30"
      },
      {
        "cardId": "fdbce1c8-f9ec-45fd-873b-b4ab43c2aff5",
        "comment": "When are we going to travel?",
        "cardType": "SAD",
        "created": "2024-02-12 13:17:30",
        "modified": "2024-02-12 13:17:30"
      }
    ],
    "created": "2024-02-12 13:17:30",
    "modified": "2024-02-12 13:17:30"
  }
]

但是如果你关闭两个用户服务应用,会发生什么呢?要了解情况,请这样做,然后执行

curl -s http://localhost:8080/users | jq
{
  "timestamp": "2024-02-12T20:14:12.361+00:00",
  "path": "/users",
  "status": 503,
  "error": "Service Unavailable",
  "requestId": "2a02d2b8-2"
}

那么,在服务中断时,我们如何添加默认消息或虚拟用户呢?可以使用在下一节中讨论的断路器模式。

更多网关特性

Spring Cloud Gateway 拥有许多其他功能,例如谓词和过滤器。谓词的示例包括 After、Before、Between、Cookie、Header、Host、Method、Path、Query、ReadBody、RemoteAddr、XForwardedRemoteAddr、Weight、CloudFoundryRouteService 以及自定义选项。过滤器的种类繁多,以下是一些按字母顺序排列到字母 J 的示例:AddRequestHeader、AddRequestHeaderIfNotPresent、AddRequestParameter、AddResponseHeader、CircuitBreaker、CacheResponseBody、DedupeResponseHeader、FallbackHeaders 和 JsonToGrpc。如果现有的过滤器无法满足您的业务逻辑需求,您还可以选择创建自定义过滤器。让我们来回顾一下这些谓词和过滤器。

断路器过滤器

要理解 CircuitBreaker 过滤器的目的,首先需要了解 Circuit Breaker 模式。Circuit Breaker 模式是一种弹性机制,用于保护系统免受级联故障的影响,当依赖的服务或资源不可用或过载时,它就像一个自动开关,可以处于三种状态之一:

  • 关闭:正常运行;请求已转发至服务。
  • 打开:服务故障阈值已达到,请求会立即被中断并转向备用机制(例如,缓存响应或替代服务)。
  • 半开状态:在超时后,单个请求会探测服务。如果请求成功,电路将关闭;如果失败,电路将保持开放更长时间。

该模式可以防止对故障服务的进一步负担,并允许系统在问题解决时继续平稳运行。CircuitBreaker 过滤器是基于电路断路器模式的。

将 CircuitBreaker 过滤器添加到我的 Retro Gateway 应用中

让我们在 My Retro Gateway 应用的 application.yaml 文件中添加 CircuitBreaker 过滤器。您可以在 spring.cloud.gateway.routes 的用户部分添加以下代码片段:

          filters:
            - name: CircuitBreaker
              args:
                name: users
                fallbackUri: forward:/fallback/users

我们正在定义过滤器的名称为 CircuitBreaker,并声明它所需的一些参数,包括名称和 fallbackUri。这意味着如果/users 端点无法访问,它将默认转向/fallback/users 端点。完整的配置详见列表 13-15。

spring:
  application:
    name: myretro-gateway
  config:
    import: optional:consul://
  cloud:
    gateway:
      routes:
        - id: users
          uri: lb://users-service
          predicates:
            - Path=/users/**
          filters:
            - name: CircuitBreaker
              args:
                name: users
                fallbackUri: forward:/fallback/users
        - id: myretro
          uri: lb://my-retro-app
          predicates:
            - Path=/retros/**
          filters:
            - name: CircuitBreaker
              args:
                name: retros
                fallbackUri: forward:/fallback/retros
server:
  port: ${PORT:8080}

列表 13-15 src/main/resources/application.yaml 中的 CircuitBreaker

列表 13-15 展示了 application.yaml 文件的最终版本。关于 fallbackUri,我们声明了转发到新的路由/fallback/users 和/fallback/retros。这意味着我们需要实现这些回退功能。因此,请打开或创建 MyRetroFallbackController 类。请参见列表 13-16。

package com.apress.myretrogateway;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.Map;
@RestController
@RequestMapping("/fallback")
public class MyRetroFallbackController {
    @GetMapping("/users")
    public ResponseEntity userFallback() {
        return ResponseEntity.ok(Map.of(
                "status","Service Down",
                "message","/users endpoint is not available at this moment",
                "time",LocalDateTime.now(),
                "data", Map.of(
                        "email","dummy@email.com",
                        "name","Dummy",
                        "password","dummy",
                        "active",false)
                ));
    }
    @GetMapping("/retros")
    public ResponseEntity retroFallback() {
        return ResponseEntity.ok(Map.of("status","Service Down","message","/retros endpoint is not available at this moment","time",LocalDateTime.now()));
    }
}

列表 13-16 源代码:src/main/java/com/apress/myretrogateway/MyRetroFallbackController.java

列表 13-16 展示了 MyRetroFallbackController 类。你对此已经很熟悉,因此无需解释。实际上,你使用的是一个默认响应,这不一定非得像我们这里用 Map 的方式,你也可以联系一个你知道永远不会宕机的其他服务。

所以,如果你想进行测试,可以关闭用户服务应用的两个实例,然后用新的配置重新启动我的复古网关。请在/users 端点执行以下命令:

curl -s http://localhost:8080/users | jq
{
  "message": "/users endpoint is not available a this moment",
  "status": "Service Down",
  "data": {
    "active": false,
    "name": "Dummy",
    "email": "dummy@email.com",
    "password": "dummy"
  },
  "time": "2024-02-12T16:00:34.087459"
}

现在,您可以尝试关闭 my-retro-app 服务,然后通过 /retros 进行调用,以查看控制器的消息。

在 Docker Compose 中整合云环境

到目前为止,我们一直是单独处理每个组件,现在让我们在这个单一文件中一起处理所有内容。我们使用 Docker Compose 来搭建整个环境。

接下来,我们来定义 compose.yaml 文件。您可以在 13-cloud/docker-compose 文件夹中找到这个文件。请参见列表 13-17。

services:
  ## HashiCorp Consul
  consul-server:
    hostname: consul-server
    container_name: consul-server
    image: consul:1.15.4
    restart: always
    networks:
      - cloud
    healthcheck:
      test: ["CMD", "curl", "-X", "GET", "localhost:8500/v1/status/leader"]
      interval: 1s
      timeout: 3s
      retries: 60
    command: "agent -server -ui -node=server-1 -bootstrap-expect=1 -client=0.0.0.0"
  consul-client:
    hostname: consul-client
    container_name: consul-client
    image: consul:1.15.4
    restart: always
    networks:
      - cloud
    command: "agent -node=client-1 -join=consul-server -retry-join=172.17.0.2"
  consul-init:
    hostname: consul-init
    container_name: consul-init
    image: consul:1.15.4
    depends_on:
      consul-server:
        condition: service_healthy
    volumes:
      - ./consul-init.sh:/tmp/consul-init.sh
    networks:
      - cloud
    command: |
      sh -c "/tmp/consul-init.sh"
  ## PostgreSQL
  postgres:
    hostname: postgres
    container_name: postgres
    image: postgres
    platform: linux/amd64
    restart: always
    networks:
      - cloud
    environment:
      POSTGRES_PASSWORD: mysecretpassword
      POSTGRES_USER: admin
      POSTGRES_DB: users_db
    ports:
      - "5432:5432"
    healthcheck:
      test: pg_isready
      interval: 10s
      timeout: 5s
      retries: 5
  ## Users Service
users-service:
  image: users
    build:
      context: ../users
      dockerfile: Dockerfile
    restart: always
    environment:
      - SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/users_db
      - SPRING_CLOUD_CONSUL_HOST=consul-server
    networks:
      - cloud
    depends_on:
      consul-server:
        condition: service_healthy
      postgres:
        condition: service_healthy
  ## My Retro App
my-retro-app:
  image: myretro
    build:
      context: ../myretro
      dockerfile: Dockerfile
    environment:
      - SPRING_CLOUD_CONSUL_HOST=consul-server
    networks:
      - cloud
    depends_on:
      consul-server:
        condition: service_healthy
  ## Gateway
myretro-gateway:
  image: myretro-gateway
    build:
      context: ../myretro-gateway
      dockerfile: Dockerfile
    networks:
      - cloud
    environment:
      - SPRING_CLOUD_CONSUL_HOST=consul-server
    depends_on:
      consul-server:
        condition: service_healthy
    ports:
      - "8080:8080"
## Networks
networks:
  cloud:
    name: cloud
    #external: true

列表 13-17 compose.yaml

列表 13-17 显示,我们只需定义网关,而无需定义任何出口端口。在继续之前,请仔细查看列表 13-17,并注意我们需要为每个 Spring Boot 服务添加 SPRING_CLOUD_CONSUL_HOST 环境变量,以及为用户服务添加 SPRING_DATASOURCE_URL。此外,我们还有一个 consul-init 服务,用于初始化所需的属性。

现在你可以启动环境了

docker compose up

第一次运行此命令时,由于我们需要构建所有服务,因此会花费一些时间。之后的运行会非常快速。启动并运行后,您可以通过向 /users 和 /retros 端点发送请求来进行测试:

curl -s http://localhost:8080/users | jq
curl -s http://localhost:8080/retros | jq

要停止服务,您可以打开一个新的终端,切换到包含 compose.yaml 的目录,然后执行命令。

docker compose down

在每个项目(用户、myretro、myretro-gateway)中,我都创建了以下的 Dockerfile:

FROM eclipse-temurin:17-jdk-jammy AS build
WORKDIR /workspace/app
COPY . /workspace/app
RUN --mount=type=cache,target=/root/.gradle ./gradlew clean build -x test
RUN mkdir -p build/dependency && (cd build/dependency; jar -xf ../libs/*-SNAPSHOT.jar)
FROM eclipse-temurin:17-jdk-jammy
VOLUME /tmp
ARG DEPENDENCY=/workspace/app/build/dependency
COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF
COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","com.apress.users.UsersApplication"]

这个文件是 docker-compose 用来构建镜像的。因此,它会创建每个镜像,并且这些镜像应该在您的本地 Docker 注册表中。您可以通过以下命令进行检查。

docker images | grep -E "retro|users"

如果你没有任何内容,可以在每个项目文件夹中使用以下命令来构建镜像:

# cd users
docker build -t users .
# cd myretro
docker build -t myretro .
# cd myretro-gateway
docker build -t myretro-gateway .

创建多种架构的镜像

Docker 支持多架构,允许您创建一个可以在不同计算机系统上无缝运行的单一 Docker 镜像,例如使用 ARM 或 x86 处理器的系统。这就像拥有一个可以控制不同品牌电视的通用遥控器——一个镜像可以在各种设备上运行,而无需为每种设备单独创建版本。这是通过将多个版本的应用程序(每个版本针对特定架构编译)打包到一个镜像中实现的。Docker 会自动选择适合其运行设备的正确版本,从而使部署变得更加简单和灵活。我正在使用 M3 Mac,因此默认情况下,一些架构层是基于 ARM64 的,但我们如何实现多架构支持,例如 Linux AMD64?

Docker 的最新版本提供了--platform 参数,使我们能够创建多架构镜像。要创建 ARM64 和 AMD64/Intel 架构的镜像,您可以执行以下命令:

docker build \
--push \
--platform linux/arm64,linux/amd64 \
--tag <your-username>/<your-image-name>:<your-tag> .

这个命令不仅会创建您的镜像,还会添加所需的操作系统架构层(ARM64/AMD64),并将镜像推送到 Docker Hub 注册表(--push)。这意味着您需要登录到 Docker 注册表(我建议您在 https://hub.docker.com/上创建一个免费的账户)。当然,您可以去掉--push,但在下一节中我们会需要它。

如果在执行之前的命令时遇到“错误:docker 驱动程序不支持多平台构建”的提示,您需要启用 containerd 功能。请查看此链接以获取更多信息:https://docs.docker.com/desktop/containerd/#build-multi-platform-images。

因此,对于用户项目,我们可以进行执行

docker build \
--push \
--platform linux/arm64,linux/amd64 \
--tag felipeg48/users:latest .

关于 myretro 项目:

docker build \
--push \
--platform linux/arm64,linux/amd64 \
--tag felipeg48/myretro:latest .

关于 myretro-gateway 项目:

docker build \
--push \
--platform linux/arm64,linux/amd64 \
--tag felipeg48/myretro-gateway:latest .

你可以访问 Docker Hub 来查看和管理你的镜像。例如,请参见图 13-17。

在 Docker Hub 上查看镜像 (https://hub.docker.com/repository/docker/felipeg48/users/tags)

现在我们可以开始讨论云平台了!

Tags:

最近发表
标签列表