Chris Richardson 微服务系列翻译全7篇链接:
- 微服务介绍
- 构建微服务之使用API网关
- 构建微服务之微服务架构的进程通讯
- 微服务架构中的服务发现
- 微服务之事件驱动的数据管理
- 微服务部署
- 重构单体应用为微服务(本文)
原文链接:Refactoring a Monolith into Microservices
微服务重构的概述
将单体应用转化为微服务是应用现代化的一种形式,数十年来开发者们一直致力于此。因此,将应用重构为微服务时,我们可以借鉴其中的一些经验。
首先不要大规模地重写代码,不要集中所有力量从头构建一个新的微服务应用,这中方式听起来很吸引人,但是会有极大的风险,有可能以失败告终。正如 Martin Fowler 所言:
the only thing a Big Bang rewrite guarantees is a Big Bang!
相反,我们应该渐进式的重构单体应用。逐步构建由微服务组成的新应用,与单体应用一起运行;随着时间的推移,单体应用实现的功能会不断收缩,直至完全消失或者转变为另一个微服务。这一方法虽然充满挑战,但风险远小于大规模重写代码。
Martin Fowler 将这一策略称为“杀手应用”。这一名称源自热带雨林中的杀手藤。杀手藤附生于大树的周围,企图得到树冠处的阳光,最后树木死后,留下树状的藤蔓。应用现代化也遵循这一方式。我们将围绕着遗留应用构建由一些列微服务组成的新应用,直至遗留应用消失。
接下来了解不同的实现策略。
策略一:停止挖坑
如果发现自己掉入坑里,应该马上停止挖坑。一旦单体应用变的难以管理,你应该停止让单体应用继续变的庞大,实现新功能时,不应该往单体应用中添加新的代码。相反的,而是把新代码放到独立的微服务中。下图展示了此方法的系统架构:
除了新服务和遗留的单体应用,这个系统还包括其他两个组件:第一个是请求路由,用来处理 HTTP 请求,与之前文章中所说的 API 网关类似。路由将与新功能相对应的请求发送到新服务上去,将遗留请求发送到已有的单体应用上去。
另一组件是胶水代码,用来集成微服务与单体应用。微服务很少独立存在,通常需要访问单体应用拥有的数据。胶水代码存在于单体应用或微服务中,或者两者兼有,用来负责数据的集成。微服务使用胶水代码来对单体应用的数据进行读写。
微服务可以通过以下三种方式来访问单体应用的数据:
- 调用单体应用提供的 API
- 直接访问单体应用的数据库
- 维护一份数据副本,与单体应用的数据库保持同步
胶水代码也被称为 anti-corruption layer。因为胶水代码能防止拥有全新领域模型的服务被遗留单体应用的领域模型所污染。胶水代码在两种不同的模型间进行转换。anti-corruption layer 这一术语来自 Eric Evans 撰写的 Domain Driven Design 中。要想远离单体应用的泥淖,开发 anti-corruption layer 是必不可少的。
以轻量级微服务的方式实现新功能有许多优点:它能够防止单体应用变的不可管理。微服务能够独立开发、部署和扩展。你可以通过创建新的服务来体会到微服务架构的好处。
然而,这一方法并没有解决单体应用的问题。要想解决这些问题,需要拆分单体应用。让我们看一下拆分的策略。
策略二:前后端分离
缩小单体应用的策略之一是将展示层从业务逻辑层和数据访问层中拆分出来。典型的企业应用包括一下三种组件:
- 展示层:处理 HTTP 请求并实现基于 REST API 或 HTML 的 Web UI。在一个-
用户界面复杂的应用中,展示层通常包含了大量的代码 - 业务逻辑层:应用的核心,实现了业务逻辑
- 数据访问层:访问数据库和消息代理等基础架构组件
通常展示层对于业务逻辑层与数据访问层来讲,彼此有着清晰的划分。业务层由若干个 API 组成,内部封装了业务逻辑。这些 API 是将单体应用拆分为两个更小应用的分界线。一个应用包含表示层,另一个应用包含业务逻辑层和数据访问层。拆分后,展示逻辑的应用向业务逻辑的应用发起远程调用。下图展示了重构前后的构架:
以这种方式拆分单体应用有两大好处:1)它使得两个应用可以独立的开发、部署和扩展。尤其是,它使得展示层的开发者能够快速迭代用户界面,轻松的进行 AB 测试。2)暴露了可被其他服务调用的 API
这种策略也只是部分解决方案,很有可能两个应用会变成难以管理的单体应用。这时需要使用第三种策略来消除剩余的单体应用。
策略三:提取微服务
重构的第三个策略是将单体中现有的模块变成独立的微服务,每次提取模块为微服务时,单体就会缩小,一旦转化了足够多的模块,单体应用将不再是问题,要么消息,要么变成另一个微服务。
为需要转化为微服务的模块设置优先级
大型、复杂的单体应用由数十甚至数百个模块组成,所有模块都是可提取的。弄清楚哪个模块需要首先被提取往往是挑战性的问题。一个好的方式是先选择易于提取的模块,这将给开发者熟悉微服务以及积累提取经验。之后可以提取哪些可以带来最大收益的模块。
将模块转变为微服务通常需要一定时间,一般会根据获得收益的大小来给模块排序。通常转换经常变化的模块带来的收益也最大。一旦把一个模块转换为微服务,就可以独立开发、部署它了,从而加快了开发速度。
提取那些对资源有独特需求的模块也会带来很多好处。例如,把需要内存数据库的模块转化为微服务,就能部署在大内存的主机上。同样的,将实现计算密集型算法的模块提取出来也是值得的,该微服务可以部署在拥有多个 CPU 的主机上。这种方式使得应用更容易扩展。
当决定哪些模块需要提取时,找出现有粗粒度的边界(即分界线)也大有裨益。这会使模块转化为微服务更加简单、省力。例如:一个只通过异步消息与其他部分通信的模块,转化为微服务是更简单的。
如何提取模块
提取模块的第一步是确定模块和单体应用的接口粒度。单体应用和微服务需要访问彼此的数据,更像是双向 API,模块和应用其它部分之间存在着互相依赖,因此实现这些 API 一般充满挑战。重构时使用领域模型来实现业务逻辑变的尤为困难,一般需要大的代码改动才能打破这些依赖。
一旦实现粗粒度的接口,就可以将模块转化为独立的微服务。要做到这一点,必须能够让单体应用和微服务通过 API 通信。 下图展示了重构前、重构中和重构后的不同架构:
模块 Z 是要被提取的模块,它使用到模块 Y ,同时它的组件被模块 X 使用。重构的第一步就是定义一组粗粒度 API,第一个接口是模块 X 调用模块 Z 的 接口。第二个接口是模块 Z 调用模块 Y的接口。
重构的第二步是把模块转变为独立的微服务。对内和对外接口通过 IPC 机制实现,开发人员可能只需要将模块 Z 与微服务支撑框架(Microservice Chassis framework)组合起来构建微服务。
一旦将模块提取完毕,你就拥有了一个新的微服务,它能够独立于单体应用和其它微服务进行开发、部署和扩展。如果想重写微服务的代码,集成微服务和单体应用的 API 会成为这两个领域模型之间的 anti-corruption layer。每提取一个模块,就向着微服务的方向又迈进了一步。随着时间推移,单体应用将会逐渐消失,你也会拥有更多的微服务。
总结
将单体应用迁移到微服务的过程是应用现代化的一种形式。并不需要从头重写代码,而是渐进式地将应用重构为一组微服务。其中有三种策略:使用微服务实现新功能;将展示层从业务逻辑层、数据访问层中拆分;将单体应用内的模块转化为微服务。随着时间的推移,微服务的数量将会增加,从而提升团队的敏捷和效率。