从300万行到50万行代码,遗留系统的微服务改造

发布时间:2020-08-14 20:30:14 作者:技术琐话
来源:ITPUB博客 阅读:169

在传统企业甚至互联网企业中往往存在大量的遗留系统,这些遗留系统大多都能够正常工作,有的可能还运行着关键业务或者持有核心数据。但是,大部分遗留系统通常经常存在技术陈旧、代码复杂、难以修改等特点。笔者曾经维护过一个Perl实现的网站,在2015年被解耦前,它已经工作了十几年,为公司占领市场立下了汗马功劳。奈何技术陈旧,维护困难,最后在微服务化过程中慢慢淡出。

可见,随着时间的推移,遗留系统的维护和管理的成本越来越大。在向微服务架构全面转型的过程中,这些遗留系统就像一只只“拦路虎”,阻挡微服务转型之路。如何在不影响业务的同时,以更安全、更高效、更低成本的方式将这些遗留系统进行微服务改造,使之顺利融入微服务架构,并充分利用到微服务架构的优势呢?本章将详细介绍如何解决遗留系统的微服务改造问题。

一、遗留系统综述

1. 什么是遗留系统

“遗留系统是一种旧的方法、技术、计算机系统或者应用程序,它意味着系统过时了或者需要被取代。”

这是维基百科上对于遗留系统的定义,对于“遗留”的含义有明确阐释,即过时或者需要被取代的系统。正因为如此,遗留系统通常具有一些类似的特征。

这些问题随着日积月累,可能会给团队带来越来越多的负担,直到无法承受其管理和维护成本。所以遗留系统在微服务架构下如何进行改造,是大多数企业在面向微服务转型时都不得不面对的问题,而且急需尽早考虑、尽快解决,才能避免问题累积到一定程度后集中爆发。

2. 直接重写遗留系统可行么

遇到遗留系统的改造问题时,不少人可能会首先想到一个直截了当的方案:推翻重写,用全新的系统一次性替换掉遗留系统。采用这种方式,在落地过程中总会发现种种问题,导致新系统无法顺利切换,旧系统又无法完全替代。常见的问题有以下几种:

综上所述,寄希望于直接重写遗留系统、进行一次性替换而解决微服务改造问题是不现实的,必须探索一条能够持续可演进的道路,实现快速、低成本、影响面可控的改造效果。

二、遗留系统改造策略

对遗留系统的改造,既要不影响业务,实现平滑、安全的过渡,又要保证能够高效、稳步地推进,这对于软件开发者来说是个不小的挑战。

结合笔者的经验,在遗留系统改造过程中可以采取以下思路:

1. 演进式改造流

演进式改造流程是一种以逐步演进的方式对遗留系统进行改造的流程,其过程如图6-1所示。

从300万行到50万行代码,遗留系统的微服务改造

图6-1  对遗留系统的演进式改造流程


1.1 构建服务路标图

动手进行改造之前,首先需要构造出一个服务路标图,来粗粒度地展示在理想情况下所期望的各个服务及相互之间的依赖关系。以该图作为路标,指导我们着手进行服务化改造。当然,服务路标图可以不必苛求完整,可以随着开发过程的演进而不断完善。构建路标图的过程可由架构师、业务分析师及技术负责人共同参与,构建路标图的方法可以参考3.1.1小节的“服务拆分策略”。服务路标图构建好之后应在团队中共享并接受来自各个方面人员的反馈。

1.2 服务选择

有了服务路标图之后,也许会发现改造过程千头万绪,不知如何选择合适的部分优先进行改造。此时不妨遵循价值最大化的原则,从多种角度去制定优先拆分策略,比如:

1.3 服务改造

选好优先需要改造的部分后,着手将这部分业务功能迁移到微服务架构下实现。改造过程中,通常需要解决这样的问题:

根据改造需求的不同以及数据依赖关系,具体可以分为三种不同的改造场景,每种场景下的技术实现各有不同。

1.4 业务验证

业务功能迁移到微服务架构下实现后,需要验证新的服务是否满足业务需求。在新服务上线投入使用并稳定后,可以从遗留系统中移除原有的代码模块,如有需要时,一并移除数据同步任务。

1.5 迭代优化

至此已经对一部分遗留系统的业务完成了微服务改造,对于剩余的部分,可以按照类似的方法迭代进行,重新审视服务路标图,选出下一个需要改造的业务,继续进行优化,直到完成既定的微服务改造目标。

2. 绞杀者模式

在微服务架构还未流行起来之前,对于增量式的大规模软件改造,常用的是名为“抽象分支”的方法,如图6-2所示。

