单体应用到微服务架构转型-实践过程总结

发布时间 2023-07-05 14:46:55作者: 码农小凡

 

今天重点谈下传统的单体应用架构朝微服务转型实践过程中遇到的一些问题,具体的解决方法的一些思考,供大家参考。

这篇文章涉及到的项目背景为我们自己的财务共享项目,即原来是一个大单体应用,需要进行微服务架构化拆分,我在前面华南CIO大会上关于微服务架构转型的演讲中也提到了这个实践案例,但是对有些关键内容没有细化展开,因此今天对里面涉及到的一些内容做展开说明。

前期积累

从12年我们开始实施联通集团的私有云PaaS平台项目的时候,就进行了整体的平台+应用的架构规划,而当时应用要进行组件化拆分没有马上实施,但是基础平台建设则按进度推进。

而基础平台建设里面强调过多次的即基础开发框架+4A+流程引擎。

也就是通过基础平台建设,需要形成了一个包括了4A+流程引擎的空框架,当接到新的业务的时候我们可以基于这个空框架快速的进行业务功能的设计和开发。

在第一阶段我们将4A和流程引擎整体融合在空开发框架中,也就说当进行新项目的开发的时候所有的4A和流程引擎JAR包也需要引入到开发人员本地项目,同时还需要将整个包含了4A和流程数据库表的数据库全部拷贝一份,基于这套进行开发。

而这个显然是不符合SOA下共性基础能力下沉,以接口服务方式提供的思想,同时也不利用后续基础组件的统一维护和管理。

因此在这个阶段,我们已经做了4A+流程引擎从单体应用系统中的剥离,形成一个基础平台能力,同时也独立数据库,该基础平台单独设计和部署,业务应用以接口服务方式进行交互。这样处理后,整套基础平台可以很方便地应用到财务类应用,SOA和主数据类应用,供应链类应用多个应用系统中。

也就是说,我们在前期已经做了一个重要工作,即共性基础技术平台剥离。

 

如果你自己的业务系统需要转型微服务,实际上这个工作也是必须提前做的一个工作,即共性技术平台必须下沉,而不能再由原来的单体应用系统来承担这部分工作。

单体拆分

在传统单体应用转型到微服务的时候,有一个关键内容就是单体应用拆分。

对于单体拆分,实际上最重要的还是数据库拆分,但是根据微服务架构标准定义,对于每个微服务来说从数据库,逻辑层,前端都应该是完全独立自治的一套。以实现各个微服务间的彻底解耦。

如上财务共享应用来说。

如果我们最终仅仅拆分为报账,预算,银企,影像四个微服务,那么我们拆分出四个独立的数据库是合理的,相互之间也可以解耦。

但是在拆分后可以看到就报账一个模块来说,实际上我们还拆分了差不多10个独立的SpringBoot项目,可以独立打包为Jar包并进行部署。那么这10个独立的项目实际是对应到一个数据库上面的并没有在进行拆分。这也就是前面文章谈到的,引入了微服务业务域的概念,同一个业务域里面的微服务模块可以拆分,但是数据库不再拆分。如下图所示:

 

对应报账应用,我们接着举例再说明下。

一个完整的报账应用,实际上里面包括了日常费用报销流程,差旅费报账流程,借款报账,采购报账流程等。我们可能想到直接按流程进一步拆分。

但是实际我们看到对于上面四个流程不是简单纵向孤立流程,每一个流程都会用到共性的基础数据能力,同时流程执行完后涉及到形成正是的应付发票或凭证。即,流程虽然独立,但是流程前后都有共性的内容进行支撑。

再回到SOA的思路,即我们希望应用的构建是横向分层进行的。这种横向分层重点就是首先找共性业务或数据能力下沉,然后再基于共性能力来构建上层的应用或流程,这个思想和现在说的中台思想完全是一致的。

 

因此我们在进行报账微服务拆分的时候,不是简单按照业务功能模块进行拆分,而是进一步结合SOA和横向分层进行应用构建的思路进行拆分。

如上图:实际上就可以拆分为独立的6个微服务模块,这6个微服务模块采用独立的数据库SID或Schema本身也不会造成大的耦合性。同时我们看到,当有新的报账流程需要实现的时候,底层的基础数据中心和发票凭证中心两个模块基本都不用做大的变动。

最后简单总结就是两点:

  • 其一是微服务模块和数据库不一定必须1对1,可以多个微服务对应一个数据库
  • 其二是微服务拆分要引入SOA分层构建和领域建模思路进行

前后端分离开发

首先我要澄清一个概念,即在微服务标准要求和定义下,我们看不到有任何的必须前后端分离的说法。当然我也多次强调了,在微服务实践中,前后端分离是一个比较好的开发模式,因为前后端分离本身也很好的体现了SOA分层构建应用的思想。

