如何迁移到微服务和事件溯源EventSourcing

banq 18-03-06
                   

这是一篇提供如何从单体大型应用迁移到微服务+事件溯源的指导性文章,文章提供了六条建议,主要是确定微服务边界,将事件作为首要设计,将系统从过去面向接口的耦合变成面向事件数据的耦合,从而大大地增加微服务的独立性和灵活性,同时为性能的弹性扩展提供了可能。所谓面向接口耦合,就是你事先设计几个接口,定义其中的方法,让耦合双方共同围绕这些接口进行实现和调用,如果这是一种X坐标思维,那么你可以转换到Y坐标思维,将这几种接口类型设计为几种事件类型,让使用者和被使用者通过发布事件和获取事件方式进行耦合,这种耦合方式比接口耦合更加松耦合,而且可扩展。

下面是Pedro Costa这篇原文的大意翻译:

像'微服务'和'事件溯源Event-Sourcing'这样的流行语对软件行业来说绝对不是新鲜事物,因为它们已被用于解决大多数大型企业应用程序面临的共同问题。现在已经有更多关于它们的累积知识,并且越来越多的应用程序正在使用这种模式构建,因此更容易得出一些结论。在此,我将列举一些关键考虑因素,同时思考如何将大型单体应用程序迁移到微服务和事件溯源架构。我还将提供一些指导方针,让您在迁移过程中遵循这些指导原则,同时对我自己使用的库和工具(如Apache Kafka和Protobuf)分享一些心得,以便在最近开展的一些项目中是如何实现它们的。

微服务
如果读者不知道关于微服务架构,可以参考Martin Fowler的话:

简而言之,微服务架构风格是一种将单个应用程序(单体)分解为一套小型服务开发的方法,每个服务都在自己的进程中运行,并通过轻量级机制(...)进行通信。这些服务是围绕业务功能构建的,可以通过全自动部署机制独立部署,(...)可以用不同的编程语言编写,并使用不同的数据存储技术。

架构师喜欢它们,因为它们是将大型紧密耦合的软件巨石分离成小型、独立和松散耦合的服务的有效方式。这些独立服务应该具有有限的范围,明确的责任,并有权拥有自己的数据模型。

这套特性强化了模块化和责任分离,提高了独立开发团队的自主性,因为他们将从事具有明确边界的任务,解耦依赖关系并减少与其他团队工作的重叠。这种自主权允许开发团队选择最适合他们手中每个问题需求的工具。DevOps团队也应该喜欢这种方法,因为微服务应该易于部署并运行在自己的容器中。

我还应该提到可伸缩性和容错性方面的好处 - 运行在孤立进程上的微服务通过仅启动服务的新实例而在负载高峰期更容易扩展; 整个系统变得更具弹性,因为单点故障减少:如果某项服务降级或失败,其他服务能够独立运行工作。

事件溯源


事件溯源是一个完全不同的故事。它的想法是以不可变事件的形式表示每个应用程序的状态转换。事件随后以日志或日记的形式被存储,因为它们发生(也可以参考“事件存储”) 。它们也可以被无限期地查询和存储,旨在表示整个应用程序的状态随着时间的推移而发展。

为什么要把微服务和事件采购放在一起?
请记住,通过选择微服务,我们的目标是构建能够动态扩展和适应传入流量的系统,最好是伸缩规模,这意味着更好更高效地利用可用资源,并最终减少我们所需资源的数量系统。

要建立Reactive响应反应式的应用,这不是依靠传统的阻塞请求/响应方式,而是一种立即响应的方式,它增加了整个系统的响应能力的使用可用资源的更好的有效途径。与其阻塞并等待计算完成,应用程序将忙于使用可用资源异步地处理用户请求,同时在不会在主线程上执行繁重的任务而造成线程阻塞。

正是在这里,事件才发挥作用:事件是思考反应系统的核心问题,它代表了在微服务之间实现异步通信的有效方式,并代替了传统的同步/阻塞模型,如JSON over REST / HTTP或其他对等体对特定协议。代表了每个时刻的应用程序最新状态,不同的微服务可以使用事件来构建其本地状态和数据模型。微服务可以在短期内使用Pub /Sub模式来仅使用与其范围相关的事件。