从300万行到50万行代码,遗留系统的微服务改造

图6-2  抽象分支

组件解耦:在需要被替换的组件与组件消费者之间创建一个抽象层,这一层只根据需要声明抽象的接口或协议,具体的实现仍存留于待替换组件中。这样就通过引入抽象层实现了待替换组件与组件消费者之间的解耦。

对遗留系统的微服务改造策略,也可以借鉴“抽象分支”的思路,只不过在微服务架构下,抽象层是由一个独立的门面(Facade)服务实现,该服务将请求转发到新系统或者旧系统,起到路由作用。

在改造过程中,新旧系统会同时存在,共同协作对外提供价值。随着改造过程的推进,新系统提供的功能和价值越来越多,逐步地取代原有遗留系统的功能,用户流量也会越来越多地导入到新系统上,最后原有遗留系统不再被使用时,就可以安全地下线了,此时门面服务也可以安全地移除。

这种平滑的过渡方法通常被称为“绞杀者模式”。遗留系统被逐步“绞杀”的过程,如图6-3所示。

从300万行到50万行代码,遗留系统的微服务改造

图6-3  绞杀者模式

绞杀者模式特别适合用于对复杂度较高的大型遗留系统进行逐步改造,但在迁移过程中也需要注意以下问题:

3. 挎斗模式

传统企业中存在大量的遗留系统,要对这些遗留系统全部进行微服务化改造的成本会很高,并不现实,而且有些遗留系统甚至是无法完全改造的。对于这些系统,我们的选择并不一定是将其进行微服务化改造,而是将其接入到微服务环境中,与其他服务共同协作来实现业务需求。

接入代码如果是和原系统运行在同一进程中,就意味着没有很好的隔离,可能会因为接入代码的一点小问题造成原系统无法工作。那么是否存在低成本的方法,将遗留系统接入到微服务环境中呢?一种方法是使用挎斗模式,如图6-4所示。“挎斗”一词来源于带挎斗的摩托车。

从300万行到50万行代码,遗留系统的微服务改造

图6-4  挎斗模式

如图6-4所示,具体到遗留系统接入场景下,挎斗模式就是将接入功能代码集中在一起,作为一个独立的进程或服务,为不同语言的遗留系统提供一个同构的接入接口。在部署结构上,挎斗服务与原遗留系统紧密相关,原遗留系统在哪里它就在哪里。对于原遗留系统应用程序的每个实例,旁边都部署和托管了一个挎斗实例。挎斗是支持与原应用一起部署的进程或服务。

使用挎斗模式的好处有以下几个:

在使用挎斗模式时,也需要注意以下问题:

ServiceMesh就是采用了挎斗模式的思路,在每个服务近端部署一个代理,帮助遗留系统接入ServiceMesh,享受服务治理带来的好处。

三、遗留系统改造场景

在进行具体的改造前,可能会遇到如下的挑战:

下面根据遗留系统改造过程中的常见场景,来一一解答这些问题。遗留系统的常见改造场景,如图6-5所示。

从300万行到50万行代码,遗留系统的微服务改造

图6-5遗留系统的常见改造场景

如图6-5所示,对于某个具体改造需求,可以分为以下两种不同的场景。

改造场景1:实现新业务时与现有业务数据独立

适用场景:新业务与现有业务数据独立,不存在依赖。

实现方案:新业务可以通过单独的服务和数据库来实现。在消费者请求和底层系统之间引入一个绞杀者门面服务,该服务负责将请求按照业务不同路由到遗留系统和新业务服务上即可,如图6-6所示。

从300万行到50万行代码,遗留系统的微服务改造

图6-6改造场景1:业务数据独立


由于和遗留系统的数据之间无耦合,新服务的技术栈、工具选型就比较灵活,充分利用到微服务技术的异构性。

改造场景2:实现新业务时与现有业务数据依赖

新业务与现有业务有数据依赖时,根据数据持有者不同,有两种处理方案。

方案A:遗留系统持有数据,新业务访问共享数据

适用场景:允许新业务访问遗留系统的共享数据,或者数据库分离成本较高。

实现方案:在允许新业务服务直接访问遗留系统的数据库的情况下,最简单的一种实现方案是新业务服务直接连接遗留系统数据库获得数据,如图6-7所示。

这种方案的实施成本相对较低,与原有数据源进行集成即可,但缺点也在于此,由于直接使用了数据库作为集成方式,新业务服务仍与原遗留系统数据存在直接耦合,原数据库的变动会直接影响到新业务服务的实现,而且对于新旧业务的数据访问权限与控制也需要纳入考虑范围内。针对这些问题,也可以考虑另外一种方案,即新业务服务通过遗留系统所提供的API来获得数据,如图6-8所示。