而实际上SOA分层构建和前后端分离是两个不同的概念。

比如前面谈到的报账应用拆分为6个独立的微服务,其中底层两个共性的微服务模块,上层4个独立的流程相关微服务模块。这个是明显的SOA分层思路。

而如果是前后端分离思路则是上面谈到的6个微服务模块,进行前后端进一步拆分。比如拆分为6个独立的后端模块,6个独立的前端模块。同时前后端模块间通过Rest API接口交互。

当然前端模块也可以合并,比如对应APP端我们可以不拆分,只保留一个前端模块。

 

大家可以比较下SOA分层思想图和前后端分离两张图。实际上两者之间没有任何必然的约束关系。即你应用了SOA分层思想,并不代表你的模块开发一定要前后端分离。反之,你采用了前后端分离,同样不代表你有SOA分层共性能力下沉的思想。

也正是这个原因,我们再来回顾下中台的一个说法,即将后端模块或逻辑层理解为中台,这个明显就是错误的。后端模块是前后端分离下的概念,但是在前后端分离后,你的后端模块并不一定就是共性下沉业务能力,并不一定就能够复用。

也正是这个原因,我们再次强调前后端分离在微服务里面不是必须,但是SOA分层思想往往在进行微服务模块拆分的时候是必须具备的思想。

前后端分离看起来进一步实现业务功能实现解耦,但是实际上前后端分离本身也带来其它问题,比如一个业务功能实现往往需要经过更多的人协同配合才完成,那么功能出错的概率自然也就提高。特别是在前后端分离后,如果后端的单元测试和自动化测试能力跟不上的时候,前后端分离开发反而是明显降低了开发效率。

同时往往后端人员并不熟悉前端类似VUE,React等开发看框架,导致团队规模小的时候,人员并不能得到充分的复用。或者在极端情况下,一个3个后端1个前端的小团队,如果前端离职将直接导致整个开发工作停滞。

微服务开发框架和技术栈

 

对于微服务开发框架,我们采用主流的Spring Cloud ,并且 还基于 Spring Boot 进行了通用性模块的封装,例如鉴权服务、调度服务、消息服务等等;前端使用 VUE 作为开发组件进行二次封装和改造并自研了前端组件库,使之更适合企业级应用系统的使用体验。

大家在选择微服务开发框架的时候经常会遇到选择困难症。

比如究竟用SpingCLoud好还是Dubbo好,是采用自带的API网关还是采用Kong网关,是否采用类似Nacos注册中心替代Eureka等。实际上我的建议很简单,就是直接采用标准的SpringCLoud框架来进行微服务架构设计和模块开发。

在真正应用和实践起来后,再来看哪些无法满足真实的业务需求,对个别独立的组件进行替代。要明白,当前很多独立的类似网关,限流熔断,服务链监控等各种开源组件,基本都能够做到完全兼容SpingCloud,或者说有专门的插件来适配。

至于性能方面的问题,对于传统的业务系统信息化应用本身面对的性能压力远远小于互联网应用,当前的SpingCLoud框架在性能上基本能够完全满足。如果不满足,你也不要马上怀疑是框架的问题,而是应该首先检查你本身的代码问题。

选择了Spring Cloud后,实际上看到还需要做其它大量的技术组件选择和共性化封装,包括消息,缓存,日志,安全,自动化调度,文件存储等,这些都需要在搭建技术架构的时候进行融入,这样才能够形成一个完整的底层技术架构。

 

因此在整个技术平台搭建中,微服务框架选择仅仅是第一步。而我们实际做的时候,技术平台构建本身又包括了两个关键阶段。

在阶段一,我们的重点是将前面谈的消息,缓存,安全,调度,文件,日志等各种和业务无关的共性技术能力抽取,形成标准的技术组件或技术服务。

在阶段二,对技术平台进一步发展演进,重点在于提升开发效率和快速开发上,因此在这个阶段包括了各类共性组件,前端组件的封装。自定义报表,自定义查询,表单设计器,Rest API接口快速开发和生成,业务模块快速开发和生成等。

由于本身项目转型到SpingCLoud微服务开发框架也是一个逐步的过程,因此必须项目团队全部熟悉整个微服务下开发方法和模式后,通过实践才能够更好地总结哪些可以进一步可复用,哪些和业务无关技术能力可以进一步自动化。

Zuul网关或Kong API网关

 

对于API网关,实际上我在前面很多文章的里面都谈到过。

对于Zuul网关,Netflix官方当时有个解释如下,即Zuul是从设备和网站到后端应用程序的所有请求的前门,为内部服务提供可配置的对外URL到服务的映射关系,基于JVM的后端路由器。其关键功能包括了代理,路由,安全,限流熔断,灰度发布等。也就是说在去中心化架构下,实际是通过Eureka,Ribbon,Hystrix等多个组件来共同实现了网关的能力。

