Microservices陷阱:概念篇

2011年5月,microservice一词由一次在威尼斯举行的软件架构工作坊中被提出,直到次年的同次会议中被正式命名为microservices(微服务)。 James Lewis, Martin Folwer

通常,微服务被用于描述一种架构风格。在这种风格下,单个应用软件被设计成一系列可独立部署的服务。尽管业界在微服务的具体定义上还有争议,但其基本上代表了自动化部署、智能终端、语言和数据的去中心化等特征。

1.基本概念

Martin是这样用一句话描述微服务的:

对于每一个小型服务,其拥有独立的进程、轻量化通信机制(通常是http请求)、这些服务围绕着业务上下文构建,并可以实现独立的自动化部署。而这种控制的去中心化允许不同服务可以采用不同的语言和数据库技术实现。

微服务的拥趸把与之相反的设计风格称为monolithic,也就是单一系统。在他们看来,凡是不符合微服务特征的架构,都属于单一系统。

企业web应用中的单一系统和微服务架构

基本的企业web应用架构包含三层,分别是用户界面层(页面)、数据层、以及服务端应用层。其中服务端应用层负责接收请求,处理领域逻辑,操作数据库,然后选择响应的视图。单一系统风格一般是指服务端应用层的设计,任何修改都需要对整个系统进行重新部署。

单一系统风格的缺点是:1.小的更新需要对整个系统进行重新构建和部署;2.单一系统内的模块化设计通常很难维持,事实上很难做到完全的修改隔离;3.扩展时需要考虑整个系统,而不是其中拥有真正需求的一小部分。

针对单一系统的上述问题,微服务提供了自己的解决方案:应用软件被设计为一系列服务,这些服务允许独立部署和扩展。每个服务需要提供一个稳固的模块边界,允许由不同语言和团队实现。

2.微服务的基本特征

利用服务实现组件化

在微服务中,组件化是目的,分割服务是方法。这里的组件是指实现替换和更新独立的软件单位。其中,在不同的场景中软件组件的定义可能不同。一种情况是采用库实现组件,这里的库是链接程序代码、并且直接在内存中调用的一种组件化形式。而对微服务而言,由于服务是进程隔离的,通常采用web service或者rpc实现相互通信的组件化。

基于服务的好处在于,1.允许独立部署,对整体变化更轻量,尽管有些改动不可避免地影响到接口层面,从而产生协同变化,但可以通过聚合服务边界和利用服务契约实现优化演进。2.更加明确的发布接口,对于语言层面来说,发布接口可能没有什么太好的办法,通常只能借助文档并且约束用户破坏组件的封装性,导致组件间的紧耦合。

当然服务也有缺点,首先rpc的效率远不及进程内调用,意味着远程api需要是粗粒度的,但使用起来并不方便。如果需要跨组件的职责变更,跨进程边界通常并不容易。再次,服务内的进程完全独立于外界,包括开发和部署,也就意味着应用服务器和数据库也应当是独立并仅为该服务使用的。

围绕业务能力构建组织

为了避免康威法则,应转变固有的以功能划分团队的方式,尽可能组建全功能团队。对于大型单一系统应用,几乎都可以围绕业务能力进行模块化,但这通常需要团队按照业务线划分,这里遇到的主要问题是,通常这些团队的组织都基于复杂的上下文。如果单一系统跨越了多个模块边界,那么对团队中的个体成员来说消化成本是非常高的。此外,模块线需要一个强有力的约定去保证实施。而服务组件能够提供更加明确的分割,从而使团队边界更清晰。

产品而非项目

绝大多数的应用开发采用项目模型的方式运作,即完成软件并交付给运维团队,然后开发团队解散。而微服务建议摈弃这种项目形式,而是让团队拥有产品的整个生命周期。这种做法的好处是令整个开发团队能够更进一步和用户接触,承担一定的用户支持任务,并理解用户的业务需求。而对于单一系统而言,该做法几乎很难实现。

智能终端和哑管道

当构建不同过程间的通信框架时,多数做法是将业务智能整合进通信机制中,例如Enterprise Service Bus,ESB。ESB产品通常包括了消息路由、编排、变换、乃至业务规则的制定等复杂功能。智能终端和哑管道则相反,由于解耦的需求,服务应当拥有其自身的逻辑,采用类似REST的通信协议,而非WS-Choreography,BPEL或其它采用集中式工具编写的复杂协议。两个好的例子分别是Http API(或protobuf)和轻量级消息总线。后者通常的特点也是“哑”的,如RabbitMQ或ZeroMQ,它们仅仅提供一套异步纤程,智能仍掌握在终端服务手中。

当你试图从单一系统转移至微服务时,最大的难点在于解决内部消息机制。而朴素的转移可能会导致不良通信行为,这里可能需要采用粗粒度方法解决细粒度通信的问题。

去中心化的控制

控制中心化的一个结果是使单一的系统平台成为标准,这么做的缺点在于,并非每个问题都是钉子,也并不是每个问题都是锤子,更恰当的方法是在具体情况下采用正确的工具。

微服务的理念是条条大路通罗马式的,也就是说强调服务的独立性。实际上,你会发现越来越多的开发人员开始从自己的实现中分离出实用工具,并分享至社区(工具之间可能拥有类似的功能,却存在不同的实现形式)。

然而这种自由并不意味着微服务并不遵守服务契约。恰恰相反,像是Tolerant Reader和Consumer-Driven Contracts这种契约模式经常被用于微服务实现,帮助服务能够独立发展。同时契约能够保证新服务开发的精简性,保证YAGNI原则,提高开发效率。

