当前位置: 首页 > 科技观察

遗留系统服务拆分_0

时间:2023-03-20 23:39:02 科技观察

过去一年,我的项目对一个传统行业客户的IT核心系统遗留系统进行了改造。参与过系统一个业务模块的拆分和服务化。过程中有一些有趣的实践落地,记录下来分享给大家。项目背景这是一个至少运行了15年的单一系统。使用的技术栈有JDK8、Servlet、JSP、Oracle、JDBC、存储过程、Weblogic。从这些关键词中,可以感受到它的沧桑。整个系统在一个代码仓库里,或者按业务或功能分成30多个maven模块。模块之间可以任意调用对方的方法,也可以随意访问对方的业务数据库表。最令人沮丧的是,大部分业务逻辑都是用复杂的SQL语句和存储过程编写的。几百行的SQL,一堆表的join,层层调用的存储过程比比皆是。当然,该系统不属于遗留系统“无自动化测试”的典型标签。图1单个代码仓库中包含的各种业务模块和技术模块。客户PO是一个对技术有理想和抱负的人。他不希望这个系统继续恶化,所以他找到了我们公司来对系统进行现代化改造。其中一个落地措施就是拆分服务这个单体系统。经过遴选,A企业成为本批改造的试点对象。本次拆分的目标是将A业务的代码和数据库表从原来的代码和数据库中拆分出来,形成一个独立的A服务及其数据库,实现A业务的代码独立性、数据独立性、部署独立性。图2拆分目标总体策略本次服务拆分的策略可以概括为三种:(1)先代码拆分,后数据拆分代码和数据是服务拆分的两个重要物理实体。这两种方式的优缺点在《Monolith to Microservices》是先反汇编代码还是先反汇编数据中有介绍。我们考虑在现有代码极其复杂的前提下,先拆数据会给代码带来更大的复杂度,而且在出现问题需要回滚的情况下,拆分前后的数据一致性也非常困难.所以我们先选择代码拆分的策略。图3先代码拆分,再拆分数据(2)以单个页面请求为单位进行拆分拆分工作由10名开发人员承担。如何划分每个人的工作内容:按数据库表?通过Servlet?按页?我们选择按要求划分。A业务的后端Servlet提供了近300个功能,每个功能对应一个前端请求URL。我们将开发任务以单个请求的粒度进行划分,并以我们熟悉的敏捷开发方式创建Jira卡片并安排到每个迭代中。经过这样的粒度划分,大部分开发任务可以对应1、2、3、5天的工作,非常有利于安排每次迭代的内容,分批上线,形成紧凑的工作节奏,减少每次迭代开发任务的测试范围。(3)先完整复制代码,再修改新服务的框架。搭建好新服务后,是一开始就把A业务的代码复制到新创建的服务中,还是在做开发任务时把涉及到的代码复制到新创建的服务中?对新服务进行更改?我们选择了前者,因为后者在多人并行开发的时候会遇到副本冲突的问题。与其这样,还不如从头拷贝一个整体,然后在做开发任务的时候修改涉及的代码。当然,一开始需要复制一些公共代码或者其他依赖它的业务代码,保证A服务的代码可以编译通过。使用FeatureToggle进行功能回退只要是代码的改动,就有可能引入bug。——我说了,虽然Dev和QA团队尽了最大的努力,但是拆分服务上线后还是无法避免bug。一旦发生,需要尽快切换回原系统,减少对业务的影响。结合我们以页面请求为单位进行拆分的方法,我们引入featuretoggle作为切换新旧系统的开关,控制前端的请求是发送到原系统还是发送到拆分服务。图4使用FeatureToggle在新旧系统之间切换。实现时,默认所有请求先发送到原系统。我们在原有系统的后端增加了一个请求过滤器,从过滤器中提取请求的URL。根据URL判断:如果是已经拆分的功能请求,并且启用了数据库中记录的toggle,则将请求转发给新的服务处理;否则,仍由原系统处理。由于回滚是作为一种快速恢复功能的手段,引入开发协议:在拆分过程中,只允许修改新服务的代码,不允许修改原系统的代码。FeatureToggle不仅可以在处理线上问题时及时止血,还可以给团队带来额外的好处:在上线前,Dev和QA可以切换开关,快速比较某个功能的效果是否正常改造前后一致。当该上线,但测试还不够充分,或者担心年终大促业务高峰期引入BUG影响业务时,就会出现“开发中”的情况已完成但无法上线”。此时关闭对应的toggle,允许拆卸拆分后,该功能将暂停。使用代码分析工具简化数据库表的使用分析每个拆分任务的关键任务之一是识别该函数读写的表是否为A业务的表。因为最终只有A业务的表会被拆分到A库中;否则,如果不是A业务的表,则认为只有原来的系统可以直接读写,不能在A服务中读写,所以需要调用原来的有新的方式为系统添加API,替代原有的数据操作。如果是简单的功能,肉眼看代码还是可以识别出操作了哪些表的。只要功能稍微复杂一点,人工检查的效率和准确性就会大大降低,甚至无法进行。幸运的是,我们公司的一位大牛为这个项目开发了代码分析工具。它可以分析编译后的Java字节码文件,获取方法调用链中所有方法的调用关系,以及SQL和存储过程。表格,并将分析结果形成树状结构,并保存为xmind或svg格式。通过分析结果,开发者可以很容易的知道当前拆分的功能涉及到哪些表,以及这些表在使用时调用了哪些方法,从而知道下一步要拆分的代码。图5稍微复杂一点的方法调用链的分析结果如果没有这个分析工具,Dev可能要花几天甚至几周的时间来分析一个复杂的待拆分函数,而现在只需要几秒钟的时间,分析结果就会出现在眼前。该工具被客户誉为“神器”。我们在使用的时候常常感叹:“自动化真香!”使用CodeOwner使新旧代码保持一致。既有系统和新服务中的代码都需要同时修改,否则会出现两者功能不一致:如果只修改原系统,不修改新服务,那么功能会和原来的不一样修改前有区别。如果只修改新服务而不修改原有系统,那么一旦关闭切换,原有系统将无法提供新的所需功能。客户使用的版本控制系统是BitBucket,通过提交PullRequest(PR)合并新代码。因此,我们利用BitBucket的CodeOwner功能(Github和Gitlab也有此功能)对原系统中A业务涉及的模块和文件夹进行监控,同时也对新服务的所有代码进行监控,将二者拆分团队的骨干开发人员。设置为代码所有者。这样,一旦被监控代码的变更被包含在PR中,CodeOwner会自动设置为PR的Reviewer,CodeOwner会在收到系统通知后同步检查代码是否被修改。如果代码更改未同步,则不允许合并拉取请求。结论让我们面对现实吧,我们今天所做的就是编写明天的遗留系统-MartinFowler我们正在编写,我们将继续,我们正在处理遗留系统。在与遗留系统的相爱相杀中,我们需要根据项目目标和现状,结合过去的经验,剪裁和取舍,面对新出现的挑战。我用这篇文章来吸引玉石,欢迎大家交流。