9月7日-8日,相约 2023 腾讯全球数字生态大会!聚焦产业未来发展新趋势! 了解详情
写点什么

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

  • 2020-05-06
  • 本文字数:10269 字

    阅读完需:约 34 分钟

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

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


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

一、遗留系统综述

1. 什么是遗留系统

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


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


  • 庞大的单体应用:遗留系统大多是多年积累下来的“巨无霸”系统,以单体应用形式呈现。

  • 难于修改:多年的代码累积过程中疏于重构,导致代码的可读性和可扩展性较差。

  • 维护成本很高:在庞大的代码中寻找 bug 的根本原因可能会比较困难。此外,运维也是难题,比如在本章开头提到的 Perl 系统,没有 APM 工具支持它的监控。

  • 学习成本高昂:由于设计陈旧或技术过时,可能了解遗留系统技术和业务的人已经很难找到。

  • 缺乏质量保障:由于过去缺少投入,许多功能基本上没有自动化测试来保障质量。


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

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

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


  • 上线困难,业务阻塞风险高:在遗留系统重写的过程中,往往会有新增需求对遗留系统的功能进行修改,在新系统未上线前,这些需求要么被阻塞,要么需要付出双倍的工作量在遗留系统和新系统上同时实现,无论哪种选择对团队都是很难接受的。

  • 影响面不可控,系统改造周期长:遗留系统往往运行着关键业务或者持有核心数据,会被多个上层服务所调用。一旦决定开始重写,其影响面无法评估,需要极为小心,考虑周全,客观上会造成系统改造周期被无限期拉长。

  • 学习成本高,知识传递周期长:遗留系统的改造周期越长,对系统的学习成本就会随之升高。在这个过程中随着人员的正常流失,团队对业务和技术的熟悉程度会逐渐降低,而新人需要花费更多的时间才能熟悉遗留系统改造过程中必要的知识和技术。


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

二、遗留系统改造策略

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


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


  • 遵循“演进式改造流程”,优先改造最具价值的部分,并保证改造过程中的风险可控。

  • 采用“绞杀者模式”,通过逐步替换而非一次性替换的方式,来保证新旧系统的平滑过渡。

  • 采用“挎斗模式”,将不容易改造的遗留系统接入微服务环境中。

1. 演进式改造流

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



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

1.1 构建服务路标图

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

1.2 服务选择

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


  • 优先拆分相对独立的部分,独立业务与旧系统之间的耦合相对较小,比较容易实施。

  • 优先拆分频繁变更的部分,可以通过拆分为新的服务实现独立部署与快速上线,对于提高团队总体交付效率价值较大。

  • 优先拆分有特殊资源占用需求的部分,比如将计算密集型任务拆分为新服务,并恰当地利用云基础设施的弹性伸缩能力对新服务实例进行扩缩容,有助于提高系统总体性能并降低成本。

1.3 服务改造

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


  • 新旧系统可能需要不同的数据源,或具有不同的数据库结构,怎样解决数据之间的同步和依赖问题?

  • 单体的旧系统需要拆分为多个服务时,怎样实现安全的渐进式拆分?


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

1.4 业务验证

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

1.5 迭代优化

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

2. 绞杀者模式

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



图 6-2 抽象分支


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


  • 新旧组件共存:开发新组件实现同一套抽象层接口,并与待替换组件同时在系统中工作,但开始时只将少部分功能在新组件中实现,或者只有少部分生产环境流量导入到新组件上。

  • 逐步替换:随着功能逐渐完备和技术逐渐成熟,新组件承担越来越多的生产环境流量。经过全面考验后,新组件可以完全替代旧组件之时,旧组件就可以正式下线了。此时如果有需要也可以移除抽象层。


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


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


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



图 6-3 绞杀者模式


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


考虑新系统和遗留系统之间的数据共享或者同步方式。


确保绞杀者门面服务不会出现单点故障或成为性能“瓶颈”。

3. 挎斗模式

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


  • 然而将各种形态各异的遗留系统接入到微服务环境中并不容易,存在以下常见问题:

  • 需要对原有代码进行一定修改,但遗留系统有时本身已经难于修改,而且如果原系统是由多种语言构成的,就要为每种语言考虑解决方案。


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