去中心化的数据管理

目前存在许多方法来解决数据管理中心化的问题。在最抽象级,就意味着划分出系统间不同的概念模型。例如,在大型企业级应用中,顾客的销售视图和支持视图之间存在差异,在销售视图中的顾客信息并不会存在于所有支持视图中,而这又会由于不同语义间的细微差别而存在不同或相同情况。

基于上述原因,如果试图把单一系统划分为多个分离的组件,采用领域驱动设计DDD会是一个不错的方法。DDD能够把复杂的领域分解成多个带边界的上下文,并建立其相互之间的映射关系。对于单一系统和微服务来说DDD都很有效,而后者在概念上更加符合DDD增加隔离的思想。

当基于概念模型进行去中心化时,微服务同样要求把数据存储去中心,而单一系统则采用单独的逻辑数据库进行持久化,企业甚至倾向于使用同一个数据库覆盖多种应用(这在很大程度上取决于供应商的许可证业务)。微服务则更加极端,既可以采用同一数据库技术的多个实例,或者采用完全不同的数据库技术实现(Polyglot Persistence,同样可适用于单一系统)。

这里最大的问题是变更管理,因为在许多应用场景中,单一系统采用事务处理的方式保证多个资源间的一致性。而在分布式系统中,事务无疑变得十分困难,这令微服务面临两难的问题:既要保持分割独立,又要保证一致性。而在实际上,微服务的设计思想倾向于强调服务间的弱事务性。这就使得一致性只可能是最终一致性,并采用弥补操作解决相关出现的问题。

这种非一致性管理的问题对多数团队都是一项挑战,但在实际业务中却时有发生。有时具体业务会要求将不一致性保持在一定程度以内,从而快速响应需求,同时拥有一些类似于回退的过程处理错误。一旦修复这些错误的耗费小于保证业务的强一致性,这种权衡就是值得的。

基础设施自动化

近年来,基础设施自动化技术取得了巨大的进步,特别是类似AWS这种公有云服务的兴起,降低了构建、部署、以及操作微服务的门槛。

一般来说,采用微服务的团队通常都能熟练应用持续集成CI、甚至持续交付CD技术,拥有丰富的基础设施自动化背景。CI/CD的目标是令构建和部署变得“无趣”,一旦目标达成,任何扩展就变得非常容易实现,而这在单一系统上已经得到了充分验证。对于微服务来说,该项技术与单一系统相比没有太大区别,但两者的操作域存在明显区别。

高融错设计

当采用服务作为应用组件以后,该应用就需要被设计成面向服务的高容错系统。而在实际情况中,任何服务都有可能发生错误而中止服务,客户端必须据此给出合理的响应。与单一系统相比,这是一个存在于微服务系统中的额外复杂度。

首先是测试,包括在生产环境中执行自动测试,以及当某些服务或者数据中心出错的意外情况恢复和监控。然而对习惯于单一系统的运维团队来说,这种方式可能是一时难以接受。 微服务特别强调服务的实时监控,无论是架构方面,还是业务方面,这种语义化监控能够提早给出出错警告并同时开发团队。对于单一系统,可以把单个应用视为一个微服务,区别是你需要在不同进程中的服务出错时得到警告。而由于单一系统通常采用库实现组件化,相同进程内的服务出错可能并不会得到明确的警告。

总而言之,微服务要求较为完善的监控和日志系统,同时拥有一个实时的dashboard随时通知开发团队,重要的测量单位有断路器状态、吞吐量、延迟等等。

演进化设计

微服务的实践者通常拥有演进化设计背景,他们把服务分解当作未来控制变更的工具之一。控制变更的目的并非是要降低变更频率,而是保证更好、更快地控制变更对软件的影响。 无论何时对现有系统进行组件化分解,你都需要遵守一个分解原则:组件的关键属性之一是独立可替换和升级。这就意味着我们需要寻找一个点,能够通过重写这部分的组件而不会影响其它组件。而实际上,很多团队选择直接将服务碎片化,而非进行长期演进。

在模块化设计中,一个通用的准则是强调可替换性,这样就可以保证接受变更。当你发现你需要重复的修改两个服务时,那就意味着它们应当被合并。

微服务的优势是降低变更发生时的构建和部署时间,缺点是你需要考虑服务变更对消费者带来的影响。一个传统的解决方式是采用服务版本管理策略,但对于微服务来说,应当尽量不要进行版本管理。在许多情况下,我们可以通过设计使得服务尽可能接受不同的消费者请求。

2. 总结

上文几乎列出了微服务架构到目前为止被发现的全部优点,这里列出其缺陷或尚未解决的问题:

组件边界的确定

由于微服务本质上是采用服务实现系统组件化,那么组件边界就成为衡量微服务设计优劣的重要参考价值。一旦设计完成,任何代码级重构、借口变更、向下兼容、以及测试架构的复杂度都会提升。

组件的组成

当组件的组成方式存在瑕疵,剩下来能做的就是把组件内的复杂性转移至组件连接部分。当你关注组件内部时,应当注意组件间的组成方式设计。

团队技能

任何新技术都倾向于更适合拥有中高级能力的团队,但并非对其它团队完全无用。即使是采用单一系统,某些团队依然做的一团糟,而微服务表现如何犹未可知。

最后给出Martin对何时向微服务架构迈进的评论: One reasonable argument we’ve heard is that you shouldn’t start with a microservices architecture. Instead begin with a monolith, keep it modular, and split it into microservices once the monolith becomes a problem. (Although this advice isn’t ideal, since a good in-process interface is usually not a good service interface.)

Comments