本文是构建微服务系列文章的第七篇,也是最后一篇。第一章介绍了微服务架构模式并讨论了微服务的利弊,之后的文章讨论了微服务架构的不同方面:API网关的使用进程间通信服务发现事件驱动的数据管理以及微服务的部署。在本文中我们将探讨如何将单体应用迁移到微服务架构中。

我(指作者)希望这一系列文章能有助于更好地理解微服务的架构,利弊和使用时机。微服务架构可能正好适合你的组织。

然而,很可能你正处理一个巨大、复杂的单体应用,每天应用的开发和部署经历非常漫长和痛苦。微服务似乎是一个遥远的天堂。幸运的是,你可以采用一些策略来逃离单体应用的地狱。本文会描述如何将单体应用递增地重构成一系列微服务。

一、重构为微服务概述

将单体应用转变为微服务的过程是应用现代化(application modernization)的一种方式。这也是一些开发者最近几十年在做的事情。所以当重构应用为微服务时,有一些方法可以重用。

一种策略是“不要使用大爆炸式重写(Big Bang rewrite)”。这意味着从头开始将全部开发精力集中到构建基于微服务的应用。虽然它听起来很吸引人,但是风险极大并可能以失败结束。正如Martin Fowler所说,“大爆炸式的重写能保证的唯一事情还是大爆炸!”

代替大爆炸式重写(Big Bang rewrite),应该递增地重构单体应用。应该以微服务的形式逐渐增加新功能并为已有功能添加扩展——以辅助的风格修改单体应用,并且同时运行微服务和修改后的单体应用,单体应用中实现功能的数量会随着时间逐渐减少,直到完全消失或者成为另一个微服务。这种策略类似于在高速公路上以每小时70英里的速度开车——充满挑战,但是相比于尝试大爆炸式的重写风险较低。

Martin Fowler提出应用现代化策略是扼杀应用(Strangler Application),名称来自于扼杀藤(strangler vine),又叫扼杀无花果,在热带雨林中被发现。为了能够接触森林上面的阳光,这种藤缠绕着一棵树成长。有时树死了,留下一个树状的藤蔓。

这里写图片描述

应用现代化也遵循相同的模式。我们将构建一个包含微服务的新应用,该应用围绕着遗留应用,这个遗留应用将会萎缩,最后死亡。

让我们了解一下完成这种改造的不同策略。

二、策略一:停止挖掘

正如Law of holes所说,当在洞里时,应该停止挖掘。当单体应用难以管理时,这同样是一个可遵循的良好建议。换句话说,应该阻止单体应用变得更大。这意味着当实现新功能时,不要向单体应用继续增加代码。代替的是,这种策略的核心观点是将新代码构建成一个独立的微服务。

图 7-1 显示了应用这种方法之后的架构。

这里写图片描述

图 7-1 将新功能实现为单独的服务而不是向单体应用中添加模块

除了新服务和遗留的单体应用,在上面的架构中还有两个其他组件。

  • 第一个是请求路由器,它处理(http)请求。类似于第二篇文章中所说的API网关。路由器将请求发送给对应新功能的新服务,路由遗留的请求给单体应用;
  • 另外一个组件是胶水代码,它将服务和单体应用相结合。服务几乎不独立存在,它需要经常访问单体应用拥有的数据。胶水代码属于单体应用或者服务,抑或两者兼有,负责数据整合。服务使用胶水代码来读写单体应用拥有的数据;

服务可以使用三种策略访问单体应用的数据:

  • 调用单体应用提供的远程API
  • 直接访问单体应用的数据库;
  • 自己维护数据的备份,该数据与单体应用的数据库同步;

胶水代码有时候也叫做反腐败层(anti-corruption layer),这是因为胶水代码可以防止拥有自己全新领域模型的服务被遗留系统领域模型的概念所污染。胶水代码在两种不同的模型之间进行转换。术语反腐败层首先出现在必读书籍——Ecric Evans所写的领域驱动设计一书中,接着被提炼为白皮书。开发一个反作弊层是意义重大的任务,但是如果想要走出单体应用的地狱,这是十分必要的。

将新功能实现为轻量级服务也有很多的好处。它阻止单体应用变得更加难以管理。服务可以独立于单体应用进行开发、部署和扩展。对于开发的每个服务,都会经历微服务架构带来的好处。

然而,这个方法并没有解决单体应用的问题。为了解决这些问题,需要拆分单体应用。我们来了解一下如何处理这个问题。

三、策略二:拆分成前端和后端

一种缩小单体应用的策略是将表现层与业务层、数据访问层分离。典型的企业级应用至少包括三类组件:

  • 表现层:该组件处理HTTP请求并且实现REST API或者基于HTML的Web UI。在包含复杂的用户界面的应用中,表现层通常拥有大量代码;
  • 业务逻辑层:该组件是应用的核心,来实现商业规则;
  • 数据访问层:该组件来访问基础组件,比如数据库和消息代理;