图 6-4 挎斗模式


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


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


  • 挎斗服务是独立运行的进程或服务,与原遗留系统的实现语言无关,不需要为每种语言各开发一种挎斗。

  • 由于是非侵入式的接入方法,通常不需要改写原遗留系统的代码,可以实现零修改成本的接入。

  • 挎斗服务与原遗留系统相邻部署,可以访问与原系统相同的资源,有时可以拿来作为监控服务的接入代理。

  • 虽然增加了一些通信成本,但是由于挎斗与原系统相邻部署,增加的通信成本往往很少,延迟很低。


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


  • 注意与原系统相邻部署,降低通信时延。

  • 注意进程间采用与语言无关的通信机制,如 REST。

  • 考虑使用容器化的部署方式,比如将跨斗服务和遗留系统部署在同一个 Pod 中。

  • 考虑放入挎斗的功能,是作为单独的服务或是传统的守护进程运行方式。


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

三、遗留系统改造场景

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


  • 新旧系统可能需要不同的数据源,或具有不同的数据库结构,怎样解决数据之间的同步和依赖问题呢?

  • 单体应用下的旧系统需要拆分为多个服务时,怎样实现安全的渐进式拆分?


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



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


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


  • 实现新业务:需要在系统中增加新业务,与现有业务相对独立。根据新业务与现有业务之间.

  • 否存在数据依赖关系,又可分为两种子场景,即与现有业务数据独立或者与现有业务数据依赖。

  • 对现有业务微服务化:需要将系统的现有业务改造为微服务架构,比如对现有业务的拆分和服务化工作。


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


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


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



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


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


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


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


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

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


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


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


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


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



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



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


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


方案 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 进行数据同步


改造场景 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 服务依赖关系图示例

四、遗留系统改造案例

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

1. 改造前的系统情况

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


  • 为用户提供基于地理位置、关键字的房产信息搜索功能。

  • 提供基于定制搜索条件的房源信息订阅。

  • 提供桌面端、平板端、手机端等多种不同方式的访问。



图 6-13 系统现状


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


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

2. 改造过程

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


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


  • 支持 NativeApp 提供核心功能

  • 三个月上线首个版本


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


  • 该系统的主要研发成员在海外,国内团队刚成立,新人多,知识迁移成本高。

  • 系统本身是基于商业通用搜索平台上进行的二次开发,逻辑复杂,代码耦合度高,变更成本高。


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


  • 在遗留系统上封装现有逻辑,提供基础 API。

  • 重新实现一个服务,类似之前章节提到的 BFF 模式,为 NativeApp 提供数据。

  • 该服务通过访问遗留系统提供的基础 API 获得所需要的数据。

  • 该服务内实现必要的转换,提供给 NativeApp 界面进行呈现。


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


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



图 6-14


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


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


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


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


支持房产发布者的信息展示。


支持地理兴趣点(POI)信息显示。


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


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


  • 直接访问遗留系统数据源:由于原门户平台的基础搜索 API 提供的数据不足,便让 AppServic

  • 通过直接访问遗留系统数据源的方式,获得所需的房源、发布者、地理兴趣点等数据信息。

  • 构建基础搜索模块:在 AppService 中构建基础搜索模板(EngineAPI),实现与原门户平台的基础搜索 API 同样的功能,提供数据给上层 API。

  • 构建业务层 API:在 AppService 中构建业务层 API,新增两组 API,分别是提供地理兴趣点数据的 POIAPI 和房产发布者信息的 PubAPI。

  • 独立地理位置服务:将之前相对独立的 LocAPI,拆分出来作为独立的地理位置服务 LocService。


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


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


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


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


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



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


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

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


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



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

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

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

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

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

图6-17

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

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

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

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

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

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

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

3. 改造结果

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

小结

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

  • 遗留系统是“需要被替代的系统”,往往存在类似的特征,如难于修改、学习和维护成本高、缺乏质量保障等。

  • 通过直接重写并一次性替换遗留系统解决微服务改造问题是不现实的,可能会导致上线困难、影响面不可控、学习成本高等问题的产生。

  • 对于遗留系统的改造过程,应当采取逐步替换而非一次性替换的策略。通常采用“演进式改造流程”和“绞杀者模式”来保证整个改造过程可控,并实现平滑过渡。

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

