微服务:真正的架构模式


【编者的话】本文来自Medium,通过比较CRUD app和数据流app两种应用类型的微服务化探索来向听众介绍微服务。

简介

微服务的神秘和背后的知识令我着迷。微服务作为概念,它属于现代最有趣的架构之一。微服务应用广泛,涉及不同的使用场景。但也有很多地方模糊不清,难以定论。

人们在讨论微服务时,我会努力理解他们的真实意图。尽管在上一次演讲中我分享了对微服务的认识,但我很清楚其它公司和我们使用的架构是不一样的。最近我询问了一位同行,了解到他们部署微服务的方式(和我)不同。所以我打算通过比较这两种方式向技术大会的观众介绍微服务。

这篇文章会举两个例子。第一个是我上次演讲中的微服务例子,属于困难模式。第二个例子以流式架构实现微服务,在我了解的为服务使用场景中,它更接近微服务应用的“理想情况”。

微服务基础

我认为微服务作为架构由以下因素演进而来:
  1. 21世纪初,大量创业公司使用一门语言像rails,来快速扩展他们的业务和团队。遇到某些困难后,他们开始思考,可以用一门语言做哪些合理的事情。
  2. 云计算的发展让我们更便捷、简单地获得服务器实例运行软件。
  3. 我们都认可使用分布式系统架构,尤其是网络调用来解决问题。


伸缩调整、易于获取新硬件、分布式系统与网络,将这些因素结合起来,会发现它们在我提出的“microservice for CRUD”概念中作用巨大。如果你将一家公司拓展到将近成功的水平,但在技术/工程团队拓展上遇到麻烦。将庞大单一的管理拆分成service-style的架构会有很大帮助。这是我的亲身经历。

微服务的要点看起来像这样:
  • 服务支持独立伸缩。如果系统服务的一部分有高负荷或相对其它服务有高容量的需求,你可以扩展服务来满足需求。这在单一系统架构(译者注:比如说Java Web中把代码编译成一个war包发布)里可行,但是有时也会变得很复杂。
  • 服务允许在可控的范围内故障。在您系统中各个模块可独立操作的情况下,可以通过拆分成不同的服务来提高可用性。例如,一款商业app,如果产品的搜索流程崩溃,但是它仍然可以提供检测服务,这将是一件好事。可是实际操作起来比理论要复杂得多,并且人们对于微服务有许多愚蠢的认识,比如暗示服务重叠没有价值。通过设置实现可控的故障并不是必须的,但拥有它情况会好些,但让单一架构做到这一点(设置独立可控的故障域)并不容易。
  • 服务要能支持开发团队人员独立良好地工作在各自负责的模块。再次声明,这单一架构系统中你是可以做到的,因为我就做到了。但整体上的挑战(以及与monorepo(单个代码仓库)中服务相关的挑战)是,当这些“域(译者注:可理解为范围)”的概念以源代码呈现时,工程师要努力去理解理论上与实际编码上的区别。如果我能看到所有代码,并且它们可以编译在一起,就像是一个交付件(而不是各个服务单独拆分构建),我更趋向把它作为整体。这样我就可以从这里抓起代码在另一个地方使用,从那里获取数据在这里使用,等。


我还准备了一些笔记。人们经常弄混“Monolith”和“monorepo”。一个Monolith风格的应用是指把一组代码编译成单独的服务交付件(过程中可能会产生额外的客户端交付件)。你可以通过配置文件来让交付件做几乎任何你能想象的事情,包括上文所有的service-type,但是如果所有代码没有集中放到一个仓库中,这样做最后会生成很大的镜像,也会导致混乱,因为团队有时会根据不同的构建工具和配置文件编译生成不同的交付版本。我依然认为这种架构是单一架构。

Monorepo或者是monolith repository是一种模型,它指一个单一的代码仓库,包含了你正在编码的系统的所有代码(很可能还不包括OSS/外部依赖的源代码)。代码仓库中的代码由多个独立的应用交付件组成,这些代码都可以独立编译/打包和测试,不需要整个仓库的代码。monorepo的一个优势是,当修改了共享库的版本后,依赖共享库的开发人员可以更容易地开发,而不用去等其他团队更新依赖库的版本后再开发。monorepo模型最大的缺点是,支持它的OSS工具不多,因为大部分OSS工具不是这么构建的。所以需要大量投资开发新的工具来支持monorepo模型。

