本文通过使用 Spring Boot,Spring Cloud 和 Docker 构建的概念验证应用程序的示例, 为了解常见的微服务架构模式提供了一个起点.
该代码可在 GitHub 上获得 https://github.com/sqshq/PiggyMetrics , 图像可在 Docker Hub 上获得. 只需一个命令即可启动整个系统.
作为这个系统的基础, 我选择了一个旧项目, 其后端曾经是一个整体. 该应用程序提供了一种处理个人财务, 组织收入和支出, 管理储蓄, 分析统计数据和创建简单预测的方法.
功能服务
整体应用程序被分解为三个核心微服务. 所有这些都是可独立部署的应用程序, 围绕某些业务功能组织.
帐户服务
包含一般用户输入逻辑和验证: 收入 / 费用项目, 节省和帐户设置.
方法路径描述用户通过身份验证可从 UI 获得得到 / 账户 / {帐户}获取指定的帐户数据
得到 / 帐号 / 电流获取当前帐户数据 ** 得到 / 帐号 / 演示获取模拟账户数据(预先填写的收入 / 费用项目等)
* 放 / 帐号 / 电流保存当前帐户数据 **POST / 帐号 / 注册新帐户
*
统计服务
对主要统计参数执行计算并捕获每个帐户的时间序列. 数据点包含标准化为基本货币和时间段的值. 此数据可用于跟踪帐户生命周期中的现金流动态.
方法路径描述用户通过身份验证可从 UI 获得得到 / 统计 / {}帐户获取指定的帐户统计信
得到 / 统计 / 电流获取当前帐户统计信息 ** 得到 / 统计 / 演示获取模拟账户统计信息
* 放 / 统计 / {}帐户为指定的帐户创建或更新时间序列数据点
通知服务
存储用户的联系信息和通知设置(如提醒和备份频率). 计划工作人员从其他服务收集所需信息, 并向订阅客户发送电子邮件.
方法路径描述用户通过身份验证可从 UI 获得得到 / 通知 / 设置 / 电流获取当前帐户通知设置 ** 放 / 通知 / 设置 / 电流保存当前帐户通知设置 **
笔记
每个微服务都有自己的数据库, 因此无法绕过 API 并直接访问持久性数据.
对于这个项目, 我使用 MongoDB 作为每个服务的主数据库. 拥有多语言持久性体系结构 (以便选择最适合服务要求的数据库类型) 也是有意义的.
服务到服务通信非常简单: 微服务仅使用同步 REST API 进行通信. 现实世界系统中的常见做法是使用交互方式的组合. 例如, 执行同步 GET 请求以检索数据并通过 Message 代理使用异步方法进行创建 / 更新操作, 以便解耦服务和缓冲消息. 然而, 这给我们带来了 最终的一致性 世界.
基建服务
分布式系统中有许多常见模式, 可以帮助我们使所描述的核心服务工作. Spring 云 http://projects.spring.io/spring-cloud/ 提供了强大的工具, 可以增强 Spring Boot 应用程序的行为以实现这些模式. 我会简要介绍一下.
配置服务
Spring Cloud Config 是分布式系统的水平可扩展集中配置服务. 它使用可插入的存储库层, 目前支持本地存储, Git 和 Subversion.
在这个项目中, 我使用 native profile, 它只是从本地类路径加载配置文件. 您可以 shared 在 Config 服务资源中查看 目录 . 现在, 当 Notification-service 请求它的配置时, 使用 shared/notification-service.YAML 和 配置服务响应 shared/application.YAML (在所有客户端应用程序之间共享).
客户端使用
只需构建具有 spring-cloud-starter-config 依赖关系的 Spring Boot 应用程序 , 自动配置将完成剩下的工作.
现在, 您的应用程序中不需要任何嵌入属性. 只需提供 Bootstrap.YAML 应用程序名称和配置服务 URL:
- spring:
- application:
- name: notification-service
- cloud:
- config:
- uri: http://config:8888
- fail-fast: true
使用 Spring Cloud Config, 您可以动态更改应用程序配置
例如, EmailService bean 使用注释 @RefreshScope. 这意味着您可以更改电子邮件文本和主题行, 而无需重建和重新启动 Notification Service 应用程序.
首先, 在 Config 服务器中更改所需的属性. 然后, 对 Notification 服务执行刷新请求: curl -H "Authorization: Bearer #token#" -XPOST http://127.0.0.1:8000/notifications/refresh
您还可以使用 webhooks 自动执行此过程.
笔记
但动态刷新有一些限制. @RefreshScope 不适用于 @Configuration 类, 不能影响 @Scheduled 方法.
fail-fast property 表示如果 Spring Boot 应用程序无法连接到 Config Service, 则会立即失败启动. 当你一起启动所有应用程序时, 这非常有用 .
下面有重要的安全说明.
验证服务
授权职责被完全提取到单独的服务器, 该服务器 为后端资源服务授予 OAuth2 令牌 https://tools.ietf.org/html/rfc6749 .Auth Server 用于用户授权以及周边内部的安全机器到机器通信.
在这个项目中, 我使用 Password credentials https://tools.ietf.org/html/rfc6749#section-4.3 授权类型进行用户授权(因为它仅由本机应用程序 UI 使用), 并 Client Credentials https://tools.ietf.org/html/rfc6749#section-4.4 用作微服务授权的授权类型.
Spring Cloud Security 提供方便的注释和自动配置, 使服务器和客户端都能轻松实现. 您可以在文档中了解有关它的更多信息, 并检查 Auth Server 代码中的配置详细信息 .
从客户端来看, 一切都与传统的基于会话的授权完全相同. 您可以 Principal 从请求中检索 对象, 使用基于表达式的访问控制和 @PreAuthorize 注释检查用户角色和其他内容 .
PiggyMetrics 中的每个客户端 (帐户服务, 统计服务, 通知服务和浏览器) 都有一个范围: server 用于后端服务, 以及 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://highscalability.com/amazon-architecture .
理论上, 客户端可以直接向每个微服务发出请求. 但显然这个选项存在挑战和局限, 例如必须知道所有端点地址, 分别对每个信息的和平执行 http 请求, 在客户端合并结果. 另一个问题是非网络友好协议, 可能会在后端使用.
通常, 更好的方法是使用 API网关. 它是系统的单一入口点, 用于通过将请求路由到适当的后端服务或通过调用多个后端服务并聚合结果来处理请求 . 此外, 它还可用于身份验证, 洞察, 压力和金丝雀测试, 服务迁移, 静态响应处理, 主动流量管理.
Netflix 开源了 这样的优质服务, 现在使用 Spring Cloud, 我们可以通过一个 @EnableZuulProxy 注释启用它 . 在这个项目中, 我使用 Zuul 存储静态内容 (UI 应用程序) 并将请求路由到适当的微服务. 以下是 Notification 服务的简单基于前缀的路由配置:
- zuul:
- routes:
- notification-service:
- path: /notifications/**
- serviceId: notification-service
- stripPrefix: false
这意味着所有以请求开头的请求 /notifications 都将路由到 Notification 服务. 您可以看到, 没有硬编码的地址. Zuul 使用 服务发现 机制来定位 Notification 服务实例以及 Circuit Breaker 和 Load Balancer, 如下所述.
服务发现
另一种众所周知的架构模式是服务发现. 它允许自动检测服务实例的网络位置, 这些服务实例可能由于自动扩展, 故障和升级而动态分配地址.
服务发现的关键部分是注册表. 我在这个项目中使用了 Netflix Eureka. 当客户端负责确定可用服务实例的位置 (使用注册服务器) 并在它们之间加载平衡请求时, Eureka 是客户端发现模式的一个很好的例子.
使用 Spring Boot, 您可以轻松地使用 spring-cloud-starter-eureka-server 依赖项, @EnableEurekaServer 注释和简单配置属性构建 Eureka Registry .
通过 @EnableDiscoveryClient 注释和 Bootstrap.YAML 应用程序名称启用客户端支持 :
- spring:
- application:
- name: notification-service
现在, 在应用程序启动时, 它将向 Eureka Server 注册并提供元数据, 例如主机和端口, 运行状况指示器 URL, 主页等. Eureka 从属于服务的每个实例接收心跳消息. 如果心跳故障超过可配置的时间表, 则实例将从注册表中删除.
此外, Eureka 提供了一个简单的界面, 您可以在其中跟踪正在运行的服务和可用实例的数量: http://localhost:8761
负载均衡器, 断路器和 Http 客户端
Netflix OSS 提供了另一套很棒的工具.
带
Ribbon 是一个客户端负载均衡器, 可以让您对 HTTP 和 TCP 客户端的行为进行大量控制. 与传统的负载均衡器相比, 每次线上调用都不需要额外的跳 - 您可以直接联系所需的服务.
开箱即用, 它本身与 Spring Cloud 和 Service Discovery 集成. Eureka Client 提供可用服务器的动态列表, 因此 Ribbon 可以在它们之间取得平衡.
豪猪
Hystrix 是 Circuit Breaker 模式 http://martinfowler.com/bliki/CircuitBreaker.html 的实现 , 它可以控制通过网络访问的依赖关系的延迟和故障. 主要思想是在具有大量微服务的分布式环境中停止级联故障. 这有助于快速失败并尽快恢复 - 自我修复的容错系统的重要方面.
除了断路器控制之外, 您还可以使用 Hystrix 添加一个回退方法, 以便在主命令失败时获取默认值.
此外, Hystrix 会为每个命令生成执行结果和延迟的指标, 我们可以使用它来 监控系统行为.
假装
Feign 是一个声明式 HTTP 客户端, 可与 Ribbon 和 Hystrix 无缝集成. 实际上, 通过一个 spring-cloud-starter-feign 依赖关系和 @EnableFeignClients 注释, 您可以拥有一整套负载均衡器, 断路器和 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);
- }
你需要的一切只是一个界面
您可以 @RequestMapping 在 Spring MVC 控制器和 Feign 方法之间共享
以上示例指定了所需的服务 ID - statistics-service 感谢 Eureka 的自动发现(但显然您可以访问具有特定 URL 的任何资源)
监控仪表板
在此项目配置中, 每个带有 Hystrix 的微服务都会通过 Spring Cloud Bus(使用 AMQP 代理)将指标推送到 Turbine.Monitoring 项目只是一个带有 https://github.com/Netflix/Turbine 和 Hystrix Dashboard 的小型 Spring 启动应用程序 .
让我们看看我们在负载下的系统行为: 帐户服务调用统计服务, 它响应模拟延迟变化. 响应超时阈值设置为 1 秒.
0 ms delay500 ms delay800 ms delay1100 ms delay 表现良好的系统. 吞吐量约为 22 个请求 / 秒. 统计服务中的活动线程数量很少. 中位服务时间约为 50 毫秒.
活动线程数正在增长. 我们可以看到紫色线程池拒绝的数量, 因此大约有 30-40%的错误, 但电路仍然关闭.
半开状态: 失败命令的比例超过 50%, 断路器启动. 睡眠窗口的时间量后, 下一个请求通过.
100%的请求失败. 电路现在永久开放. 睡眠时间后重试不会再次关闭电路, 因为单个请求太慢.
日志分析
在尝试识别分布式环境中的问题时, 集中日志记录非常有用. Elasticsearch,Logstash 和 Kibana 堆栈使您可以轻松搜索和分析日志, 利用率和网络活动数据. 我的其他项目中 http://github.com/sqshq/ELK-docker 描述了 http://github.com/sqshq/ELK-docker 随时可用的 Docker 配置 .
安全
高级安全配置超出了此概念验证项目的范围. 要更真实地模拟真实系统, 请考虑使用 https 和 JCE 密钥库来加密微服务密码和配置服务器属性内容( 有关详细信息, 请参阅 文档).
基建自动化
与部署整体应用程序相比, 部署微服务具有相互依赖性, 这是一个复杂得多的过程. 拥有完全自动化的基础架构非常重要. 我们可以通过持续交付方法获得以下好处:
随时发布软件的能力.
任何构建都可能最终成为一个版本.
构建工件一次, 根据需要进行部署.
这是一个简单的 Continuous Delivery 工作流程, 在此项目中实施:
在此 配置中, Travis CI 为每个成功的 Git 推送构建标记图像. 因此 latest ,Docker Hub https://hub.docker.com/r/sqshq/ 上的每个微服务始终都有一个 映像 , 旧映像使用 Git commit hash 进行标记. 如果需要, 可以轻松部署其中任何一个并快速回滚.
如何运行所有的东西?
这真的很容易, 我建议你试试. 请记住, 您将启动 8 个 Spring Boot 应用程序, 4 个 MongoDB 实例和 RabbitMq. 确保您 4 Gb 的计算机上有 可用的 RAM. 您始终可以通过网关, 注册表, 配置, 身份验证服务和帐户服务运行重要服务.
在你开始之前
安装 Docker 和 Docker Compose.
出口环境变量: CONFIG_SERVICE_PASSWORD, NOTIFICATION_SERVICE_PASSWORD, STATISTICS_SERVICE_PASSWORD, ACCOUNT_SERVICE_PASSWORD, MONGODB_PASSWORD
生产模式
在此模式下, 所有最新图像都将从 Docker Hub 中提取. 只需复制 docker-compose.YAML 并点击即可 docker-compose up -d.
发展模式
如果您想自己构建映像(例如, 代码中有一些更改), 则必须使用 Maven 克隆所有存储库并构建工件. 然后, 跑 docker-compose -f docker-compose.YAML -f docker-compose.dev.YAML up -d
docker-compose.dev.YAML 继承 docker-compose.YAML 了在本地构建映像的额外可能性, 并公开所有容器端口以便于开发.
重要的终点
localhost:80 - 网关
localhost:8761 - Eureka Dashboard
localhost:9000 - Hystrix 仪表板
localhost:8989 - 涡轮流(Hystrix 仪表板的来源)
localhost:15672 - RabbitMq 管理
笔记
所有 Spring Boot 应用程序都需要运行 Config Server 才能启动. 但是我们可以同时启动所有容器, 因为 fail-fast Spring Boot 属性和 restart: always docker-compose 选项. 这意味着所有相关容器都将尝试重新启动, 直到 Config Server 启动并运行.
此外, Service Discovery 机制在所有应用程序启动后需要一些时间. 在实例, Eureka 服务器和客户端在其本地缓存中都具有相同的元数据之前, 客户端无法发现任何服务, 因此可能需要 3 次听觉. 默认听觉时间为 30 秒.
另外本人从事在线教育多年, 将自己的资料整合建了一个公众号, 对于有兴趣一起交流学习 java 的初学者可以搜索: 程序员文明, 里面有大神会给予解答, 也会有许多的资源可以供大家学习分享, 欢迎大家前来一起学习进步!
来源: http://www.jianshu.com/p/aafce7217f71