使用Spring Cloud和Docker构建微服务架构

如何使用 Spring Boot、Spring Cloud、Docker 和 Netflix 的一些开源工具来构建一个微服务架构。

本文通过 Spring Boot、Spring Cloud 和 Docker 构建的概念型应用示例,提供了了解常见微服务架构模式的起点。

相关代码可以在 Github 上获取,并且 Docker Hub 也提供了相关镜像。您只需要一个命令即可启动整个系统。

我选择了一个老项目作为这个系统的基础,它的后端以前是单体架构。该应用提供了处理个人财务、整理收入开销、管理储蓄、分析统计和创建简单预测等功能。

功能服务

整个应用可分解为三个核心微服务。它们都是可以独立部署的应用,围绕着某些业务功能进行组织。

使用 Spring Cloud 和 Docker 构建微服务架构

账户服务

包含一些用户输入逻辑和验证逻辑:收入/开销记录、储蓄和账户设置。

方法路径描述用户验证UI 可用
GET/accounts/{account}获取指定账户数据  
GET/accounts/current获取当前账户数据xx
GET/accounts/demo获取演示账户数据(预先填入收入/开销记录等) x
PUT/accounts/current保存当前账户数据xx
POST/accounts/注册新账户 x

统计服务

计算主要的统计参数,并捕获每个账户的时间序列。数据点包含了基于货币和时间段处理后的值。该数据可用于跟踪账户生命周期内的现金流量动态。

方法路径描述用户验证UI 可用
GET/statistics/{account}获取指定账户统计数据  
GET/statistics/current获取当前账户的统计数据xx
GET/statistics/demo获取演示账户统计数据 x
PUT/statistics/{account}创建或更新指定账户的时间序列数据点。  

通知服务

存储用户的联系信息和通知设置(如提醒和备份频率)。安排工作人员从其它服务收集所需的信息并向订阅的客户发送电子邮件。

方法路径描述用户验证UI 可用
GET/notifications/settings/current获取当前账户的通知设置xx
PUT/notifications/settings/current保存当前账户的通知设置xx

注意

  • 每个微服务拥有自己的数据库,因此没有办法绕过 API 直接访问持久数据。
  • 在本项目中,我使用 MongoDB 作为每个服务的主数据库。拥有一个多种类持久化架构(polyglot persistence architecture)也是很有必要的。
  • 服务间(Service-to-service)通信非常简单:微服务仅使用同步的 REST API 进行通信。现实系统中常见的做法是使用多种交互方式组合。例如,执行同步的 GET 请求检索数据,并通过消息代理(broker)使用异步方法执行创建/更新操作,以解除服务和缓冲消息之间的耦合。最终,这带给我们是最终一致性

基础设施服务

分布式系统中常用的模式,可以帮助我们描述核心服务是如何工作的。Spring Cloud 提供了强大的工具,可以增强 Spring Boot 应用的行为来实现这些模式。我会简要介绍一下:

基础设施服务

配置服务

Spring Cloud Config 是分布式系统的水平扩展集中式配置服务。它使用了当前支持的本地存储、Git 和 Subversion 等可拔插存储库层(repository layer)。

在此项目中,我使用了 native profile,它简单地从本地 classpath 下加载配置文件。您可以在配置服务资源中查看 shared 目录。现在,当通知服务请求它的配置时,配置服务将响应回 shared/notification-service.yml 和 shared/application.yml(所有客户端应用之间共享)。

客户端使用

只需要使用 sprng-cloud-starter-config 依赖构建 Spring Boot 应用,自动配置将会完成其它工作。

现在您的应用中不需要任何嵌入的 properties,只需要提供有应用名称和配置服务 url 的 bootstrap.yml 即可:

spring:
  application:
    name: notification-service
  cloud:
    config:
      uri: http://config:8888
      fail-fast: true
使用 Spring Cloud Config,您可以动态更改应用配置

比如,EmailService bean 使用了 @RefreshScope 注解。这意味着您可以无需重新构建和重启通知服务应用就能更改电子邮件的内容和主题。

首先,在配置服务器中更改必要的属性。然后,对通知服务执行刷新请求:curl -H "Authorization: Bearer #token#" -XPOST http://127.0.0.1:8000/notifications/refresh

您也可以使用 webhook 来自动执行此过程

注意

  • 动态刷新存在一些限制。@RefreshScope 不能和 @Configuraion 类一同工作,并且不会作用于 @Scheduled 方法。
  • fail-fast 属性意味着如果 Spring Boot 应用无法连接到配置服务,将会立即启动失败。当您同时启动所有应用时,这非常有用。
  • 之后有重要的安全提示

授权服务

负责授权的部分被完全提取到单独的服务器,它为后端资源服务提供 OAuth2 令牌。授权服务器应用于用户授权以及在周边内进行安全的机器间通信。