Konrad Malawski的报告:为什么是反应性的? 对于Reactive应用程序如何成为实现可伸缩性和响应能力以及分布式系统复杂性的相关方式而言是非常好的选择。

但是,真的,你真的应该迁移吗?
到目前为止,我们已经将微服务和事件确定为拥抱模块化、反应式和非阻塞应用程序的模式; 现在我们将讨论如何将事件存储、查询并广播到感兴趣的服务。但是,也许现在是时候回答你的问题了:我是否应该真正转向微服务和事件采购架构?答案是:这取决于!

确定应用程序是否能够充分受益于这一复杂体系结构的一个很好的经验法则是查看其目标平台 - 您的应用程序是否有要求在不受您控制的异构客户机特定平台上运行?您的系统是否需要旧式“下一个,下一个,下一个类型”安装器?

如果您对上述问题回答“ 是”,那么您的系统可能不会从微服务体系结构的全部潜力中受益,因为您无法监控平台的运行状况或动态调整资源以应对系统负载。按需生成新的微服务实例以响应高流量峰值也不可行,因为您无法控制可用资源和可用性统计信息来有效执行操作。

另一个重要问题是:您的应用程序是否具有很高的性能要求,或者在很长一段时间内每秒会有数千次用户请求?

如果这次你回答否,你可能不会从异步中受益,因为你可以构建传统的同步和单用户的用例。

同样重要的是要注意,如果您正在讨论的是带有大型和旧式代码堆栈的应用程序,而这些应用程序并非围绕强大的功能责任明确分离的固体模块化架构构建,而是依靠通用API进行内部通信,对于你和你的团队来说,重新构建它可能会更有效,而不是花费大量的时间来分解它,并改变它的内部通信流。对您的系统是否值得采取行动做出冷淡和公正的判断很重要,因为迁移过程肯定会花费您大量的时间和资源。

将单体移植到微服务中
这里的想法是执行增量迁移,以使其尽可能平滑和无痛。试图一次性做到这一点将是一个自杀式的使命 - 一方面,没有人会以完美的判断力乐于处理这种移民意味着的“大爆炸”整合,另一方面,没有经理人将推迟2到3个月的功能路线图,以便开发团队可以颠倒项目的体系结构。

我推荐的方法对我来说很合适,就是避免重写现有的生产代码,至少在发现实际问题或发布新功能之前。尝试开始围绕现有系统构建新的服务,采用更具反应性的开发方式,只有在需要时才重写旧的内部结构。




1 - 确定主要用途
第一个也许是你手中最难的任务是定义服务的边界。记住:一个 微服务应该有一个定义明确的责任,应该映射到一个或多或少简单的需求用例。

正如您现在应该知道的那样,服务应尽可能与其对等体分离并自治,并拥有自己的持久性模块。这种方法可以更有效地处理数据模型,因为每个服务只会管理和存储它真正需要的状态,并且以一种非常适合其使用情况的格式 - 更多的模块化和责任分离点。

2 - 解耦内部组件并重新定义其数据模型
既然您已经明确了主要需求用例并定义了微服务的边界,那么使用这个设计阶段来查看您的应用程序,并反过来确定其当前软件层可以如何映射新定义的服务。不要同时期望这是一件容易的事,因为通常整体式应用程序不是面向用例的:大多数用例将跨越多个抽象层并执行多次数据转换。

在微服务方法中,我们旨在实现自顶向下的隔离,每个用例都尽可能少地与其他服务进行交互,并且尽可能以最少的数据转换。要获得高响应吞吐量,对每项特定任务使用优化的数据模型至关重要。举例来说,也许这将是有帮助的JSON支持的数据库,像Elasticsearch用于对于只生产JSON响应,或可能考虑服务基于图的数据库像Neo4j用于只执行路由计算的服务。

3 - 设计清洁和通用的API
现在是时候通过设计干净和演变的API来规范服务间通信协议。记得事件吗?现在你应该实现它们并选择一种技术。在这个设计阶段,您将把所有应用程序的状态转换编码为小型的面向实体的事件。这些事件将在系统内部流动,并被几种不同的服务消费使用以建立本地状态,因此为事件定义选择一种可靠的技术至关重要,它具有经过验证的快速处理和良好的编码/解码性能结果。