基于CRUD应用的微服务

在介绍CRUD应用由单一架构到微服务架构演进之前,我会先介绍构建中等规模CRUD平台所需的架构设计。这种平台有一个不错的用例,它涉及“事务”和“元数据”。

事务:指用户做了一个行为,你想把它持久化,这里数据的一致性最有价值。CRUD中的Create,Update,Delete操作频率比Read低得多。

元数据:描述用户的信息,但是只能由内部内容创建者修改,外部用户(比如审查者)很少能修改。通常具有很高的可缓存性。甚至有时能够容忍一定程度的临时不一致性(显示陈旧的数据)。

CRUD依赖型公司还有哪些能做的事呢,尤其是分析领域?当然有,你可能需要根据用户在浏览网站时的行为以及其它个性化的操作来频繁地调整结果。但是做到实时调整结果很困难,因为你不一定总是能从用户那里得到足够的数据来达到最好的效果,所以这(指上文调整结果)不是系统的一级关注点。

这种架构在迁移过程中是相对简单的:
  • 确定独立实体。这篇论文Life Beyond Txns有很多有趣又实用的定义。越早迁移架构越好,否则系统臃肿后不得不实现一个分布式事务。你可能希望拥有企业产品,用户等服务的数据,再集成其它面向企业的逻辑和聚合服务。
  • 从实体中抽象出逻辑,集成到服务实体中。尽可能不要去改变数据模型,并且通过重定向访问新的服务实体APIs。


基本上就这些了。你需要做的是收集足够的用户函数、数据和术语,然后改成微服务架构并开发新的功能。

这些服务不是典型的SOA,也不是很小的微服务。拥有数据的服务会更复杂。你可能不想要太多服务,因为你希望在满足用户请求的同时,不需要太多的网络跳转,甚至不需要分布式事务(理想情况)。

可能你不需要每天都开发新的服务,特别是当你有一个五十人的工程团队和一个长期的产品路线图时,你可能不会为了“点一下按钮就动态添加一个新的功能”而投入大量的工程时间到开发编排和工具上。

决定投入多少时间在工具上很简单:开发人员花费在自动添加新服务的时间VS实现和维护自动化程序的时间VS随着时间的推移,你希望添加多少新的服务?比较下它们就可以了。显然,如果你认为让人们能够快速频繁开发微小的服务很重要,你最好投入更多的时间和开发更多的工具。与所有工程优化决策一样,这样做不代表它完全正正确,但是在可预见的未来,它是正确的,而且我们还需要不断地重新评估。

在这个实例中,我发现有许多是微服务必须具备的。比如上文中我提到过很多次的编排。但是如果你不需要自动启动服务或频繁地迁移服务,你则不需要动态服务发现(如果需要的话,负载均衡器是一个不错的选择)。

允许团队为每个服务选择合适的语言、框架和数据存储也不是必须的。事实上,对于你的团队这样做可能是一个令人头疼的问题而不是福音。


为每个微服务创建单独的数据存储。
不要让所有微服务都使用同一个后端存储服务。因为使用单一的数据存储,不同团队编写的微服务就可以容易地共享数据库结构,也会减少许多重复的工作。但是,如果某个团队更新了数据库结构,使用该结构的其他服务也需要调整。
这是真实存在的,但是对于较小的团队,可以通过事先约定好规则来禁止共享数据库结构(如果还有顾虑,可以通过代码审查、自动测试和检测来预防)。如果你很仔细地定义数据相关的服务,不太可能发生这样的事情。另一种方法在下一段介绍:


将数据分离开,可能使数据管理更加复杂,因为分离的存储系统更容易脱离同步或变得不一致,外键也可能被意外更改。这时你就需要增加工具来执行主数据管理(MDM):后台检测然后修复数据不一致性。例如,它可以检测每个数据库的用户id,来验证所有数据库中的id是否一致(任何数据库中没有重复或多余的id)。你可以自己写工具或者买一个。许多商业版的关系型数据库管理系统(RDBMS)会执行这些类型检查,但是它们耦合性很高导致无法伸缩(出处)。
上面这段可能令很多经验丰富的数据检测工程师失望(译者注:因为商业检测服务不支持伸缩,言下之意是要自己开发)。 正是由于数据检测的开销,我鼓励小型组织,在决定使用完全独立的数据存储或个人存储之前,要评估约定数据库共享的方法。决定最终使用何种数据存储服务是可以根据需要来推迟的。