从300万行到50万行代码,遗留系统的微服务改造

图6-7改造场景2:直接访问遗留系统数据


从300万行到50万行代码,遗留系统的微服务改造

图6-8改造场景2:通过API访问遗留系统数据


这种方案的优点是可以通过API隔离数据变化,避免新业务服务与原遗留系统数据之间的直接耦合。可以在API中实现对数据的处理逻辑,满足新业务服务的数据需求。API只对外提供新业务服务允许访问的数据域,从而实现数据权限的控制,更适用于数据库直接访问受限制的场景。但其引入的额外成本是需要在原遗留系统上增加API实现的工作,有时因为遗留系统的技术陈旧、结构复杂等原因,会使得这部分工作比较难于执行。

方案B:新业务服务持有数据,通过数据同步解决数据依赖问题

不难看出,方案A中的方法多多少少都对原系统有一定的侵入性,或与原数据库直接耦合,或需要对原遗留系统进行修改。面向对象设计原则中的“开闭原则”,即“对扩展开放,对修改封闭”,可以帮助我们实现灵活可扩展的软件架构,在这里也同样适用。因此可以考虑在原有系统基础上进行扩展,而不是直接修改原遗留系统,于是诞生了另一个方案:新业务服务持有数据,通过数据同步解决数据依赖问题。

适用场景:不允许新业务访问遗留系统的共享数据,或者数据库分离成本不高。

实现方案:具体实现方法是建立独立的数据库供新业务服务所使用,而新数据库与遗留系统数据库之间通过一个专门的数据同步服务进行同步,将新数据库中的数据转化为与遗留系统数据可兼容的形式,并迁移过去。数据同步服务又称ETL(Extract、Transform、Load)服务,其主要职责是读取、转换和同步数据,如图6-9所示。

从300万行到50万行代码,遗留系统的微服务改造

图6-9改造场景2:通过ETL进行数据同步

该方案的优点在于使用了独立的数据库,对原遗留系统的侵入性较低,新业务服务持有新的数据库,与原遗留系统解除了直接耦合,新业务服务和新数据库的选型都可以比较灵活。所有新旧数据之间的转换逻辑都在ETL服务中实现,以后任何数据转换与同步的逻辑变化都只与ETL服务有关,无须再去修改原遗留系统和新业务服务。但该方案带来的成本是需要额外实现一个ETL服务。另外由于需要在独立的两个数据库之间进行同步,可能只能满足数据的最终一致性而无法做到强一致性。另外也要考虑ETL服务的可用性,避免引入新的单点故障。

如果进一步考虑到数据隔离问题,避免直接暴露新服务的数据库数据,还可以让ETL服务通过新业务服务的API来访问数据,如图6-10所示。这种方案进一步解除了ETL服务与新业务数据库之间的耦合,新增的API还可以进一步被复用,作为其他服务消费者访问数据库的接口。

从300万行到50万行代码,遗留系统的微服务改造

图6-10改造场景2:通过ELT访问新业务服务的API进行数据同步

改造场景3:对现有业务微服务化

大多数遗留系统在单体应用架构下已经承载了许多关键业务或持有核心数据,对其进行微服务化改造时,一次性的重构风险是比较大的。因此,更为提倡通过数次小的重构来逐步实现微服务化改造。

适用场景:对遗留系统承载的现有业务微服务化,实现渐进式的重构。

实现方案:以一个含有多模块的单体应用遗留系统改造为例,通过以下三个步骤拆分为业务数据分离的两个服务。

1.将内部代码调用修改为本地REST接口调用:将被调函数修改为REST接口暴露出来,调用者模块通过对本地REST接口调用完成与原有业务等价的功能。此时还未拆分服务,仍然是作为一个服务整体上线。

2.将本地REST接口调用改为服务间REST接口调用:拆分服务,将原有的调用者模块拆分为独立服务,REST接口调用地址改为目标接口所在的服务地址。这一步接口变动的成本相当小,重点在于让拆分后的服务能够独立部署与上线。同时,为避免一次引入过多变更,此阶段拆分后的服务仍然直接访问原数据库的共享数据。

3.业务数据分离:将拆分后的服务所需的数据分离出来,作为新服务的独享数据库所持有。两个数据库之间的数据依赖问题,可以借前文所述的数据同步方案(ETL服务)解决。

按照上述过程,根据需要不断迭代,将原遗留系统的业务逐步微服务化,总体过程的示意图,如图6-11所示。

从300万行到50万行代码,遗留系统的微服务改造

