本文通过以 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 | 保存当前用户的消息设置 | × | × |
注意
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。具体操作如下:
- curl -H "Authorization: Bearer #token#" -XPOST http://127.0.0.1:8000/notifications/refresh
也可以采用 webhooks 自动执行这个流程
注意
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 中,您可以使用
依赖项,通过 @EnableEurekaServer 注释和简单的配置属性来构建 Eureka Registry。
- spring-cloud-starter-eureka-server
客户端支持需要使用
注解和添加包含应用名称的 bootstrap.yml 文件
- @EnableDiscoveryClient
- spring:
- application:
- name: notification-service
在应用启动的时候,它会在 Eureka Serve 中注册,并提供相关的 meta-data 信息(比如:host,port,健康检查页,主页等)。Eureka 从微服务的每个实例接收心跳信息,如果在约定的时间内(可配置)没有接受到心跳信息,这个实例就会被注册中心移除。
Eureka 提供了一个简单的 页面 ,在这个页面上你可以查看运行的微服务以及这些服务对应的实例
Load 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 无缝集成。 实际上,通过一个
依赖和 @EnableFeignClients 批注,您可以拥有一整套负载均衡器,断路器和 HTTP 客户端,并具有合理的随时可用的默认配置。
- Spring-Cloud-Starter-Feign
下面是 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 秒。
Log Analysis
集中式日志在分析分布式系统中存在的问题时十分有效。Elasticsearch, Logstash, 和 Kibana 的技术栈让你轻松搜索和分析你的日志,系统利用率和网络活动数据。在 这篇文章 中可以找到相关的描述。
Security
高级安全配置超出了这个概念验证项目的范围。 要更真实地模拟真实系统,请考虑使用 https 和 JCE 密钥库来加密微服务密码和配置服务器属性内容(请参阅 文档 以了解详细信息)。
Infrastructure Automation(基础设置自动化)
部署相互依赖的微服务,比部署整体应用程序要复杂得多。 拥有完全自动化的基础设施非常重要。 采用持续交付方式,我们可以获得以下好处:
这是在这个项目中实现一个简单的持续交付工作流程:
在这个配置中,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,您始终可以运行重要的服务。
开始之前
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.yml,可以在本地构建镜像并公开所有容器端口以方便开发。
- docker-compose.dev.yml
Important Endpoints
注意
所有 Spring Boot 应用程序都需要依赖运行中的 Config Server 才能启动。 但是,我们可以同时启动所有的容器,因为 docker-compose 选项始终存在 Spring Boot 的 fail-fast 和 restart 属性。 这意味着所有从属容器将尝试重新启动,直到配置服务器启动并运行。
此外,在所有应用程序启动之后,服务发现机制还需要一些时间,服务都不会立马被客户端的发现,直到实例,Eureka 服务器和客户端在其本地缓存中都具有相同的元数据,因此可能需要 3 个心跳。 默认心跳周期是 30 秒。
来源: http://www.jianshu.com/p/8fcf7cfb49fc