在我前面谈到,如果你构建的应用不需要和外部交互,也没有APP移动端,那么整套架构里面完全不用启用API网关,直接采用服务注册发现即可。

结合我们的场景,整个报账应用实际是有APP端的,那么是否启用API网关?

当我们思考这个问题的时候看到,对于APP端实际并没有太大的业务并发访问,也就是说没有限流熔断的需求。APP端唯一需要考虑的就是统一服务代理出口和安全方面的考虑。

在这种场景下,我们没必要马上启用API网关。

简单来说采用Ngnix来解决服务代理路由问题,通过Auth2.0+JWT来解决安全问题足够。

通过Ngnix来解决的时候,唯一需要做的就是我们在扩展微服务模块节点的时候,需要对集群节点进行手工配置,这点本身我们可以接受。

但是Ngnix本身并不能很好完成心跳监测功能,如果出现节点假死仍然会发送接口请求。在这点上我们引入当前网上推荐的Ngnix+Lua,即OpenRestry方案。OpenResty 通过lua脚本扩展nginx功能,可提供负载均衡、请求路由、安全认证、服务鉴权、流量控制与日志监控等服务。即本身也实现了API网关的常见功能。

微服务和DevOps

 

在实施微服务后,你会看到原来的一个单体应用已经拆分为20个独立的微服务模块。每个微服务模块都涉及到编译,构建,打包部署,测试,环境迁移等诸多工作。

当是一个大应用的时候通过人工来解决没有问题,但是拆分为20个微服务后再通过人工来解决基本不现实,因此必须进行自动化。

而这里的自动化首先就是要实现CI/CD持续集成和持续部署能力。

当前,采用Jekins进行持续集成和扩展定制已经成为应用最多的实践。而对于财务应用在前期同样采用Jekins+Maven来完成整个自动化编译,构建,打包和部署动作。在这个阶段完成后整个持续集成过程基本通过脚本化完成,无可视化的界面,配置的修改也无法在前台完成。

在后期,这块内容则全部转移到我们自己的DevOps支撑平台上面,对于该平台我在前面也有过介绍,即完成整个持续集成和交付的一个支撑平台,实现了主流的各种持续集成和交付开源工具的集成,提供了持续集成,自动化测试,持续交付,流水线设计,资源管理等核心能力。

由于DevOps支撑平台核心也是基于Jekins来实现。

因此财务应用花了不到一周的时间即全部迁移到了DevOps支撑平台上面。

 

在这个阶段,我们实现了整个应用可以持续集成和发布,包括各个测试环境前的动态迁移,同时也支持将应用之间动态发布到类似阿里云,华为云的公有云环境。但是对于实施的客户来说,很多客户仍然是私有云的数据中心。那么持续交付这个步骤并不能自动化完成。

在这种场景下,我们整个持续集成和交付就分为了两端,即后端测试人员测试和UAT测试全部通过DevOps平台来完成,但是最终的生产环境交付和部署需要手工交付给客户前方实施人员完成。

这样,DevOps平台就必须有一个关键功能,即制品库和版本提取。

即对于持续集成完成并测试通过的版本,我们需要打标签进行标记,通过标签我们可以直接在制品库功能中提取和下载对应的版本。

版本的下载既提供下载部署包方式,也提供直接下载容器镜像方式。下载部署包方式的原因是客户现场有些并没有实施容器化部署,那就还必须保留对传统部署方式的支持。

接口驱动开发

 

在我们自己的API网关产品的开发中,实际上已经集成了Swagger工具,即可以将Swagger定义的接口描述文件直接导入到API网关里面,新生成和发布一个API接口。同时对于发布的API接口,我们还提供Mock功能,即先不要对接到后端服务,也可以提供按要求格式的接口数据返回,方便消费方进行测试。

实际上在微服务架构设计和开发里面,特别是进行了前后端分离后,引入类似Swagger工具来实现基于接口驱动的设计开发是必须的。

 

即首先通过Swagger定义清楚接口描述文件,并基于接口描述来生成前端消费端代码框架,后端提供端代码框架。这样确保接口契约,前端代码框架,后端代码框架三者一致,有了标准的接口契约前后端人员可以启动并行开发工作,后续再进行集成。

在并行开发适合,通过API测试提供的Mock功能返回测试数据,前端开发人员基于测试数据即可进行前端功能的完整开发。同时在接口先行开发模式下,我们可以进一步通过Swagger来实现API接口的自动化测试功能,形成一个完整的闭环。

在谈DevOps持续集成和交付最佳实践的时候,经常会谈到自动化测试,实际我们看到对于自动化测试中优先需要考虑的就是接口的自动化测试工作。