在咱们的工做中,常常会遇到系统或模块重构工做,今天就来聊一聊我曾经经历过的一次系统重构经历。nginx
重构发生的背景是,原有的系统架构采用all-in-one的方式,随着业务的快速发展,用户访问量急剧上升,系统请求流量成倍增加,陆续出现了各类问题。当时的系统架构的示意图以下程序员
当时遇到的典型问题有数据库
系统模块耦合严重,访问量上涨没法快速扩容缓存
数据库表混杂,定位不清。好比支付订单和商品订单在一张表,一个状态字段表明两种不一样订单的状态流转含义,常常会出现各类状态异常单据。架构
复杂SQL和跨表join横行,SQL慢查多,数据库频频告警app
无服务和领域划分,系统和接口耦合严重,常常是单点出问题,全系统宕机dom
接口响应慢,系统稳定性差,数据丢失、错乱状况常常出现分布式
产品需求版本庞杂,业务需求场景多,业务逻辑分散,需求迭代速度慢性能
客诉问题高发,排查问题困难,研发疲于奔命在查问题的道路上学习
面对着这些问题,当时摆在眼前的方案有两个
继续按照原有系统迭代,但可能要付出更多的人力、精力来维持系统的稳定性和需求迭代速度
彻底重构系统,但须要投入必定的人力,而且可能会在短时间影响业务的需求迭代进展
考虑到产品会长期迭代,而眼前系统已经成为巨大的瓶颈,所以决定对系统作彻底的重构。
当时我被领导安排做为这个重构项目的负责人。但领导也提出了要求
公司业务在快速发展中,系统重构期间,需继续保持业务需求的迭代速度,能够适当增长人员
新系统设计和规划,需考虑到3年后可能的用户访问量的上涨和数据量的上涨
新老系统切换期间,须要保证不影响用户和业务方的正常使用,不出现数据的丢失和错乱
任务既然已经肯定了,接下来就是考虑如何作的问题了。
系统重构是一个复杂的工程,而在一个业务高速发展的背景下作系统重构,无疑于给飞行中的飞机换引擎,须要考虑周全,计划缜密,才能保证万无一失。
针对面临的问题和目标要求,在技术层面制定了如下几点大的原则:
采用分布式架构设计,将各个模块系统彻底拆分出来,独立部署迭代演进
数据库模型彻底重构,原有的数据库模型已经没法支撑新的业务需求扩张,同时配合分布式架构的改造落地
业务逻辑收归,对涉及到的相关领域按照业务逻辑收口,统一服务接口
新老数据库双写,保证系统稳定性和数据不丢失
新老系统并行提供服务,经过灰度控制流量切换,直至老系统下线
在大目标和技术方向肯定的状况下,接下来就进入到实施阶段。
考虑到系统中的核心场景和瓶颈都出如今订单模块,所以制定了分布分阶段实施的方案,第一步核心解决订单相关功能的重构拆分,本文也将按照订单系统的重构拆分来展开说明。
既然是系统级重构,首先须要对业务需求和产品功能进行梳理。
好在有产品的历史文档,加上经过线上产品的实时模拟验证,可以将订单相关的大体功能脉络理清楚。
功能层面的需求梳理还没法知足系统级重构的要求,须要更精确的梳理到接口级别,包括对订单相关接口调用的上游模块和订单对其它下游模块的调用,这样才基本作到把订单模块的边边角角功能彻底覆盖。
功能需求和接口层面的整理,为数据库表模型设计提供了大体的参考。
经过对已有产品功能和接口的分析,分析清楚订单模块提供的核心能力应该有哪些,和其它模块的边界是怎样的,外部对订单模块的复杂调用需求有哪些,基于这几点设计新的数据库模型。这里面有几个关键的考虑:
大数据量的解决方案:分表。考虑到订单数据量过大,原有的单一订单主表已经没法知足需求,所以将订单主表按照用户ID取模的方式分64张表,按照单表5000w数据的测算,基本能够支撑将来3年内数据量的增加。按照用户维度的分表方案,在单个用户的订单查询场景下,经过数据库单表就能够完成。但除了按照用户维度的查询,还有按照时间、地域等维度的订单查询需求,考虑到继续按照其它维度创建相应的分表方案太过冗余,所以决定对其它的查询能力经过ES构建搜索索引提供。
主键生成策略:分布式ID自增。订单表的主键,原来采用的是数据库自增策略,分表后已再也不适合,借鉴twitter的snowflake方案,设计了分布式的ID自增方案。
跨表查询的解决方案:服务层聚合。原有的代码中,有大量的跨表查询,容易致使复杂SQL出现,严重影响数据库性能。在新的数据库表结构下,将表的职责划分清楚后,再也不容许新的跨表查询,涉及到跨表查询的需求,经过在代码层面拆分红单表查询再聚合的方式,解除跨表查询带来的问题。
新老模型双写:为了保障系统的稳定性和不停机灰度流量验证,设计开关来实现对新老模型进行双写,所以还须要将新老模型的相关表整理好对应关系,如表字段枚举值不一样带来的映射等等。新老模型双写采用的方案也是经过程序处理,而非binlog等方式,主要考虑是为了处理的灵活性和设置开关用于切换的可控性。
数据库模型设计完成,接下来须要考虑到订单模块的架构设计方案。
根据对于需求的整理和理解、接口的梳理以及表模型的整理,大体能够肯定的系统架构示意图以下
这里面有几点须要说明:
首先,考虑到历史版本App没法强制要求全部用户升级,所以须要在Nginx中将老版本的接口作重定向,转发到新设计的接口服务层对应的接口上。
其次,对接口服务层作了拆分,因产品有不一样的展示形态,包括App、Web管理后台等,因不一样用户角色也有多个不一样的App,所以设计接口服务层,将相关的用户鉴权、数据加解密等统一收归到这一层处理。
第三,设计业务逻辑层,将订单相关的业务逻辑抽象到业务逻辑层,对外提供聚合封装的订单服务能力,如订单详情服务,订单列表服务等。业务逻辑层须要调用订单领域层的服务,还可能会调用到其它模块的领域层服务作聚合,例如订单详情页除了展示订单的信息,还有商品相关信息、支付相关信息、配送相关信息,这些信息基本都在业务逻辑层作聚合处理。
第四,领域服务层,核心是本领域内数据库表的操做封装,这一层基本只作单个表的增删改查。
最后,将订单相关的库从原有的单一库中拆分出来,创建订单库。实际上订单系统又分了多个领域,也可根据实际状况将订单相关的单一库再作拆分细化。
以上的设计只是一个改造完后的方案。但真正在实施重构的时候,为了保障线上系统能够不停机切换,又分别做了相关的开关设计用于过渡阶段的验证。
阶段一的过渡方案架构示意图以下:
在阶段一,有如下两点设计
在接口服务层all-in-one-app应用中,设计开关,能够控制all-in-one-app应用调用新的接口服务层,或继续走原有的直接访问数据库的逻辑。一旦出现新服务、新的库表模型有问题,经过开关直接切换回原有的调用链路中。
在领域服务层如oder-domain1-service、order-domain2-service、other-domain-service等应用中,设计开关,实现对原all-in-one库和订单库的读、写开关。
第一阶段上线后,正常的流程实现是
一、经过nginx将老的订单相关接口,转发到新的订单接口服务层应用的相关接口,实现流量切换。
二、将all-in-one-app应用中的调用开关打开,切换到调用新的拆分过的相关业务逻辑层。
三、在领域服务层,将对all-in-one库实现读、写,而对订单库实现只写不读。
这个阶段主要验证了整个服务和接口调用链路正常。当相关链路或环节出现问题,也能够经过关闭对应开关快速切换回原有方案。
阶段二的架构示意图以下
通过阶段一的验证,基本能够保证整个接口链路层面的逻辑正确,外部的调用方再也不感知接下来的改动变化。
阶段二的核心是在内部的数据层面作验证,保证落在新模型中的数据是正确无误的。
这一阶段没有特别多的开发工做,主要操做是
一、在订单领域层相关应用中,将对all-in-one库的写开关保留,读开关关闭
二、在订单领域层相关应用中,将对订单库的读写开关同时打开
这时整个调用链路和数据链路已经彻底实现了走新的接口服务和新的数据库表。再经过产品功能层面验证数据展示和产品流程是否正确,辅助老库相关数据作对照,基本可以验证整个系统的重构的正确与否。
这个阶段若是相关链路或环节出现问题,能够继续经过开关的控制切回到原有的调用链路和数据链路。
阶段二验证经过后,后续还须要作一些收尾工做,包括去除双写代码、去除代码中的开关及历史代码逻辑等等。
整个架构方案肯定后,接下来的重点是制定重构项目的计划,锁定相关资源,肯定重构项目的各个里程碑节点。
项目计划的制定,不只仅是关注订单模块自己的改造开发,还包括识别相关资源方和调用方,推进项目排期和落地。
在大部分的程序员认知中,只要本身系统没有大问题,都不肯意作相关的改动,毕竟任何一点改造都会额外的工做量,也会对系统的稳定性有着未知的影响。另外业务方也可能会对重构有排斥,这时就须要搞定关键人物,将改造的利弊陈述清楚,有时甚至须要上升到更高的层级去推进。最终可以和相关方达成一致,肯定改造的时间计划,提早锁定对应的开发、测试资源,保障整个重构的顺利进行。
开发阶段的任务既包括重构相关的接口改造开发,还须要考虑新老库表模型切换所作的兼容,包括新老库表数据迁移兼容、消息队列兼容、缓存兼容等。
在开发完成后,须要作新老库表数据迁移的模拟演练,以验证老的表数据导入到新库表后,流程和展示不会出现问题。
系统级的重构改动,不可或缺的是全流程的测试验证。
为了保证测试的充分性,当时咱们采起了如下几点关键措施:
一、经过已经沉淀和新增长的接口自动化用例,对大部分接口的响应和返回值作屡次的跑批验证
二、经过测试人员不断的交叉测试,对可能遗漏的业务场景验证
三、经过将线上的流量复制重放,对新的接口进行逻辑验证
四、经过预发环境的流量灰度,对全流程的业务作模拟验证
系统重构在开发测试完成后,面临的另一个重要问题是上线。
首先,制定详细的上线计划,将上线步骤事项按照前后顺序所有罗列出来
其次,每一个上线步骤事项须要预估出操做的时间并明确责任人
第三,对任一环节可能出现的问题,提出假设并给出解决预案,防止上线中途出现问题因慌乱致使可能出现的异常。
最后,统一指挥,有序切换流量作灰度验证,保障总体流程正常。
上线过程当中,借助已有的监控系统,用于观察系统、服务、接口等各项数据指标的变化状况,判断上线中的每一个环节是否有异常。
上线完成后,经过逐步的控制灰度流量占比,验证流程和数据是否正常,进而验证整个重构是否成功完成。
在订单模块重构的过程当中,其它模块也在改造和推进中,通过接近半年左右的时间,基本完成了对原有all-in-one服务的彻底拆分重构。
整个架构也在后续的迭代中不断进行着新的重构和演进,包括在接口层前置设计网关接入层、订单服务的细化拆分、ES对查询场景的替换改造、业务逻辑层的中台化演进等。
总结在整个重构过程当中的几个关键步骤
分析目前系统的问题点,找到最重要最优先要突破的点
肯定重构所要达成的目标、方向及限制条件
肯定重构涉及到的核心技术方案及可行性
梳理重构所涉及到的需求、场景及相关上下游依赖方
设计明确和完善的技术方案
制定详细的项目计划,锁定资源和里程碑节点并推动
全流程的测试验证
详细完备的上线计划
不可或缺的灰度验证
系统重构是一件耗时耗力的工做,但同时也是对自身综合能力的一个巨大挑战和锻炼,期间会遇到各类各样新的问题。但正是经过这些真实的实战,在不断的重构中发现自身的能力瓶颈,去学习和成长。
若是你也有相关经历和想法,也欢迎与我交流。