图6-11改造场景3:对现有业务微服务化

如何对遗留系统的数据库进行拆分

在上述几个改造场景中,有些步骤涉及对遗留系统的数据库进行拆分。那么在多服务共享数据库的情况下,如何决定首先拆分哪个服务的数据库?哪种拆分顺序的工作量最小呢?一种方法是采用数据“读依赖最小”的顺序进行拆分,这种方法的实施可以概括为以下三个步骤:

1.表拆分,解除服务之间的数据关系耦合:列出各个服务对表的读写关系,如果只有一个服务对某个表进行写操作,不用拆分该表;当有多个服务对某个表进行写操作时,首先考虑将这张表拆分成多张有连接关系的表,将其转化为被某个服务单独进行写操作的表。

2.绘制服务依赖关系图:在每张表被独立服务单独进行写操作的情况下,按照如下规则构造有向图,即将所有服务作为有向图中的点,当服务A需要从服务B的数据库读取数据时,画一条由B指向A的有向边,表示服务A依赖于服务B;依此类推,直到绘制完整个依赖关系图。

3.库拆分,解除服务之间的数据库耦合:以有向图中各节点的出度(即从节点出发的边的条数)作为该服务被依赖数,进行排序,挑选被依赖数最小的服务,首先对其进行数据库解耦,把该服务下的数据库表独立出来,并在该服务里提供数据接口,以供依赖于它的服务调用。

重复第3步,直到所有数据库被拆分为由各个服务独享的数据库。

例如,如图6-12所示,是一组包含四个服务的依赖关系图,服务右上角的角标表示该服务的被依赖数。得知短信服务的被依赖数为0,为当前被依赖最少的服务,可以首先将该服务的数据库拆分出来。其次再按顺序依次拆分积分服务、订单服务和账户服务的数据库,直至所有数据库被拆分为由服务所独享的数据库。

从300万行到50万行代码,遗留系统的微服务改造

图6-12服务依赖关系图示例

四、遗留系统改造案例

本节以笔者曾亲身参与改造的遗留系统为案例,介绍如何将一个大型遗留系统向微服务架构进行迁移。

1. 改造前的系统情况

该系统是一个用于提供全球房产信息搜索服务的大型门户平台,核心功能主要包括如下几点,如图6-13所示。


从300万行到50万行代码,遗留系统的微服务改造

图6-13系统现状

该系统是从多年前收购的一个通用搜索平台改造而来的,整体为一个规模庞大的单体应用,使用同一个代码库,技术栈主要以Java为主,数据库为Postgre,搜索引擎使用FASTSearch(历史原因),代码量大约在300万行左右。

随着业务演进的速度越来越快,单体应用的弊端导致系统变更成本越来越高。即使修改几行代码也需要经过冗长的测试以及发布流程,系统发布才能生效。面对这些问题,团队决心使用微服务架构,将未来的新功能全部以微服务实现,并将系统原有功能逐步迁移到微服务架构下。

2. 改造过程

步骤1:通过遗留系统API提供数据

当时,研发团队接到的第一个特性需求如下所示:

此时,面临的挑战主要包括以下两个:

采取的方案主要包括如下几部分:

利用微服务架构的异构性,新服务命名为AppService,使用RubyOnRails实现,并实现相关的数据转换逻辑,为NativeApp提供数据API。通过这种方式实现的新服务,不仅能满足NativeApp的需求,而且可以快速开发,独立部署。

改造后的系统架构图,如图6-14所示。其中AppService服务提供两组API,分别是提供房源信息的EstateAPI和提供地理位置信息的LocAPI。

从300万行到50万行代码,遗留系统的微服务改造

图6-14

步骤1:通过遗留系统API提供数据

通过如上所述的方式,极大地减少了对原门户平台的修改,同时通过在新服务上实现相关逻辑,不仅提升了交付效率,而且该服务能够独立部署上线。

步骤2:通过遗留系统数据源提供数据

接下来,研发团队接到的特性需求如下:

但是这两部分的数据在原门户平台的基础搜索API中并未包含,在面临人手有限和交付时间紧的挑战下,如果要在门户平台中新增接口,变更成本会比较高,可能无法按时交付。

面对这些挑战,团队决定尽量避免对原有遗留系统的直接修改,改为在AppService微服务中实现新业务逻辑,做了如下修改:

改造后的系统架构图,如图6-15所示。通过这步改造,将基础搜索数据的查询功能重构到了AppService微服务内,减少了对原门户平台相关功能的修改。

步骤3:拆分基础搜索服务,替换数据源

