微服务:真正的架构模式
解剖我们最喜欢的流行架构
介绍
微服务的相关知识和它的神秘令我着迷。概念上的微服务就像是现代最有趣的流行架构之一。它足够功能强大,有着广泛的使用方法;也足够模糊,难以统一而论。
当人们在讨论“微服务”时,我一直在努力了解他们真正所指的是什么。尽管我在最近的演出中部署了我所认为的微服务的一个版本,但我很清楚,我们使用的架构与其他所有公司使用的方法都不一样。最近,我终于询问了一个人,他用一种和我非常不同的方式部署微服务,因此我决定用一种对比分析我们(两种)架构的形式来想广大科技听众进行说明。
这篇文章将有两个例子。首先是“微服务”在我最近的演出中部署的方式,这是一种困难的方式,我也会讲到我为什么决定如此。第二个例子是一个接近于我所听说的“理想化”微服务的架构示例,这种架构以流为中心。
微服务的基础知识
我认为微服务作为一种架构由以下因素演变而来:
- 21世纪后期,一批初创公司开始在rails等大型框架上迅速扩大业务和团队规模,因而框架的合理能力范围发生了变化
- 云技术使得对新的服务器访问及运行软件变得更加容易
- 我们对于分布式系统的应用更加认可,特别是将网络呼叫作为我们的系统的一部分
旧有的架构的伸缩难题,轻松访问新硬件的需求,分布式系统和网络访问的发展——这些新环境下的综合因素在我称之为“CRUD的微服务”中扮演了重要角色。如果您已经基于某一框架将公司规模扩大至通向成功的某一阶段,但您在扩展技术和/或工程团队方面遇到问题,将大的框架打破成服务式架构将大有利处。这是我的直接经验。
微服务的主要竞争力看起来如下:
- 服务允许独立的伸缩。如果您的系统的一部分比其他部件具有更高的负载或容量要求,则可以进行扩展以满足其需求。在一个单一的大框架下当然也可以办到,但会有更复杂的相关问题。
- 服务容许在有限范围内的独立故障。既然系统的某些部分可独立运行,您可以期望将它们分解为服务来实现部分可用性。这可能被认为是一件好事,例如,在商业应用程序中,即使产品搜索流程停止,您也可以提供结账流程。实践比理论复杂得多,而人们又对微服务做出许多愚蠢的期望,如潜意识认为重叠的服务没有价值。故障的独立分割有时更像是一种“加分项”而非必须,但使架构真正地达到这一点并不容易。
- 服务允许团队在系统的某些部分独立工作,例如:从这里抓取代码在那里使用,从那里抓取数据用于此处,等等。同样的,你也可以在大框架中做到这一点。我已经试过了。但基于大框架(以及monorepo(单一源代码仓库)中的服务)时面临的挑战是:人类难以有形地把源代码理解为理论上独立的域。如果我能看到所有的代码,它们都汇编在一起,感觉就像一个孤立整体,那么我就会更倾向于把它作为整体来使用。
我还记了其他的内容:
人们谈论“Monolith”和“monorepo”这两个词时经常会混淆在一起。一个monolithic的应用程序是一组可编译为单个主服务器工件(可能还会生成一些其他客户端工件)的一系列代码。你可以借助配置框架完成你能想到的几乎任何事情,包括上面所有的服务类型,但是生成的镜像往往包含大部分(或是全部)代码库中的代码。这确实很模糊,因为有时团队通过构建工具和配置的组合来发展他们的庞然大物来编译成几个专门的服务器构件。我认为这仍然是一个单一的架构。
Monorepo或monolith存储库是一个模型。在这种模型下,您拥有一个存储库,该存储库包含您正在更改的任何系统的所有代码(因此,可能不包括OSS /外部依赖关系的源代码)。存储库本身包含源代码,该源代码包含作为独立应用程序运行的多个构件,并且可以在不使用整个存储库的代码而单独编译/打包和测试。通常的用法是使用Monorepo令某些共享库可以在所有使用这些库的服务中被修改,因此使用共享库的开发人员可以更轻松地更新它们,而无需等待每个相关团队先更新。Monorepo模型最大的缺点是没有太多支持的OSS工具,因为大多数OSS不是以这种方式构建的,
基于CRUD的应用程序的微服务
在我开始研究CRUD如何由整体架构演变为微服务之前,让我先进一步阐述构建传统的中型CRUD平台所需的架构。这种平台涵盖有一个使用了“事务”和“元数据”的相当不错的用例。
事务:指用户执行一个想要坚持运行的操作,而其中数据的一致性非常有价值。CRUD的“创建,更新,删除”比CRUD的“读取”操作少得多。
元数据:指向用户描述事物的信息,但通常仅由内部内容创建者修改,或者很少由外部用户(如评论者)修改。因此其更新频率较低,通常高度可缓存。更进一步说,它往往可以容忍一定程度的临时不一致(显示陈旧的数据)。
高度依赖CRUD的公司是否有更多的事情要做呢,特别是在分析领域?当然。您可能希望根据用户在浏览网站时的行为和其他个性化操作来频繁调整结果。然而,这是一件很难实时完成的事情,并且您并不总是能从用户那里获得所需的大量数据——因此它通常不是系统的首要关注点。
这种架构搬移过程相对简单:
- 确认独立的实体。Pat Helland 撰写的这篇论文“Life Beyond Txns”中有一些有用且有趣的定义。搬迁工作最好稍微早一些,而不要太晚以至于最终不得不实现分布式事务。您可能需要为主要业务对象(产品,用户等)提供可以收集数据的服务,然后集合来实现对涵盖这些的聚合和逻辑的对象的服务。
- 将逻辑从(数据)实体中拉入服务实体中。尽量不要在这个过程中更改数据模型。随着功能的迁移,重定向Monolith以调用新服务中的API。
基本过程如上所述。直到您有足够的数据和集成条件来覆盖特定的用户功能组,您才能开始发展,然后您可以开始发展用户功能的这一部分,以便在服务中实现新功能。
这些服务不是经典的SOA,但它们也不是足够小的微服务。拥有数据的服务可能相当复杂。您可能不期望太多的服务,因为您希望能在够满足用户的请求的基础上不必进行大量的网络跳跃,甚至在理想情况下不执行分布式事务。
您可能不会每天都在提供新服务,特别是如果您拥有一个不超过50人的工程团队和长期的产品路线图时,应该不希望将大量工程时间投入到复杂的开发协调和工具中,而仅仅是为了可以只点击一个按钮动态地添加新的功能(nb:支持这些的产品一直在变得越来越好,因此在某些时候,即使对于一个较小的团队也值得这么做。只是我还不清楚此刻这些产品有没有这么好)。
用于确定在工具上投入多少资金的公式非常简单:开发人员花费多少时间才能比较自动地添加新服务vs需要多长时间才能实现并维护自动化工作vs你期望随着时间的推移新部署多少新服务?你可以预先估计一下。很明显,如果你认为让人们快速频繁地启动小型服务是有价值的,那么就投入时间和工具。与所有工程过程优化决策一样,它不是两全其美的办法,在未来需要不断重新评估。
在这种实例下,我发现有许多“必备”的微服务不是那么必须。如上面经常提到的协调。如果您没有自动启动服务或频繁移动服务的需求,则也不需要动态服务发现(只需要基本的实现的话,负载平衡器是不错选择)。
允许团队为每个服务选择理想的语言,框架和数据存储当然不是必须的——事实上它可能令您的团队头疼,而不是一个福音。
为服务提供独立的数据存储也不是必须的,尽管它可以避免您将在共享数据库上面临高风险的SPOF。在写这篇文章时,我发现了这样一篇文章:2015年一些关于微服务的文章:
为每个微服务创建一个单独的数据存储
不要在微服务中使用同一个后端数据存储...而且,使用单个数据存储,对于不同团队编写的微服务来说,可以很容易的分享数据库结构,也因此可能是减少重复工作。如果一个团队更新了数据库结构,那么使用该结构的其他服务也必须更改。
这是真的,但对于较小的团队,您可以约定好来阻止共享数据库结构(如果还是担心,可以引入过程和代码审查,并自动测试和检查此类访问)。当您仔细地定义数据所有者服务时,发生这种情况的可能性较小。另一种选择如下一段所述:
拆分数据会使数据管理变得更加复杂,因为单独的存储系统可能更容易脱离同步或变得不一致,并且外键可能会意外更改。您需要添加一个工具,通过在后台运行来执行主数据管理(MDM),以查找和修复不一致性。例如,它可能会检查存储订阅者ID的每个数据库,以验证其中是否存在相同的ID(在任何一个数据库中都没有缺少或额外的ID)。您可以编写自己的工具或购买一个。许多商业关系数据库管理系统(RDBMS)都会进行这些检查,但它们通常对耦合提出了太多的要求,所以不能扩展(原文)
这段话可能会导致任何有经验的数据调解人员疲惫不堪。正是由于这种开销,我鼓励小型组织中的那些人至少在决定使用完全独立的个人数据存储之前评估基于约定的共享方法。您可以(先进行尝试,再)根据需要来稍后决定具体方案。
这个版本的微服务架构在扩展的CRUD方面非常有吸引力,因为它可以让你一块一块地重写。您可以完成整个系统,或者您可以简单地选择对缩放规模最敏感的部分(进行修改)。您应该通过仔细考虑数据以及需要在那些数据上进行交易的方式,积极参与分布式系统复杂性的许多部分。你可能不需要大量漂亮的数据管道。因为你知道哪里的数据将被修改。
你必须使用微服务来扩展吗?可能不必要,但这并不意味着使用微服务来扩展这样的系统是一个坏主意。但是,对于微服务模型而言,极端的做法可能不是一个好主意,因为你应该不是真的希望以分布式事务处理的方式来分割数据。
用于数据流处理的微服务
现在,我们来谈谈一个非常不同的用例。这个用例并不是典型的、与事务更新对象的业务规则密切相关的那种CRUD应用程序。相反,这个用例流经有大量的数据。它有许多不同来源的小数据流入其中,数据总量巨大。这种大数据流又会引入许多不同的服务来使用、修改并将其传递以进一步处理。
此应用程序的主要关注点是摄取大量不断变化的数据,以各种方式处理数据,并向客户展示其视图。此时对数据流的跟踪和根据该数据流发生的情况重新计算信息成为更令人担忧的部分,而CRUD的担忧变为了次要的。
例如,我们来看一个衡量指标汇总的SaaS应用程序。该应用程序向全球客户提供各种应用程序,服务和机器,并将它们的指标报告发送回聚合器。这些客户只需要查看他们的数据,但是任何一个客户的总数据量可能非常大。我们的聚合器需要处理这些指标,并将它们发送给将要显示给客户的应用程序。面向客户的应用程序可能实时运行传入指标、来自缓存以及后备存储系统的历史数据。数据的大部分价值在于显示了现在/最近发生事情的移动窗口。
这个架构从一开始就考虑了数量,而这一点即使规模较小的CRUD可能在很长时间之内也不会在乎。另外,数据本身主要是随时间推移变化的不断更新的数据流。“有状态”概念数据的事物更新很少,最有用的数据更像是时间序列或事件日志。交易数据,比如说存储的用户视图和用户配置,可能更像是我们CRUD应用程序在第一个例子中的“元数据”,与来自数据流的更新相比,很少发生变化。大部分开发人员的时间很可能不是用于处理这些事务性变化,而是花在管理输入流,提供新的输入类型,对输入流应用新的计算以及更改计算上。
在这个例子中,你可以想象一个服务,希望通过在数据流上的特定元素上进行不同的计算来运行实验。实验服务不是修改现有代码,而是在与现有计算同一点监听数据流,提供新的计算值,并将该计算值推回到不同通道上的数据管道中。在某个时间点,实验服务会将这些数据提供给实验客户,且显示的是计算结果而不是标准结果。您需要在所有地方记录发生的事情以便分析实验的成功案例和调试情况,但该记录不需要特别详细,更不要记录事实上与此时系统中其他事件的相关的内容。
在这个例子中,根据需要启动新服务可能更有效,以便进行快速实验,而不是更改现有服务。特别是在服务可以做到这一点的情况下,无需担心与任何现有服务进行数据消耗或生产上的协调。这就是我想称之为“以流为中心的微服务”的模式。
如果您的企业对管理实时数据流有巨大的价值需求,并且将有很多开发人员通过创建新服务来监听它们并产生结果以消耗数据流,那么您绝对愿意在(开发)工具上保证投资以使服务创造过程并投产的过程尽可能地简单化。一旦拥有了它,您可能会随时把它应用于所有服务,但要明确意识到其关键价值是您拥有可以独立进行处理和操作并进行试验的动态数据。
以Cron job(定时工作)作为微服务
如果我没有提到这种模式,那是我的失职。当微服务变得非常容易时,所有东西都将变成微服务,这也包括传统上以cron job运行的服务。
但是cron job是一个很好的概念,并非所有的东西都必须是“服务”。您可以使用AWS的CloudWatch Events实现此目的,或者使用预置好的Lambda函数。还可以使用Gearman,一个队列和异步作业运行器来安排cron job。记住你的cron job需要是幂等的(可以在相同的输入上运行两次而结果不变)。如果你可以很轻易的创建服务、创建基础的cron job的小型服务,那就没有问题——但cron job本身并不是创建大型协调服务环境的重要理由。
结论
我希望这篇文章能是微服务的狂野世界的有用的突破。亲自思考对我来说非常有用。它帮助我理解了人们在极端情况下的正常需求——那些花大部分时间专注于流处理的人所做的事情,显然对那些更关注于CRUD应用程序扩展的人来说没有多大意义。
微服务系统架构微服务系统架构