我一直在使用Google的Protobuf获得很好的体验,但是还有其他几种类似技术适合像Thrift或Avro这样的任务。文件大小和序列化/反序列化时间方面,Protobuf和Thrift非常类似,比普通的JSON或XML表现得更好。Avro对于大型对象表现不错,但对于我们的小型事件则不太合适。

Protobuf是一个非常棒的协议:Google在其核心应用程序中使用它,它有很好的文档记录并被业界广泛采用。它也被证明非常适合通过支持可选字段来允许API的演变。这使得不同版本的相同API可以被不同的服务使用,只要遵循将'non-required'添加到新字段的方法,让应用程序层实现默认逻辑,以避免这些字段缺失。

4 - 添加事件代理
如果事件在您的应用程序中被视为一等公民,您最好找到一种有效的方式来充分利用它们 - 您选择的事件代理对整体性能至关重要,因为它将成为实现不同微服务的核心组件相互沟通。

对于Apache Kafka我有很好的评价 - 它已经被证明是一个可靠和高效的平台,用于存储和发送每秒数千个事件。你可以找到关于卡夫卡的复制和容错模型的更多细节在这里

Kafka既可以用作消息代理,也可以作为事件的持久存储,因为它可以无限期地存储在磁盘上,可以随时从主题topic中消费使用它们(但不会删除)。

你的主题topic将是不可改变的,并且有序的事件将以结构化的形式增长。然后,事件被分配offset变量以唯一地识别它们在主题中位置  -卡夫卡可以管理偏移量本身,很容易提供“最多一次”或“至少一次”的传递语义,当一个事件消费者加入一个主题,允许微服务从任何时间点开始消费事件。如果最后将事件偏移量在用户成功完成时事务性地保留在服务的本地存储中,那么可以很容易地使用该偏移量来实现“恰好一次”事件传递语义。

5 - 仔细设计事件主题




上图是比特币交易采取的(并且非常简化)的微服务+事件采购event-sourcing架构。面向实体的主题的亮点是面向用例的微服务

这里的主题是一个重要的抽象,对它们建模的不同方式将会产生所有的不同点:它们就像事件的存储库一样,用户可以根据自己的意愿定义多少。已经证明对我有效的策略是为每种实体类型的事件使用不同的主题,使它们面向实体而不是面向用例。 这样,我总是知道某个Topic只代表特定数据模型实体的状态转换,而不是将它成为用例的一部分。这种方法使事件消费大大简化,使服务仅消费他们感兴趣的状态转换,而且这通常是与不同微服务有关的唯一的通用信息。

这个概念在上图 - 主题中存储了第一类实体,这些实体是一些事件实体,例如Logins,Orders或Fills; 微服务处理简单的用例,如下订单,显示订单簿,显示价格图表等。我们还可以看到无状态服务根本不共享数据库:Trading History服务使用更多的面向JSON的持久性模型,对于User Balance服务来说,关系数据库就足够了。人们还应该注意到,像Price Chart或Order Book这样的不同的有状态服务通过消费Fills Topic和其他地方的事件来独特地建立其本地状态。

对于确实需要不同服务之间的通信的更复杂的用例,必须认识到完成用例的责任 - 用例是分散的,并且只有当涉及的所有服务都确认他们的任务已成功完成时才会完成,否则整个用例必须失败并且必须采取纠正措施来回滚任何无效的本地状态。这实际上是一种在微服务架构中使用的称为“ Saga”的非常常见的模式。

6 - 将碎片粘合在一起
现在您已经完成了所有建议的(重新)设计阶段,现在是部署新基础架构并实施新的简单功能的时候了。请记住,一次只能提供一项服务,给自己留下实验消息代理的空间,并收集有关新的反应式系统的性能统计信息。不要忘记适当地衡量你的性能和响应时间,因为在适当的平台微调之前,有时会引入一些意想不到的延迟开销。

最后的想法
请记住,微服务应用程序是分布式系统,分布式系统本质上是复杂的。话虽如此,不要指望迁移到微服务和事件采购是一件容易的事情,也不要期望在第一次尝试之后就能立刻获得它。你肯定会犯错误,但我相信如果你按照我在这里描述的主要指导原则,他们会大大减少: 执行增量迁移 ; 为未来的改进留出空间;


Migrating to Microservices and Event-Sourcing: the



[该贴被banq于2018-03-07 08:07修改过]

                   

3