在此项目中,我使用密码凭据作为用户授权的授权类型(因为它仅被本地应用 UI 使用)和客户端凭据作为微服务授权的授权类型。

Spring Cloud Security 提供了方便的注解和自动配置,使其在服务端或者客户端都可以很容易地实现。您可以在文档中了解到更多信息,并在授权服务器的代码中查看配置明细。

从客户端来看,一切都与传统的基于会话的授权完全相同。您可以从请求中检索 Principal 对象、检查用户角色和其它基于表达式访问控制和 @PreAuthorize 注解的内容。

PiggyMetrics(帐户服务、统计服务、通知服务和浏览器)中的每一个客户端都有一个作用域(范围、界限):用于后台服务的 服务器、用于浏览器展示的 UI。因此我们也可以保护控制器避免外部访问,例如:

@PreAuthorize("#oauth2.hasScope('server')")
@RequestMapping(value = "accounts/{name}", method = RequestMethod.GET)
public List<DataPoint> getStatisticsByAccountName(@PathVariable String name) {
    return statisticsService.findByAccountName(name);
}

API 网关

您可以看到,一共有三个核心服务。它们向客户端暴露外部 API。在现实系统中,这个数量可以非常快速地增长,同时整个系统将变得非常复杂。实际上,一个复杂页面的渲染可能涉及到数百个服务。

理论上,客户端可以直接向每个微服务直接发送请求。但是这种方式是存在挑战和限制的,您需要知道所有端点的地址,分别对每一段信息执行 http 请求,将结果合并到客户端。另一个问题是,这并不是 web 友好协议,一般只在后端使用。

通常一个更好的做法是使用 API 网关。它是系统单入口点,用于通过将请求路由到适当的后端服务或者通过调用多个后端服务并聚合结果来处理请求。此外,它还可以用于认证、insights、压力测试、金丝雀测试(canary testing)、服务迁移、静态响应处理和主动变换管理。

Netflix 开源有这样的边缘服务,现在用 Spring Cloud,我们可以用一个 @EnabledZuulProxy 注解来启用它。在这个项目中,我使用 Zuul 存储静态内容(UI 应用),并将请求路由到适当的微服务。以下是一个简单的基于前缀(prefix-based)路由的通知服务配置:

zuul:
  routes:
    notification-service:
        path: /notifications/**
        serviceId: notification-service
        stripPrefix: false

这意味着所有以 /notification 开头的请求将被路由到通知服务。您可以看到里面没有硬编码的地址。Zuul 使用服务发现机制来定位通知服务实例以及熔断器和负载均衡器,如下所述。

服务发现

另一种常见的架构模式是服务发现模式。它允许自动检测服务实例的网络位置,由于自动扩展、应对故障和升级,它可能会动态分配地址。

服务发现的关键部分是注册中心。我使用 Netflix Eureka 配合这个项目,当客户端需要确定可以用的服务实例(使用注册中心服务器)的位置和跨平台的负载均衡请求时,Eureka 就是客户端发现模式的一个很好的方案。

使用 Spring Boot,您可以使用 spring-cloud-starter-eureka-server 依赖、@EnabledEurekaServer 注解和简单的配置属性即可轻松构建出 Eureka 注册中心(Eureka Registry)。

使用 @EnabledDiscoveryClient 注解和带有应用名称的 bootstrap.yml 来启用客户端支持:

spring:
  application:
    name: notification-service

在应用启动时,它将向 Eureka 服务器注册并提供元数据,如主机和端口、健康指示器 URL、主页等。Eureka 接收来每个自从属于某服务实例的心跳消息。如果心跳失败时长超过配置的时间,该实例将从注册表中删除。

此外,Eureka 还提供了一个简单的界面,您可以通过它来跟踪正在运行的服务和可用实例的数量:http://localhost:8761

Eureka 仪表盘

负载均衡器、熔断器和 Http 客户端

Netflix OSS 提供了另一套很棒的工具。

Ribbon

Ribbon 是客户端负载均衡器,可以很好地控制 HTTP 和 TCP 客户端的行为。与传统的负载均衡器相比,每次线上调用都不需要额外的凯越 — 您可以直接与所需的服务通信。

它与 Spring Cloud 和服务发现是集成在一起的,可开箱即用。Eureka 客户端提供了可用服务的动态列表,因此 Ribbon 可以在它们之间进行均衡。

Hystrix

Hystrix 是熔断器模式的一种实现,它可以通过网络访问依赖来控制延迟和故障。中心思想是在具有大量微服务的分布式环境中避免级联故障。这有助于快速失败并尽快恢复 — 自我修复在容错系统中是非常重要的。

除了熔断器控制,在使用 Hystrix,您可以添加一个备用方法,在主命令失败的情况下,该方法将被调用以获取默认值。

此外,Hystrix 生成每个命令的执行结果和延迟的度量,我们可以用它来监视系统的行为

Feign

Feign 是一个声明式 HTTP 客户端,能与 Ribbon 和 Hystrix 无缝集成。实际上,通过一个 spring-cloud-starter-feign 依赖和 @EnabledFeignClients 注解,您可以使用一整套负载均衡器、熔断器和 HTTP 客户端,并附带一个惯例默认配置。

以下是账户服务的示例:

@FeignClient(name = "statistics-service")
public interface StatisticsServiceClient {
    @RequestMapping(method = RequestMethod.PUT, value = "/statistics/{accountName}", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
    void updateStatistics(@PathVariable("accountName") String accountName, Account account);
}
  • 您需要的只是一个接口
  • 您可以在 Spring MVC 控制器和 Feign 方法之间共享 @RequestMapping 部分
  • 以上示例仅指定所需要的服务 ID — statistics-service,这得益于 Eureka 的自动发现(您也可以使用指定的 URL 访问任何资源)。

监控仪表盘

在本项目配置中,Hystrix 的每一个微服务都通过 Spring Cloud Bus(通过 AMQP broker)将指标推送到 Turbine。监控项目只是一个使用了 Turbine 和 Hystrix 仪表盘的小型 Spring Boot 应用。

让我们看看系统在负载下的运行状况:账户服务调用统计服务和它们在动态延迟模拟下的响应情况。响应超时阈值设置为 1 秒。

监控仪表盘

日志分析

集中式日志记录对查找分布式环境中的问题非常有帮助。Elasticsearch、Logstash 和 Kibana 栈可让您能轻松地搜索和分析日志、获取利用率和网络活动数据。在我的另一个项目中已经有现成的 Docker 配置。

安全

高级安全配置内容已经超出了此概念项目的范围。为了更真实地模拟真实系统,请考虑使用 https 和 JCE 密钥库来加密微服务密码和配置服务器的 properties 内容(有关详细信息,请参阅文档)。

基础设施自动化

部署微服务比部署单体应用的流程要复杂得多,因为它们相互依赖。拥有完全基础设置自动化是非常重要的。我们可以通过持续交付的方式获得以下好处:

  • 可随时发布软件。
  • 任何构建都可能最终成为一个发行版本。
  • 构建工件(artifact)一次,即可根据需要部署。

这是一个简单的持续交付工作流程,在这个项目的实现:

在此配置中,Travis CI 为每一个成功的 Git 推送创建了标记镜像。因此,每一个微服务在 Docker Hub 上的都会有一个 latest 镜像,而较旧的镜像则使用 Git 提交的哈希进行标记。在有需要时,可以轻松部署任何一个,且能快速回滚。

基础设施自动化

如何运行完整项目?

这真的很简单,我建议您尝试一下。请记住,您将要启动 8 个 Spring Boot 应用、4 个 MongoDB 实例和 1 个 RabbitMq。请确保您的机器上至少有 4GB 的内存。您可以随时通过网关、注册中心、配置、认证服务和账户中心运行重要的服务。

运行之前

  • 安装 Docker 和 Docker Compose。
  • 配置环境变量:CONFIG_SERVICE_PASSWORD, NOTIFICATION_SERVICE_PASSWORD, STATISTICS_SERVICE_PASSWORD, ACCOUNT_SERVICE_PASSWORD, MONGODB_PASSWORD

生产模式

该模式下,所有最新的镜像都将从 Docker Hub 上拉取。只需要复制 docker-compose.yml 并执行 docker-compose up -d 即可。

开发模式

如果您想自己构建镜像(例如,在代码中进行一些修改),您需要克隆所有仓库并使用 Mavne 构建工件。然后,运行 docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d

docker-compose.dev.yml 继承了 docker-compose.yml,附带额外配置,可用于在本地构建镜像,暴露所有容器端口以方便开发。

重要的端点(Endpoint)

  • localhost:80 —— 网关
  • localhost:8761 —— Eureka 仪表盘
  • localhost:9000 —— Hystrix 仪表盘
  • localhost:8989 —— Turbine stream(Hystrix 仪表盘数据流源)
  • localhost:15672 —— RabbitMq 管理

注意

所有 Spring Boot 应用都需要运行配置服务器才能启动。得益于 Spring Boot 的 fail-fast 属性和 docker-compsoe 的 restart:always 选项,我们可以同时启动所有容器。这意味着所有依赖的容器将尝试重新启动,直到配置服务器启动运行为止。

此外,服务发现机制在所有应用启动后需要一段时间。在服务实例、Eureka 服务器和客户端都在其本地缓存中具有相同的元数据之前,任何服务都不可用于客户端发现,因此可能需要 3 次心跳。默认的心跳周期为 30 秒。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值