和之前的开源社区项目:基于Spring Cloud和Docker构建电影推荐微服务(spring-cloud-microservice-example)一样,这次翻译的是基于Spring Cloud和Reactor实现Event Sourcing事件溯源构建网上商店微服务(spring-cloud-event-sourcing-example)开源项目,翻译的文章是项目作者写的关于此示例的博客,因为在看原文时觉得写的很详细,所以决定翻译下,以后好翻看。考虑外网问题,我将原英文文档pdf上传到资源里了,如果你对原文感兴趣,可以下载看看。
文章篇幅很长,我将全文分为两部分,上部分重点讲解理论知识和项目架构要点,下部分是介绍网上商城的架构选择和技术细节的代码实现,文章按照原文顺序翻译,因自己翻译水平有限,希望读者能指出不足以作改正。
如果你对云应用很了解,可以直接移步下载运行项目(https://github.com/kbastani/spring-cloud-event-sourcing-example),或跳转到部署步骤。
English Tutorial provided here: http://www.kennybastani.com/2016/04/event-sourcing-microservices-spring-cloud.html(原文翻译如下)
当在微服务架构中构建应用时,状态管理成为分布式系统的问题,相比于传统monolithic单体应用,将状态管理通过事务机制实现,微服务能够通过一种跨多个不同应用和数据库的新的分布式事务来管理一致性。
这篇文章中,我们将探索微服务中的数据一致性和高可用性权衡问题。我们将首先看看在处理分布式系统数据一致性背后的一些重要的概念和主题。
本文以一个在线网上商城网站为案例,使用Spring Boot和Spring Cloud实现,展示如何使用Reactor在微服务中实现Reactive流,从而通过event sourcing事件溯源实现分布式事务,最后,我们会使用Docker 和 Maven构建、运行、协调多容器应用。
当建立微服务时,我们被强迫面对结构状态的最终一致性问题,这是因为每个微服务都特定暴露自己拥有的数据库资源,每个数据库都根据自己类型配置了不同的一致性和高可用性权衡策略。
最终一致性是一种用于描述在分布式系统中数据的操作模型,在分布式系统中状态是被复制然后跨网络多节点保存,在关系数据库集群中,最终一致性被用来在集群多个节点之间协调数据复制的写操作,数据库集群中这种写操作挑战是:各个节点接受到的写操作必须严格按照复制的次序进行,这个次序是有时间损耗的,从这个角度看,数据库在集群节点之间的这种状态复制还是可以被认为是一种最终一致性,所有节点状态在未来某个时刻最终汇聚到一个一致性状态,也就是说,最终达成状态一致性。
当首次构建微服务时,最终一致性是开发者 DBA和架构师频繁打交道的问题,当开始在分布式系统中进行状态处理时,头疼问题更加严重。核心问题是:
如何在保证数据一致性基础上保证高可用性呢?
要回答这个问题,我们需要了解如何最好地处理在分布式系统中的事务,碰巧大多数分布式数据库与健康这个问题确定科学的帮助。
现在几乎所有数据库都支持高可用性集群,大多数数据库对系统一致性模型提供一个易于理解的方式,保证强一致性模型的安全方式是维持数据库事务操作的有序日志,理论上理由非常简单,一个事务日志是一系列数据更新操作的动作有序记录集合,当其他节点从主节点获得这个事务日志时,能够按照这种有序动作集合重新播放这些操作,从而更新自己所在节点的数据库状态,当这个事务日志完成后,次节点的状态最终会和主节点状态一致。
上面的图表表示群集中的三个数据库在使用共享事务日志复制数据。拉链标记的主键在这种情况下表示最权威数据库的当前视图。拉链之间的差异代表每个副本的一致性,当事务被重播,每个副本和主键靠拢于一个一致的状态。这里的基本思想是用最终一致性,即所有拉链最终都将压缩,和主键一致。
数据库的事务日志,即预日期计算,在使用历史上有深厚的根基。管理有序事务日志的基本方法是威尼斯商人早在15世纪首先使用的,这些威尼斯商人开始使用的方法被称为复式簿记系统,这是一个簿记系统,要求每个交易需要两个并排的条目。为了每一笔交易,信用卡和借记卡都指定了从一个帐户到目的地帐户。计算一个帐户的平衡,任何商家都可以简单地复制所有账户的当前状态,在分类帐重现事件记录。同样的记账概念在今天依旧使用着,并在某种程度上成为现代数据库系统的事务管理基本概念。
对于声称最终一致性的数据库,它是保证数据库集群中的每个节点通过简单重演导致事务日志合并写事务的副本,而收敛到一个全局一致的状态。然而不管怎样,这种说法只是一种保证数据库的活跃属性,而忽略对其安全属性的保障。安全与活跃性之间的区别是,最终一致性,我们只能保证所有更新将观察到最后,但不保证正确性。
现在大多数可见的内容都试图让我们接受微服务的好处,这背后将包含一个非常稀疏的解释说“微服务使用最终一致性”——有时引用CAP定理来支持任何意义上的存在的困惑。这往往是一个肤浅的解释,导致问题多于答案。一个最合适解释微服务的最终一致性问题将是下面的观点。
Microservice architectures provide no guarantees about the correctness of your data.(微服务架构对于你数据的正确性不提供担保)
通过道路上存在的巨大炒作去创建微服务,不仅是重要的,它是一个向所有开发人员必须面对的可能性。这是因为当涉及构建软件时,一个分布式系统是一个分布式系统,一组相连的微服务集合也不例外。好消息是,有尝试和真正的设计如何成功构建和维护复杂的分布式系统,这就是本文的其余部分的主题。
Event sourcing(事件溯源)是借鉴数据库事务日志的一种数据持久方式,在ES中,事务单元变得更细粒度,使用一系列有序的事件来代表存储在数据库中的领域模型状态,一旦一个事件被加入事件日志,它就不能被移走或重新排序,事件被认为是不可变的,事件序列只能被追加方式存储。
在微服务架构中使用事件溯源处理状态有很多的好处:
在本文中,我们将着眼于使用Spring Cloud和Spring Boot实现基于JVM的事件溯源应用。如同大多数的文章,你会发现在这个博客上,我们将参观一个现实的示例应用程序,您可以运行和部署它。这一次我已经将一个使用微服务的端到端的原生云应用例子整合在一起,这个甚至包括了AngularJS的前端。谢谢在Spring Engineering团队的Dave Syer博士提供的一些非常聪明的基础工作。
如前所述,这个参考应用被设计为一个云原生应用。云原生应用和架构的设计和使用一组标准的方法,最大限度地发挥云平台的效用。云原生应用程序使用的东西被称为十二因素(twelve-factor)应用方法。十二因素论是一套实践和有益的指导,由Heroku工程师编制,已成为创造适合部署到云平台上的应用程序的参考标准。
云原生应用架构通常会拥抱扩展基础设施原则,如应用程序和数据库的水平扩展。应用程序还注重在弹性和自动愈合,防止停机基础上构建,通过使用一个平台,可用性可以自动调整为必要的一组使用政策。同时,对服务的负载均衡转移到客户端和应用程序之间处理,摆脱对新的应用程序实例负载均衡器配置的困窘。
在这个博客上,你会发现我已经从其他微服务引用应用程序中有了大的飞跃。此应用程序被创建,以展示一个完全形成的微服务架构,实现了一个在线商店的核心功能。
这个在线商店应用程序的用户将和托管的网上商店网站的前端页面交互(https://github.com/kbastani/spring-cloud-event-sourcing- example/tree/master/online-store-web)。这是一个Spring Boot应用程序,并在上图中使用紫色颜色标注,这个应用程序提供了AngularJS应用网站的静态内容。
在后端的微服务上写一个前端应用是面临的主要问题,当使用客户端JavaScript框架如AngularJS的时候,如何安全地暴露REST API在包裹静态JS内容的同一主机上。我们需要解决这一挑战,以防止可能的来自多个领域公开访问我们后端REST APIS服务导致的安全漏洞。但是如果我们在不同的领域部署这些微服务集群,我们就需要启用跨源资源共享(CORS),这将使我们的应用程序的后端容易受到各种形式的攻击。
为了解决跨源资源共享(CORS)的问题,我们需要有一个优秀的工具套件,而这正是Spring Cloud项目生态系统的一部分。
回顾参考架构的图例,我们可以看到,Online Store Web微服务可通过HTTP连接的中间层直接到其他四个应用程序,这些服务包括:
每一个Spring Boot应用程序被认为是为在线商店的Web应用程序提供支持服务。支持服务是一个术语,它是十二因素推广的方法,提倡的前提是对第三方服务依赖关系应该被视为原生云应用程序的附加资源(我的理解是降低耦合度)。支持服务的关键特征是,他们提供了类似在其部署环境中绑定到应用程序的云平台。
图中的四个支持服务在部署的目标环境运行时,将被绑定到在线商店Web上。一个云平台,比如广受欢迎的开源PaaS Cloud Foundry,将为应用提供安全证书和URI以注入环境变量形式作为外部配置属性。
这些支持服务不同于上图例底层的原因是,这些支持服务必须使用静态定义的路由位置才可以作为一个环境变量注入到在线商店的Web应用程序容器中。支持服务总是有这个定义的特征,这种方法被认为是一个为连接到数据库或服务提供一个安全凭据的生产应用程序的标准做法。这里的规则是:如果它不能被发现使用发现服务和你部暑应用程序时将依赖一个静态定义的路由,那么它就被认为是支持服务。
返回到图中,底层的服务不需要有任何静态定义的路由。只要四个支持服务定位到了一个地址,通过支持服务使用Discovery Service(发现服务)和Edge Service(边缘服务),底层服务都可以发现。
User Service(用户服务)是在应用程序微服务架构中保护后端资源的身份验证网关,对于暴露给前端应用的资源,它有两种暴露方式:受保护和不受保护。受保护的资源是一个需要用户级别的身份验证。未受保护的资源通常是一个只读的资源集,这些资源可以由未经身份验证的用户查看,如产品目录。
用户服务通常包含一个Spring Cloud OAuth2授权服务器类似于资源服务器。正是这项服务,在目标环境中的所有其他应用程序将能够使用检索和验证令牌信息,验证的令牌信息将自动地提供在请求保护资源的请求头中,并用于对用户会话进行身份验证。
如果用户在访问受保护的资源时,不提供身份验证细节的头请求,他们将被重定向到用户服务登录页面,在那里他们能够安全地登录和授权,并获得一个访问令牌。
Edge Service(边缘服务)是一个Spring cloud应用程序,负责从后端微服务中安全暴露HTTP路由。边缘服务在Spring Cloud微服务架构中是一个非常重要的组成部分,因为它提供了暴露所有来自后端服务API,作为一个统一的REST API,给前端应用使用的一种方式。
利用边缘服务,一个Spring Boot应用程序将简单地把它作为一个在目标环境中的支持服务。在这个过程中,边缘服务将提供安全认证访问所有REST API所暴露的后端服务。为了能够做到这一点,边缘服务匹配一个来自前端应用程序到后端微服务请求路由的URL片段,通过一个反向代理来获得远程REST API接口的响应。
最终的结果是,边缘服务提供了一个无缝的REST API,将适合嵌入任何Spring Boot应用,将它作为一个使用Spring Cloud Netflix Zuul启动项目的支持服务。
Discovery Service(发现服务)是一个Spring Cloud应用程序,该应用程序负责维护目标环境中服务信息的注册表。每个服务应用程序在目标环境启动的时候都会去订阅服务发现应用程序,订阅应用程序将提供其当地的网络信息,包括网络地址。通过这样做,在环境中的所有其他应用程序通过下载一个服务注册表并缓存它在本地,将能够找到其他用户。本地服务注册表将被用于作为一个应用程序依赖于目标环境中的其他服务的网络地址的所需的基础上。
Configuration Server(配置管理)是一个集中外部配置使用各种方法建筑十二要素应用的Spring Cloud应用。十二因素的应用程序将配置文件存储在环境中,而不是在项目的源代码里,此服务将允许其他应用程序检索其针对目标环境的定制配置。
虽然在中间层的支持服务仍然被认为是微服务,他们解决了一系列对纯粹操作和安全相关的关注,而这个应用程序的业务逻辑几乎完全位于我们的底层,它将围绕在虚构的在线商店的业务能力上做设计。(我现在已经提前贴上原生云,一个假设的硅谷创业公司要销售四件真正聪明的t恤和连帽衫)
作为一个在线商店的业务能力的一部分,我们有以下5个微服务,将作为我们后端的REST API。这些api的主要消费者是Online Store Web,以及其他计划面向客户的应用程序,可能无法看到的情况下,原生云公司是无法等到在沙丘路光明的一天,即从一个顶级的风投公司获得种子轮投资。
这些微服务通过在中间层的边缘服务应用程序,公开为一个无缝的REST API。边缘服务使用Spring Netflix的Zuul代理将请求映射路由从在线商店的Web应用程序到适当的后端微服务的REST API。
这些应用程序可以发现:
在此示例项目的GitHub库上,有对这些微服务作用的描述。我将定期贡献出应用在这个项目库中的有预见的文章,文章将专注于更多的模式和最佳实践的微服务架构。
在这篇文章的下一部分将集中在微服务中使用Spring Boot、Spring Cloud和Project Reactor实现事件溯源的原始主题。