Spring Cloud和Docker的微服务架构
==原文地址==
本文通过以Spring Boot,Spring Cloud和Docker构建的一个应用程序为例,帮助大家理解微服务架构模式。
本文的代码保存在 github上,镜像文件保存在 docker hub 上。可以通过一条命令启动整个系统。
这个应用程序提供了:处理个人财务,组织收入和支出,管理储蓄,分析统计,并创建简单的预测等功能。
功能服务
整体应用被分解成三个核心的微服务。这些微服务是围绕某些业务功能进行组织,可独立部署的应用程序。
服务间关系
Account Service(账户服务)
包含用户的输入逻辑和验证:收入/支出,储蓄和账户设置。
方法 | 路径 | 描述 | 用户身份验证 | 可从 UI 获取 |
---|---|---|---|---|
GET | /accounts/{account} | 获取指定账户数据 | ||
GET | /accounts/{account} | 获取指定账户数据 | ||
GET | /accounts/current | 获取当前用户数据 | × | × |
GET | /accounts/demo | 获取模拟账户数据(预填充收入/支出项目等)) | × | |
PUT | /accounts/current | 保存当前用户数据 | × | × |
POST | /accounts/ | 注册新用户 | × |
Statistics Service(统计服务)
对主要统计参数执行计算,并获取每个帐户的时间线。数据点包含标准化为基本货币和时间段的值。这些数据可用于追踪账户一生中的现金流动态
方法 | 路径 | 描述 | 用户身份验证 | 可从 UI 获取 |
---|---|---|---|---|
GET | /statistics/{account} | 获取指定的账户统计 | ||
GET | /statistics/current | 获取当前用户的账户统计 | × | × |
GET | /statistics/demo | 获取模拟账户的账户统计 | × | |
PUT | /statistics/{account} | 为指定用户创建/更新时间线数据点 |
Notification Service(通知服务)
保存用户联系信息和消息设置(比如提醒和备份频率),定时器从其他服务器上收集所需的信息并通过 emial 发送给订阅者。
方法 | 路径 | 描述 | 用户身份验证 | 可从 UI 获取 |
---|---|---|---|---|
GET | /statistics/{account} | 获取指定的账户统计 | ||
GET | /notifications/settings/current Get | 当前用户的信息设置 | × | × |
PUT | /notifications/settings/current Save | 保存当前用户的消息设置 | × | × |
注意
- 每个微服务都有自己的数据库,不能绕过微服务提供的 API 直接访问微服务的持久化数据。
- 这个例子中我采用 mongoDB 作为每个微服务的数据库,用户可以根据微服务的类型选择合适的数据库。
- 微服务和微服务间的采用同步的 REST API 进行通信,通常的做法是采用交互风格进行通信,比如:通过同步的 GET 请求来检索数据,通过Message Broker使用异步方法来创建/更新操作,以便分离服务和缓冲区消息,目标是实现最终一致性。
Infrastructure Services(基础设施服务)
分布式系统中有许多共同的模式,可以帮助我们描述核心服务。spring cloud提供了强大的工具以帮助 spring boot 应用实现这些模式,下面会做一些简单的介绍:
Config Service(配置服务)
spring cloud config 是分布式系统中的集中式配置服务。
在这个项目中,我使用 native profile 从本地的 classpath 上加载配置文件,你可以在 shared 目录下面查看 config service resources。比如:当Notification-service请求它的配置信息,配置服务会返回如下来两个文件:shared/notification-service.yml
和 shared/application.yml
(这个文件会被所有的应用共享).
Client-side Usage(客户端使用)
只要用spring-cloud-starter-config依赖构建Spring Boot应用程序,其余部分将自动配置
应用中不需要其他任何内置的properties,只需要提供一个bootstrap.yml
文件,这个文件中需要包含当前应用的名字和Config service 的url
spring:
application:
name: notification-service
cloud:
config:
uri: http://config:8888
fail-fast: true
spring cloud config支持动态修改 App 的配置信息,比如: 在EmailService bean上添加@RefreshScope注解,这就意味着你可以修改 email 的正文和标题而不用重新编译或者重启Notification service。具体操作如下:
- 修改config server 上相关的属性
- 对Notification service执行刷新请求:
curl -H "Authorization: Bearer #token#" -XPOST http://127.0.0.1:8000/notifications/refresh
也可以采用webhooks自动执行这个流程
注意
- 动态刷新有如下限制:@RefreshScope 注解无法在@Configuration 类和 @Scheduled 方法上生效。
-
fail-fast
属性意味着如果服务无法连接上 config service,则服务会在启动期间立即失败。这在一起启动所有服务的时候十分有用。
Auth Service(鉴权服务)
授权职责完全提取到单独的服务器,后者为后端资源服务授予OAuth2令牌。 身份验证服务器用于用户授权以及在外围进行安全的机器对机器通信。
在这个项目中,我使用密码凭证作为用户授权的授权类型(因为它只被本机应用程序UI使用),而客户端凭证作为微服务授权的授权类型。
Spring Cloud Security提供了便利的注释和自动配置,使得从服务器和客户端都可以轻松实现。 您可以在文档中了解更多信息,并查看Auth Server代码中的配置详细信息。
客户端与传统的基于 session 的权限验证类似,你可以从 request 中获取Principal对象信息,校验用户的角色,使用@PreAuthorize注解进行基于正则的访问控制。
每个 client (account-service, statistics-service, notification-service 和 browser)都有一个 scope属性: server
:后台服务, ui
: 浏览器,通过scope 能防止 controller 被外部访问:
@PreAuthorize("#oauth2.hasScope('server')")
@RequestMapping(value = "accounts/{name}", method = RequestMethod.GET)
public List<DataPoint> getStatisticsByAccountName(@PathVariable String name) {
return statisticsService.findByAccountName(name);
}
API Gateway
在这个例子中,存在三个核心服务,将外部API 暴露给客户端,但是在现实世界中,随着系统复杂度的增加,核心服务数也会急剧增长。可能存在一个复杂页面,渲染这个页面需要调用上百个服务。
理论上,客户端应该直接请求每一个微服务,但是这种方式存在很多挑战和局限性,比如:客户端需要了解所有微服务的地址,为每一个信息独立地执行 http 调用,然后在客户端 merger 这些信息。Another problem is non-web-friendly protocols, which might be used on the backend.
通常一个更好的实现方式是使用 API Gateway,它是一个进入系统的单入口,目的是将请求路由到合适的后台服务或者调用多个后台服务并将结果聚合返回给客户端。API Gateway也会被用来做权限验证,监控,压力测试,服务迁移,静态响应处理和主动流量管理
在 Spring cloud 项目中可以通过@EnableZuulProxyannotation注解使用Netflix开源的项目edge service,
在这个例子中我们使用Zuul存储静态内容(UI application),路由请求到合适的微服务上,下面是Notification service的路由配置:
zuul:
routes:
notification-service:
path: /notifications/**
serviceId: notification-service
stripPrefix: false
这个配置意味着所有以 /notifications
开头的请求都会被路由到Notification service,Notification service 地址并没有硬编码,Zuul使用服务发现机制定位Notification service 实例并实现访问的负载均衡。
Service Discovery(服务发现)
通过服务发现能够自动地确定服务实例的网络位置(由于实例数扩展,实例失败/更新,会导致服务实例的网络地址发生变化)
服务发现的关键部分是服务注册,在这个例子中我们使用Netflix Eureka 实现这个功能,Eureka是基于客户端服务发现模式的一个好的例子,客户端负责确定可用服务实例(使用注册服务器)的位置和负载均衡请求。
在Spring Boot中,您可以使用spring-cloud-starter-eureka-server
依赖项,通过@EnableEurekaServer注释和简单的配置属性来构建Eureka Registry。
客户端支持需要使用@EnableDiscoveryClient
注解和添加包含应用名称的bootstrap.yml
文件
spring:
application:
name: notification-service
在应用启动的时候,它会在Eureka Serve中注册,并提供相关的meta-data信息(比如:host,port,健康检查页,主页等)。Eureka从微服务的每个实例接收心跳信息,如果在约定的时间内(可配置)没有接受到心跳信息,这个实例就会被注册中心移除。
Eureka提供了一个简单的页面,在这个页面上你可以查看运行的微服务以及这些服务对应的实例
EurekaLoad Balancer, Circuit Breaker, and Http Client(负载均衡,断路器以及 http Client)
Netflix OSS 提供了另外一套优秀的工具集
Ribbon
Ribbon是一个客户端的负载均衡器,可以通过它控制 HTTP 和 TCP client 请求,与传统的负载均衡器相比,每个线上调用不需要额外的跳跃,你可以直接联系所需的服务。
Eureka本身与Spring Cloud 和 Service Discovery集成在一起,开箱即用,Eureka Client提供了一个可用服务的动态列表,Ribbon可以通过这个列表来实现负载均衡。
Hystrix
Hystrix是熔断器模式的实现,通过网络访问依赖关系来控制延迟和失败。核心思想是在大量微服务的分布式环境中停止级联失败,这有助于系统尽快恢复 。
除了提供熔断器,Hystrix还可以添加一个fallback方法,在主命令失败的情况下返回默认值。
而且,Hystrix为每个命令生成执行结果和延迟的度量标准,我们可以用它来监视系统行为。
Feign
Feign是一个声明式HTTP客户端,与Ribbon和Hystrix无缝集成。 实际上,通过一个Spring-Cloud-Starter-Feign
依赖和@EnableFeignClients批注,您可以拥有一整套负载均衡器,断路器和HTTP客户端,并具有合理的随时可用的默认配置。
下面是Account Service的一个列子:
@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);
}
上面的例子指定了所需的服务id - statistics-service
,依靠Eureka的自动发现
Monitor Dashboard
在这个项目配置中,搭载Hystrix的每个微服务都通过Spring Cloud Bus(使用AMQP代理)向Turbine推送指标。Monitoring project
只是一个小型的包含Turbine和Hystrix仪表板的Spring boot应用程序。
让我们看看不同负载下的系统行为:Account service调用Statistics service ,Statistics service 响应模拟不同的延迟。响应超时阈值设置为1秒。
Monitor Dashboard
Log Analysis
集中式日志在分析分布式系统中存在的问题时十分有效。Elasticsearch, Logstash, 和 Kibana的技术栈让你轻松搜索和分析你的日志,系统利用率和网络活动数据。在这篇文章中可以找到相关的描述。
Security
高级安全配置超出了这个概念验证项目的范围。 要更真实地模拟真实系统,请考虑使用https和JCE密钥库来加密微服务密码和配置服务器属性内容(请参阅文档以了解详细信息)。
Infrastructure Automation(基础设置自动化)
部署相互依赖的微服务,比部署整体应用程序要复杂得多。 拥有完全自动化的基础设施非常重要。 采用持续交付方式,我们可以获得以下好处:
- 随时发布软件的能力。
- 任何构建可能最终成为一个release。
- 一次构建工件,根据需要进行部署
这是在这个项目中实现一个简单的持续交付工作流程:
持续交付流程
在这个配置中,Travis CI为每个成功的Git推送建立标记的图像。 因此,Docker Hub上的每个微服务总是有最新的镜像,而旧镜像使用Git commit hash进行标记。 如果需要的话,部署它们很容易并且快速回滚。
How to Run All the Things?
你将启动8个Spring Boot应用程序,4个MongoDB实例和RabbitMq。 确保您的机器上有4 Gb RAM。 通过Gateway,Registry,Config,Auth Service和Account Service,您始终可以运行重要的服务。
开始之前
- 安装Docker和Docker Compose。
- 导出环境变量: CONFIG_SERVICE_PASSWORD, NOTIFICATION_SERVICE_PASSWORD, STATISTICS_SERVICE_PASSWORD, ACCOUNT_SERVICE_PASSWORD, MONGODB_PASSWORD
Production Mode
在这种模式下,会从 docker hub 下抓取最新的 images,只需复制docker-compose.yml
并点击docker-compose up -d
Development Mode
如果您想自己构建镜像(例如,在代码中进行了一些更改),则必须克隆所有repository并使用Maven构建。 然后运行
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d
docker-compose.dev.yml
继承了docker-compose.yml
,可以在本地构建镜像并公开所有容器端口以方便开发。
Important Endpoints
- localhost:80 - Gateway
- localhost:8761 - Eureka Dashboard
- localhost:9000 - Hystrix Dashboard
- localhost:8989 - Turbine stream (source for Hystrix Dashboard)
- localhost:15672 - RabbitMq management
注意
所有Spring Boot应用程序都需要依赖运行中的Config Server才能启动。 但是,我们可以同时启动所有的容器,因为docker-compose选项始终存在Spring Boot的fail-fast和restart属性。 这意味着所有从属容器将尝试重新启动,直到配置服务器启动并运行。
此外,在所有应用程序启动之后,服务发现机制还需要一些时间,服务都不会立马被客户端的发现,直到实例,Eureka服务器和客户端在其本地缓存中都具有相同的元数据,因此可能需要3个心跳。 默认心跳周期是30秒。
网友评论