本文转载自技术琐话公众号。

原文链接:https://mp.weixin.qq.com/s/Z6Tk7FMLX9PIdbcL3iyIHA




活动推荐:

2023年9月3-5日,「QCon全球软件开发大会·北京站」 将在北京•富力万丽酒店举办。此次大会以「启航·AIGC软件工程变革」为主题,策划了大前端融合提效、大模型应用落地、面向 AI 的存储、AIGC 浪潮下的研发效能提升、LLMOps、异构算力、微服务架构治理、业务安全技术、构建未来软件的编程语言、FinOps 等近30个精彩专题。咨询购票可联系票务经理 18514549229(微信同手机号)。

2020-05-06 10:241153

评论

发布
暂无评论
发现更多内容

烂大街的Spring循环依赖该如何回答?

Java spring 程序员 架构 面试

2021年堡垒机采购就选国内知名品牌行云管家!

行云管家

云计算 云安全 堡垒机 云计算运维

“人人皆可成为AI开发者”!百度世界大会官宣百度松果学堂成立

百度大脑

人工智能

80W美团架构师整理分享出了Spring5企业级开发实战文档

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

spring-boot 2.5.4,nacos 作为配置、服务发现中心,Cloud Native Buildpacks 打包镜像,GitLab CI/CD

Zhang

gitlab nacos CI/CD spring-boot 2.5.4 CNB

手把手教你15分钟搭建人脸戴口罩识别软硬件系统

百度大脑

人工智能 EasyDL

Linux内核源码分析方法—程序员进阶必备

Linux服务器开发

操作系统 Linux内核 内核源码 底层原理 内核开发

TronChain波场链智能合约开发详情|智能合约DAPP搭建

量化系统19942438797

智能合约 波场链

iOS SDK 架构解析

神策技术社区

程序员 数据 埋点

探索技术与应用融合的区块链 实现产业良性发展

CECBC

膜拜!终于拿到了美团大佬分享的Netty源码剖析与应用PDF

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

阿里专家分享的SpringCloudNginx高并发核心文档

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

微信架构图设计&“学生管理系统”毕设架构

Imaginary

写作——开启技术成长之路

神策技术社区

程序员 写作 日志

小布助手在面向中文短文本的实体链指比赛中的实践应用

OPPO小布助手

人工智能 算法 模型训练 智能助手 短文本

4轮技术面+1轮HR面,成功拿到腾讯40k*16的Offer ,详解面试流程和真题解析

Java 程序员 架构 面试

阿里专家分享内部绝密RocketMQ核心原理与最佳实践PDF

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

MySQL information_schema 系统库介绍

Simon

MySQL

python通过Matplotlib绘制常见的几种图形

Python研究者

8月日更

LVS 学习: netfilter 与 ipvs 无秘密

绅鱼片

Linux 负载均衡 LVS Netfilter IPVS

Golang高并发:生产者消费者模型

Regan Yue

Go 语言 8月日更 生产者消费者模型

微信小程序图片流&本地图片转base64处理方案

页面仔小杨

微信小程序

小米和网易两位资深工程师联合编写的HBASE原理与实践PDF

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

迅雷不及掩耳盗铃

escray

生活记录 8月日更 搜房记

搭建太阳系3D可视化平台,科普宇宙的未知奥秘

一只数据鲸鱼

科普 数据可视化 智慧宇宙 太空

其实TCP聪明得很!详解TCP常见的五个异常处理场景

Java 编程 架构 程序人生 架构师

大数据实战训练营-sparkcore作业

Clarke

ipfs投资者靠什么赚钱?投资ipfs要多少钱?

投资ipfs要多少钱 ipfs投资者靠什么赚钱

中国法定数字货币(DCEP)全面启航!全国普及势在必行

CECBC

Android SDK 之用户路径采集

神策技术社区

数据 路径规划 分析 行为数据

新思科技推出Rapid Scan新功能帮助开发团队在编写云原生应用的同时确保安全性

InfoQ_434670063458

新思科技 静态应用安全

  • 扫码添加小助手
    领取最新资料包
从300万行到50万行代码,遗留系统的微服务改造_语言 & 开发_技术琐话_InfoQ精选文章