遗留系统改造场景
在进行具体的改造前,可能会遇到如下的挑战:
新旧系统可能需要不同的数据源,或具有不同的数据库结构,怎样解决数据之间的同步和依赖问题呢?
单体应用下的旧系统需要拆分为多个服务时,怎样实现安全的渐进式拆分?
下面根据遗留系统改造过程中的常见场景,来一一解答这些问题。遗留系统的常见改造场景,如图6-5所示。
图6-5 遗留系统的常见改造场景
如图6-5所示,对于某个具体改造需求,可以分为以下两种不同的场景。
实现新业务:需要在系统中增加新业务,与现有业务相对独立。根据新业务与现有业务之间是否存在数据依赖关系,又可分为两种子场景,即与现有业务数据独立或者与现有业务数据依赖。
对现有业务微服务化:需要将系统的现有业务改造为微服务架构,比如对现有业务的拆分和服务化工作。
//
6.3.1 改造场景1:实现新业务时与现有业务数据独立
//
适用场景:新业务与现有业务数据独立,不存在依赖。
实现方案:新业务可以通过单独的服务和数据库来实现。在消费者请求和底层系统之间引入一个绞杀者门面服务,该服务负责将请求按照业务不同路由到遗留系统和新业务服务上即可,如图6-6所示。
图6-6 改造场景1:业务数据独立
由于和遗留系统的数据之间无耦合,新服务的技术栈、工具选型就比较灵活,充分利用到微服务技术的异构性。
//
6.3.2 改造场景2:实现新业务时与现有业务数据依赖
//
新业务与现有业务有数据依赖时,根据数据持有者不同,有两种处理方案。
方案A:遗留系统持有数据,新业务访问共享数据。
方案B:新业务服务持有数据,通过数据同步解决数据依赖问题。
//
6.3.2.1 方案A:遗留系统持有数据,新业务访问共享数据
//
适用场景:允许新业务访问遗留系统的共享数据,或者数据库分离成本较高。
实现方案:在允许新业务服务直接访问遗留系统的数据库的情况下,最简单的一种实现方案是新业务服务直接连接遗留系统数据库获得数据,如图6-7所示。
图6-7 改造场景2:直接访问遗留系统数据
这种方案的实施成本相对较低,与原有数据源进行集成即可,但缺点也在于此,由于直接使用了数据库作为集成方式,新业务服务仍与原遗留系统数据存在直接耦合,原数据库的变动会直接影响到新业务服务的实现,而且对于新旧业务的数据访问权限与控制也需要纳入考虑范围内。针对这些问题,也可以考虑另外一种方案,即新业务服务通过遗留系统所提供的API来获得数据,如图6-8所示。
图6-8 改造场景2:通过API访问遗留系统数据
这种方案的优点是可以通过API隔离数据变化,避免新业务服务与原遗留系统数据之间的直接耦合。可以在API中实现对数据的处理逻辑,满足新业务服务的数据需求。API只对外提供新业务服务允许访问的数据域,从而实现数据权限的控制,更适用于数据库直接访问受限制的场景。但其引入的额外成本是需要在原遗留系统上增加API实现的工作,有时因为遗留系统的技术陈旧、结构复杂等原因,会使得这部分工作比较难于执行。
//
6.3.2.2 方案B:新业务服务持有数据,通过数据同步解决数据依赖问题
//
不难看出,方案A中的方法多多少少都对原系统有一定的侵入性,或与原数据库直接耦合,或需要对原遗留系统进行修改。面向对象设计原则中的“开闭原则”,即“对扩展开放,对修改封闭”,可以帮助我们实现灵活可扩展的软件架构,在这里也同样适用。因此可以考虑在原有系统基础上进行扩展,而不是直接修改原遗留系统,于是诞生了另一个方案:新业务服务持有数据,通过数据同步解决数据依赖问题。
适用场景:不允许新业务访问遗留系统的共享数据,或者数据库分离成本不高。
实现方案:具体实现方法是建立独立的数据库供新业务服务所使用,而新数据库与遗留系统数据库之间通过一个专门的数据同步服务进行同步,将新数据库中的数据转化为与遗留系统数据可兼容的形式,并迁移过去。数据同步服务又称ETL(Extract、Transform、Load)服务,其主要职责是读取、转换和同步数据,如图6-9所示。
图6-9 改造场景2:通过ETL进行数据同步
该方案的优点在于使用了独立的数据库,对原遗留系统的侵入性较低,新业务服务持有新的数据库,与原遗留系统解除了直接耦合,新业务服务和新数据库的选型都可以比较灵活。所有新旧数据之间的转换逻辑都在ETL服务中实现,以后任何数据转换与同步的逻辑变化都只与ETL服务有关,无须再去修改原遗留系统和新业务服务。但该方案带来的成本是需要额外实现一个ETL服务。另外由于需要在独立的两个数据库之间进行同步,可能只能满足数据的最终一致性而无法做到强一致性。另外也要考虑ETL服务的可用性,避免引入新的单点故障。
如果进一步考虑到数据隔离问题,避免直接暴露新服务的数据库数据,还可以让ETL服务通过新业务服务的API来访问数据,如图6-10所示。这种方案进一步解除了ETL服务与新业务数据库之间的耦合,新增的API还可以进一步被复用,作为其他服务消费者访问数据库的接口。
图6-10 改造场景2:通过ELT访问新业务服务的API进行数据同步
//
6.3.3 改造场景3:对现有业务微服务化
//
大多数遗留系统在单体应用架构下已经承载了许多关键业务或持有核心数据,对其进行微服务化改造时,一次性的重构风险是比较大的。因此,更为提倡通过数次小的重构来逐步实现微服务化改造。
适用场景:对遗留系统承载的现有业务微服务化,实现渐进式的重构。
实现方案:以一个含有多模块的单体应用遗留系统改造为例,通过以下三个步骤拆分为业务数据分离的两个服务。
1.将内部代码调用修改为本地REST接口调用:将被调函数修改为REST接口暴露出来,调用者模块通过对本地REST接口调用完成与原有业务等价的功能。此时还未拆分服务,仍然是作为一个服务整体上线。
2.将本地REST接口调用改为服务间REST接口调用:拆分服务,将原有的调用者模块拆分为独立服务,REST接口调用地址改为目标接口所在的服务地址。这一步接口变动的成本相当小,重点在于让拆分后的服务能够独立部署与上线。同时,为避免一次引入过多变更,此阶段拆分后的服务仍然直接访问原数据库的共享数据。
3.业务数据分离:将拆分后的服务所需的数据分离出来,作为新服务的独享数据库所持有。两个数据库之间的数据依赖问题,可以借前文所述的数据同步方案(ETL服务)解决。
按照上述过程,根据需要不断迭代,将原遗留系统的业务逐步微服务化,总体过程的示意图,如图6-11所示。
图6-11 改造场景3:对现有业务微服务化
如何对遗留系统的数据库进行拆分
在上述几个改造场景中,有些步骤涉及对遗留系统的数据库进行拆分。那么在多服务共享数据库的情况下,如何决定首先拆分哪个服务的数据库?哪种拆分顺序的工作量最小呢?一种方法是采用数据“读依赖最小”的顺序进行拆分,这种方法的实施可以概括为以下三个步骤:
1.表拆分,解除服务之间的数据关系耦合:列出各个服务对表的读写关系,如果只有一个服务对某个表进行写操作,不用拆分该表;当有多个服务对某个表进行写操作时,首先考虑将这张表拆分成多张有连接关系的表,将其转化为被某个服务单独进行写操作的表。
2.绘制服务依赖关系图:在每张表被独立服务单独进行写操作的情况下,按照如下规则构造有向图,即将所有服务作为有向图中的点,当服务A需要从服务B的数据库读取数据时,画一条由B指向A的有向边,表示服务A依赖于服务B;依此类推,直到绘制完整个依赖关系图。
3.库拆分,解除服务之间的数据库耦合:以有向图中各节点的出度(即从节点出发的边的条数)作为该服务被依赖数,进行排序,挑选被依赖数最小的服务,首先对其进行数据库解耦,把该服务下的数据库表独立出来,并在该服务里提供数据接口,以供依赖于它的服务调用。
重复第3步,直到所有数据库被拆分为由各个服务独享的数据库。
例如,如图6-12所示,是一组包含四个服务的依赖关系图,服务右上角的角标表示该服务的被依赖数。得知短信服务的被依赖数为0,为当前被依赖最少的服务,可以首先将该服务的数据库拆分出来。其次再按顺序依次拆分积分服务、订单服务和账户服务的数据库,直至所有数据库被拆分为由服务所独享的数据库。
图6-12 服务依赖关系图示例
遗留系统改造案例
本节以笔者曾亲身参与改造的遗留系统为案例,介绍如何将一个大型遗留系统向微服务架构进行迁移。
//
6.4.1 改造前的系统情况
//
该系统是一个用于提供全球房产信息搜索服务的大型门户平台,核心功能主要包括如下几点,如图6-13所示。
为用户提供基于地理位置、关键字的房产信息搜索功能。
提供基于定制搜索条件的房源信息订阅。
提供桌面端、平板端、手机端等多种不同方式的访问。
图6-13 系统现状
该系统是从多年前收购的一个通用搜索平台改造而来的,整体为一个规模庞大的单体应用,使用同一个代码库,技术栈主要以Java为主,数据库为Postgre,搜索引擎使用FAST Search(历史原因),代码量大约在300万行左右。
随着业务演进的速度越来越快,单体应用的弊端导致系统变更成本越来越高。即使修改几行代码也需要经过冗长的测试以及发布流程,系统发布才能生效。面对这些问题,团队决心使用微服务架构,将未来的新功能全部以微服务实现,并将系统原有功能逐步迁移到微服务架构下。
//
6.4.2 改造过程
//
6.4.2.1 步骤1:通过遗留系统API提供数据
当时,研发团队接到的第一个特性需求如下所示:
支持Native App提供核心功能
三个月上线首个版本
此时,面临的挑战主要包括以下两个:
该系统的主要研发成员在海外,国内团队刚成立,新人多,知识迁移成本高。
系统本身是基于商业通用搜索平台上进行的二次开发,逻辑复杂,代码耦合度高,变更成本高。
采取的方案主要包括如下几部分:
在遗留系统上封装现有逻辑,提供基础API。
重新实现一个服务,类似之前章节提到的BFF模式,为Native App提供数据。
该服务通过访问遗留系统提供的基础API获得所需要的数据。
该服务内实现必要的转换,提供给Native App界面进行呈现。
利用微服务架构的异构性,新服务命名为AppService,使用Ruby On Rails实现,并实现相关的数据转换逻辑,为Native App提供数据API。通过这种方式实现的新服务,不仅能满足Native App的需求,而且可以快速开发,独立部署。
改造后的系统架构图,如图6-14所示。其中AppService服务提供两组API,分别是提供房源信息的Estate API和提供地理位置信息的Loc API。
图6-14 步骤1:通过遗留系统API提供数据
通过如上所述的方式,极大地减少了对原门户平台的修改,同时通过在新服务上实现相关逻辑,不仅提升了交付效率,而且该服务能够独立部署上线。
6.4.2.2 步骤2:通过遗留系统数据源提供数据
接下来,研发团队接到的特性需求如下:
支持房产发布者的信息展示。
支持地理兴趣点(POI)信息显示。
但是这两部分的数据在原门户平台的基础搜索API中并未包含,在面临人手有限和交付时间紧的挑战下,如果要在门户平台中新增接口,变更成本会比较高,可能无法按时交付。
面对这些挑战,团队决定尽量避免对原有遗留系统的直接修改,改为在AppService微服务中实现新业务逻辑,做了如下修改:
直接访问遗留系统数据源:由于原门户平台的基础搜索API提供的数据不足,便让AppService通过直接访问遗留系统数据源的方式,获得所需的房源、发布者、地理兴趣点等数据信息。
构建基础搜索模块:在AppService中构建基础搜索模板(Engine API),实现与原门户平台的基础搜索API同样的功能,提供数据给上层API。
构建业务层API:在AppService中构建业务层API,新增两组API,分别是提供地理兴趣点数据的POI API和房产发布者信息的Pub API。
独立地理位置服务:将之前相对独立的Loc API,拆分出来作为独立的地理位置服务LocService。
改造后的系统架构图,如图6-15所示。通过这步改造,将基础搜索数据的查询功能重构到了AppService微服务内,减少了对原门户平台相关功能的修改。
6.4.2.3 步骤3:拆分基础搜索服务,替换数据源
产品的需求持续演进,接下来,团队需要为桌面端用户新增支持多边形的地理位置搜索功能。但在现有架构下,FAST Search搜索引擎并不支持这个功能,这就意味着需要替换原有的搜索引擎,需要对原门户平台的代码做大量的修改。
如何有效地实现这个特性呢?具体操作方法如下:
1.将基础搜索API拆分出来作为独立服务EngineService,这样以后关于搜索相关的逻辑,都可以放在EngineService中实现了。
图6-15 步骤2:通过遗留系统数据源提供数据
2.在EngineService中实现基于多边形的搜索。
3.对原门户平台的搜索机制重构,让其使用EngineService获取相关数据(之前是使用FAST SDK)。基于这种方式,门户平台和AppService与底层搜索引擎隔离开来,可以方便地替换底层数据源。
该方案的实现,如图6-16所示。
图6-16 步骤3:拆分基础搜索服务,替换数据源
6.4.2.4 步骤4:构建移动端专属的后端服务
团队在预期时间内很快地完成了前面的各项需求,随着业务发展迅猛,接下来需要在手机端和平板端增强用户体验。然而,目前手机端、平板端的实现逻辑较落伍,均是基于JSP的模板方式实现。如果要直接修改,成本高。
考虑到在屏幕尺寸、性能和显示限制等方面,移动设备与桌面浏览器的能力存在显著差异。因此,移动应用对后端的需求与桌面UI是不一致的。
如果仍然使用原有的门户平台逻辑,则需要同时为这两种客户端提供数据,这极大地增加了维护的成本。于是,团队借鉴了BFF模式,将手机端和平板端网站的特定后台需求,拆分为一个独立的后台服务SPA-Web,作为后端实现。为前端提供数据。然后基于最新的前端技术,采用轻量级、更有效的单页面方式实现前端,如图6-17所示。
图6-17 步骤4:构建移动端专属的后端服务
通过为移动端创建一个专属的后端服务,可以为移动端的用户进行专门的优化,量身打造最佳体验。同时,由于微服务架构下的这个新服务足够的小而简单,就更容易进行修改、部署和上线了,也更容易在性能和行为方面进行精心的优化。
6.4.2.5 步骤5:基于数据继续划分微服务
随着团队的微服务化实践越来越成熟,整个系统的架构趋向于朝更细的粒度演进。
系统典型的业务场景分为待售房源(Buy)、已售房源(Sold)、待租房源(Rent)等。所以,团队基于这些业务继续划分单独的搜索API以及独立的数据存储机制,每种数据由独立的Elastic Search 集群存储,并利用前后端分离的机制实现前后端的解耦。
解耦后的系统,如图6-18所示。
图6-18 步骤5:按照数据划分微服务
最终实现的服务,如下表所示。
服务名称 | 主要描述 |
UserService | 处理用户信息的认证和鉴权 |
SavedSearchService | 处理用户保存的订阅条件 |
POIService | 处理POIService相关的信息 |
LocService | 提供地理位置相关接口 |
BuyService | 提供待售房源相关的接口 |
SoldService | 提供已售房源相关的接口 |
RentService | 处理待租房源相关的逻辑 |
对于系统中使用的服务支撑组件,如服务注册发现机制、集中化配置信息等机制,其实现和在之前的章节中阐述的差不多,这里就不展开赘述了。
//
6.4.3 改造结果
//
可以看到,经过上面一系列步骤后,原有的门户平台已逐渐迁移为微服务的系统,原有的大约300万行的代码也只剩下了大约50万行,继续提供着业务价值。团队对遗留系统的修改和变更成本已经大大减少,总体交付周期也大大缩短,技术的升级带来性能、可维护性的提升,充分享受到了微服务架构小而美的好处。
▼
往期精彩回顾
▼
《微服务架构与实践(第2版)》是在第1版的基础之上,基于作者近年来对服务化改造的实战经验和思考,并结合业界的技术趋势进行的一次体系化的精进。全书共分为基础篇、策略篇和实践篇,剖析了微服务架构理论、微服务实施的参考模型、最佳实践以及基于真实案例的实战。
本书不仅适合架构师、开发人员以及技术管理者阅读,也适合正在尝试向微服务架构迁移的团队或者个人。
京东购买链接:
https://item.jd.com/12511883.html
用心做开源,不忘初衷
扫码加小助手
微服务云应用平台(ServiceStage):提供微服务的开发、构建、发布、监控及运维等一站式解决方案。
https://www.huaweicloud.com/product/servicestage.html
Apache ServiceComb:业界首个Apache 微服务解决方案,致力于帮助企业、用户和开发者将应用轻松微服务化上云,实现对微服务应用的高效运维管理。
http://servicecomb.apache.org/cn/docs/join_the_community/
本文为作者原创文章,未经作者允许不得转载。
在看点这里