这一版本的微服务架构在扩展CRUD应用方面令人期待,因为这种架构允许你分块重写代码。你可以重写整个系统,或者简单地修改对缩放最敏感的部分。你需要主动参与到涉及到分布式系统复杂性相关问题上,仔细思索数据和基于数据的事务。可能你不需要大量的数据管道,就知道哪里的数据需要修改。

我一定要用微服务来扩展这些吗?答案很可能是no,但是并不意味着使用微服务来扩展系统是一个坏的作法。但是使用极端的微服务模型可能是一个坏主意,因为你很可能不想以分布式事务的方式来切分你的数据。

基于数据处理流的微服务

现在我们讨论一个非常不同的使用案例。这个例子不是你们熟悉的CRUD应用,也不包含臃肿的企业规则和事务更新。相反,这个案例包含大量的数据流。它从不同的数据源接入许多小数据,小数据又汇聚成大量数据。不仅有大量的数据,还有大量的服务来消费、修改数据,或者存储起来以便进一步使用。

主要关注点是接收大量不断变化的数据,以各种方式处理它,并以合适的视图展示给客户。而这些在CRUD应用中是次要的。

我们以一个聚合度量(Metrics)信息的SaaS应用为例。这款应用的客户遍及世界,各种应用、服务、机器都将度量信息发送到聚合器。这些客户只需要查看他们的数据,虽然任何一个客户的数据总数可能非常大。我们的聚合器需要处理这些度量信息,并将它们发送到负责显示给客户的程序。面向客户的应用能够操作实时输入的数据以及来自缓存或者后端存储系统的历史数据。数据的最大价值在于数据在移动窗口内现在/最近都发生了什么。

这种架构从一开始就要考虑数据容量问题,而这在CRUD世界里可能很长时间都不用考虑。另外,数据本身是随着时间不断更新的。“有状态”的数据在事务上最小化更新,最有用的数据是时间序列或者事件日志。事务性的数据,比如存储用户视图、配置等,它们类似CRUD中的“元数据”,与流数据相比很少改变。开发者的时间大部分用于管理输入流、提供新的输入类型、对输入流进行计算或者改变计算方式,而不是处理事务性数据的变更(CRUD)。

本例中,你可以假设一个服务,想要通过对数据流上的特定元素执行不同的计算来进行实验。不修改现有的代码,实验性服务选择一个时间点开始监听数据流,执行新的计算得到新的结果,然后将结果推送回数据流的不同信道上。某一时刻,实验程序从实验客户那提取数据,然后将实验计算结果而不是标准结果返回给客户。你需要记录所有步骤,用于实验完整与调试分析,不要求记录非常详细或者是系统事件甚至用户相关的事务性信息。

本例中,为了更快运行实验程序,编写新的代码可能比更改原有的服务代码更简单。特别是新开发的服务不需要担心调整数据消耗或者与现存服务的计算结果冲突。这就是我所说的“以数据流为中心的微服务”。


如果管理实时数据流给你的企业带来巨大的价值,并且你有非常多的开发者,要创建新的服务来消耗数据流、检测数据和产生结果,你肯定愿意投资开发新工具来尽可能简化服务创建和生产环境部署。你会慢慢把工具应用到所有服务上。但是你也要清晰地认识到,拥有可独立操作和实验的动态数据是微服务化的关键。

Cron job作为微服务

如果没有提到这一点将是我的疏忽。当一切微服务化非常容易时,自然也包括传统的cron job微服务化。

cron job是很好的概念,它没有把一切都视作“服务”。你可以用AWS的CloudWatch Events或者是调度Lambda函数实现这个目的(将cron job微服务化)。也可以使用Gearman调度cron job,它是一个支持队列和异步作业的运行程序。注意你的cron job必须幂等(同一个输入运行两次结果不变)。如果你有更简单的方法运行服务或者是基础的cron job,那没问题(你不需要微服务化)。

结论

我希望这篇文章能在微服务世界中带来多维度的突破。不断思考和尝试对我个人非常重要。它帮助我以极端的角度去理解人们习惯的事情。我指的是把大量时间用于流数据处理与把时间用在CRUD应用弹性伸缩这两个角度。

原文连接:Microservices: Real Architectural Patterns(翻译:adolphlwq)

=========================================
译者介绍
adolphlwq,博客地址:QuanTalk

0 个评论

要回复文章请先登录注册