通常情况下,表现层与业务层、数据访问层之间有清晰的分离。业务层拥有粗粒度的API,这些API包含一个或者多个门面,这些门面封装了业务逻辑组件。API是自然的缝隙,将单体应用划分成两个更小的应用。一个应用包含表现层。另外一个应用包含业务逻辑层和数据访问层。在划分之后,表现逻辑层远程调用业务逻辑层。

图7-2显示了重构之前和之后的架构。

这里写图片描述

图 7-2 重构已存在的APP

将单体应用拆分成这种方式有两个好处。

  • 使得可以独立于另一个应用进行开发、部署和扩展。特别是,它允许表现层开发者能够快速迭代用户界面并且易于进行A|B测试;
  • 另外一个优点是这种方法暴露出远程API,可以被你开发的微服务调用;

然而这个策略只是部分解决了问题。因为拆分后的一个或者两个应用很有可能仍是难以管理的单体应用。所以需要使用第三种策略来消除拆分之后的单体应用。

四、策略三:提取服务

第三个重构的策略是将单体应用中已经存在的模块转换成独立的微服务,每当提取一个模块并将它转换成服务时,单体应用就会萎缩。一旦转换了足够的模块,单体应用就不是一个问题了。它会完全消失或者变得足够小以致成为另外一个服务。

4.1 哪些模块需要先转换成服务

一个巨大、复杂的单体应用包含数十个或者上百个模块,所有这些都是提取的候选者。弄清楚先转换哪些模块很有挑战性。一个有效的方法是先抽取容易的模块。一般情况下,这会使你体验微服务,特别是提取的过程。之后,应该提取那些能带来最大好处的模块。

将模块转换为服务通常很消耗时间。可以给这些模块按照能获得的最大利益进行排名。通常情况下,提取那些频繁变化的模块是有利的。一旦你将模块转换成服务,可以独立于单体应用来开发、部署它,这将会加速开发。

抽取对资源的要求与单体应用中的其他极其不同的模块也是有利的,例如转换一个需要内存数据库的模块为微服务,它可以被部署在拥有大量内存的主机上,无论是裸机服务器还是VM,或者是云实例。类似的,提取那些实现了昂贵计算算法的模块也是值得的,因为这些服务可以被部署在拥有大量CPU的主机上。通过将需求特定资源的模块转换为服务,可以使得应用扩展起来更加容易和廉价。

当弄清楚了哪些模块需要提取时,查找已存在的粗粒度的边界(也叫做缝隙)是很有帮助的。这些边界使得模块到转换的服务更加容易和廉价。这样的一个例子是,一个模块仅通过异步消息与应用的其他部分通信,那么将该模块转化成微服务会是相对廉价和容易的。

4.2 如何提取模块

提取模块的第一步是在模块和单体应用间定义粗粒度的接口。它很有可能是双向的API,因为单体应用需要服务拥有的数据,反之亦然。由于模块和应用其他部分之间的网状依赖和细粒度的交互导致实现这样的API通常很有挑战性。因为领域模型类之间的诸多关联导致使用领域模型模式实现的业务逻辑重构起来尤其具有挑战性。这经常需要做一些重大的代码改变来打破这种依赖。图 7-3 显示了这种重构:

这里写图片描述

图 7-3 模块从单体应用变成微服务

一旦实现了粗粒度的接口,接着需要把模块转换成独立的服务。为了完成这件事,必须编写代码来使得单体应用和服务通过使用进程间通信机制(IPC)的API来进行交互。图 7-3 显示了重构之前、过程中和之后的架构。

在这个例子中,模块Z是要提取的候选模块,它的组件被模块X使用,它自己使用模块Y。重构的第一步是定义一组粗粒度的接口。第一个接口是被模块X使用来调用模块Z的入站接口;第二个是模块Z使用的来调用模块Y的出站接口。

第二步是将模块转换成独立的服务。入站和出站接口的代码使用IPC机制。此时更需要通过将模块Z和微服务基础框架(Microservice Chassis framework)结合来构建服务,以便处理横切面关注点(cross-cutting concerns),比如服务发现等。

一旦提取完模块,则会有另一个服务可以独立于单体应用和其他的服务来开发、部署和扩展。甚至可以从头重写该服务;在这种情况下,集成了服务和单体应用的API代码会成为反腐败层,在两个领域模型之间转换。每当提取一个服务,就会向微服务的方向更近一步。随着时间的发展,单体应用会逐渐萎缩,微服务的数量将会越来越多。

五、总结

迁移已有应用到微服务的过程是应用现代化的一种方式,但是不应该通过从头重写应用来完成这种转移,而是应该递增地将应用重构成一系列微服务。有三种策略可供选择:

  • 将新功能实现为微服务;
  • 将表现层组件与业务逻辑层和数据访问层分离;
  • 将已有单体应用的模块转换为服务;

微服务的数量会随着时间不断增长,同时开发团队的敏捷性和速度也在不断提升。


相关文章:

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