产品的需求持续演进,接下来,团队需要为桌面端用户新增支持多边形的地理位置搜索功能。但在现有架构下,FASTSearch搜索引擎并不支持这个功能,这就意味着需要替换原有的搜索引擎,需要对原门户平台的代码做大量的修改。

如何有效地实现这个特性呢?具体操作方法如下:

1.将基础搜索API拆分出来作为独立服务EngineService,这样以后关于搜索相关的逻辑,都可以放在EngineService中实现了。

从300万行到50万行代码,遗留系统的微服务改造

图6-15步骤2:通过遗留系统数据源提供数据

2. 在EngineService中实现基于多边形的搜索。

3. 对原门户平台的搜索机制重构,让其使用EngineService获取相关数据(之前是使用FASTSDK)。基于这种方式,门户平台和AppService与底层搜索引擎隔离开来,可以方便地替换底层数据源。

该方案的实现,如图6-16所示。

从300万行到50万行代码,遗留系统的微服务改造

图6-16步骤3:拆分基础搜索服务,替换数据源

步骤4:构建移动端专属的后端服务

团队在预期时间内很快地完成了前面的各项需求,随着业务发展迅猛,接下来需要在手机端和平板端增强用户体验。然而,目前手机端、平板端的实现逻辑较落伍,均是基于JSP的模板方式实现。如果要直接修改,成本高。

考虑到在屏幕尺寸、性能和显示限制等方面,移动设备与桌面浏览器的能力存在显著差异。因此,移动应用对后端的需求与桌面UI是不一致的。

如果仍然使用原有的门户平台逻辑,则需要同时为这两种客户端提供数据,这极大地增加了维护的成本。于是,团队借鉴了BFF模式,将手机端和平板端网站的特定后台需求,拆分为一个独立的后台服务SPA-Web,作为后端实现。为前端提供数据。然后基于最新的前端技术,采用轻量级、更有效的单页面方式实现前端,如图6-17所示。

从300万行到50万行代码,遗留系统的微服务改造

图6-17

骤4:构建移动端专属的后端服务

通过为移动端创建一个专属的后端服务,可以为移动端的用户进行专门的优化,量身打造最佳体验。同时,由于微服务架构下的这个新服务足够的小而简单,就更容易进行修改、部署和上线了,也更容易在性能和行为方面进行精心的优化。

步骤5:基于数据继续划分微服务

随着团队的微服务化实践越来越成熟,整个系统的架构趋向于朝更细的粒度演进。

系统典型的业务场景分为待售房源(Buy)、已售房源(Sold)、待租房源(Rent)等。所以,团队基于这些业务继续划分单独的搜索API以及独立的数据存储机制,每种数据由独立的ElasticSearch 集群存储,并利用前后端分离的机制实现前后端的解耦。

解耦后的系统,如图6-18所示。

从300万行到50万行代码,遗留系统的微服务改造

图6-18步骤5:按照数据划分微服务

最终实现的服务,如下表所示。

服务名称

主要描述

UserService

处理用户信息的认证和鉴权

SavedSearchService

处理用户保存的订阅条件

POIService

处理POIService相关的信息

LocService

提供地理位置相关接口

BuyService

提供待售房源相关的接口

SoldService

提供已售房源相关的接口

RentService

处理待租房源相关的逻辑

对于系统中使用的服务支撑组件,如服务注册发现机制、集中化配置信息等机制,其实现和在之前的章节中阐述的差不多,这里就不展开赘述了。

3. 改造结果

可以看到,经过上面一系列步骤后,原有的门户平台已逐渐迁移为微服务的系统,原有的大约300万行的代码也只剩下了大约50万行,继续提供着业务价值。团队对遗留系统的修改和变更成本已经大大减少,总体交付周期也大大缩短,技术的升级带来性能、可维护性的提升,充分享受到了微服务架构小而美的好处。

小结

本章介绍了遗留系统的特点、改造策略和场景,并结合一个实战案例进行了讲解。目的是帮助读者从以下方面掌握对遗留系统的微服务改造方法:

另外,对于遗留系统的改造需求,本章将其细分为三种场景,如新业务数据独立、新业务数据依赖以及现有业务服务化。通过对这些场景的分析,能有效地指导读者进行微服务的演进。

本文节选自《微服务架构与实践(第2版)》一书,王磊等著,电子工业出版社出版。

推荐阅读:
  1. 微服务改造设计参考
  2. 利用python 实现快速插入300万行数据

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

50万 300万 代码

上一篇:【技术】linux命令行自动化重签名ipa写法

下一篇:评标委员会在评标期间是否有义务核实投标信息真伪?

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》