⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 知了一笑 「知了一笑」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

前言

开始想聊这个话题的时候,我是打算放弃的;因为这个话题涉及范围之广,内容之多, 让我犯怵;

近几年,待过两家公司;一家经历过重构,另一家也打算重构......

其实要下定决心,推翻重来,是一个很有勇气的决定;

归根结底,不到万不得已,谁想这么玩,谁愿意花费大精力去做这些脏活、累活;

所以究其原因,也只能说是一种综合因素吧,就像古话说的,天时、地利、人和;

至于为什么这是个很有勇气的决定,因为做重构这事的团队风险极高;上至业务高层,下至底层码农,都有可能一个不小心就被刀;

我曾经待过的一个团队,经历过一次重构,不过那是一次失败的经历;

开发预估了两个多月的时间,业务大佬决定,期间直接对新需求停滞;但是,最终上到生产的系统,老问题还在,然后又多了很多的新问题;

再后来啊,业务大佬和技术主管都被刀了;然后系统回退成老版本,重新承接新需求,并且开发时间被严重压缩,导致技术部门没日没夜的加班;

下面,我大致做了归档和整理,从这八个方面重新聊一聊系统重构;

原因

【重构原因】 其实上面也提到过,这是一个综合因素,可能是各种各样的因素叠加,造成一个不得不重构的结果;

下面,聊一聊我所碰到过的因素;要是没聊到的地方,也欢迎大家补充;

第一个,可以归结为系统的稳定性或可用性:不管吹得怎么天花乱坠,你的系统总要可用吧,所以说可用和稳定是一个系统的最基本要求。开发一个需求还要全服务停用吗?业务量增长,系统还能抗下所有吗?那么问题来了,怎么保证可用和稳定?

首先,如果你是单服务系统,那么为了保障可用和稳定,需要对服务进行扩展,加入更多的机器来分担系统的性能压力,也就是常说的“服务集群”;

但是,往往我们的系统并不是单服务的,也就是我们常说的分布式微服务;这种情况下,就不得不扯到分布式的三大法宝:限流、熔断、缓存。相信要实现这三个法宝,有很多成熟的框架和工具等,这里就不过多阐述了;

第二个,开发规范问题:这也考验了团队leader对底下成员的约束力,以及是否存在代码review的环节;

假设一个团队,leader确实没有对成员强制约束开发规范,更别提代码的review了;最终导致的问题就是,各写各的,每个人都有自己的一套开发习惯和开发规范;最终就是一个大写的“乱”;

这样日积月累,一代目埋坑,一代目撤,二代目上,二代目撤...... 系统不知道经了多少开发人员的手,最终,新入职的冤种某天需要排查一个产线问题,仿佛误入了代码的迷宫,深陷其中,无法自拔;

第三个,推翻原先核心业务,重立新规:这个要解释起来比较麻烦,也碰到会比较少,然而,我恰恰就遇到了;

某天,业务大佬宣布,目前做的这套业务规则要被废弃了,他们重新拟定了一套全新的规则,意味着先前不知道几手的代码已经失去意义了,几乎90%都需要重新写;

都到这一步了,那就大刀阔斧的改革吧;出事了,emmm,业务的锅;

会议

【重构会议】 既然已经确定要进行项目的重构,那么拉成员例会是必不可少的,不然就会一片迷茫,不知道干点啥;

第一次会议,由技术老大牵头,叫上业务负责人,技术主管,开发全员,测试,产品,运维;

一般,第一次这种全体会议是没有结果,但是又非常必要的;因为上级领导需要知晓整个重构涉及到的点,影响的方面,开发的时间,以及整体的技术方案,参与的人员,还有需要的资源等等;

第一次会议消化完后,很快就是第二次会议;这个会议也是由技术老大牵头,参会的主要是技术主管和参与开发的人员和运维;主要是对系统重构进行总体的技术选型,讨论用到的框架,组件,实现的方案,部署方案等等;

这个会议持续的时间会比较久,大家搬好小板凳,准备打持久战,可能从上班开始,到下班点了都还不能结束;当然,还是会有中场休息时间的;基本可以各抒己见,提出自己的意见和看法;主要是围绕做什么?怎么做?做多久?这三个疑问点讨论;

接下来,就是由技术主管组织的技术部内部会议了;由于之前已经确定好了统一的技术栈和实现方案,这一次讨论都是在此基础上展开的;

当然这一次会议可能需要更久的时间,因为涉及到是微服务的话,就避免不了一个问题:“服务拆分”;

大家都知道,微服务是需要根据业务去进行拆分的;那么问题来了,业务的划分界限是什么?为什么A业务部分要包含A1,A2,A3;其实这里就需要大家达成一个统一的认知,也是这次会议要达成的最终目的;

这次会议最后,服务已经划分完毕,但是缺少去维护这些服务的人;那就需要对开发人员进行分组,每组有对应的负责人,看人员情况而定(如果就两三个,甚至更少的开发,那就没必要分组什么的了,就是冤种,活都得你干),每组对N个服务的开发和维护;

分组过后,组长会进行简短的组内会议,这次其实就是派活会议,认领各自的工作;也会明确一点,大家要干些什么?谁干哪些?每个人干完都需要多久的时间?

最后,会整理一份排期计划,上面每个人的名字赫然在列,当然还有任务列表,工时,等等;

一切准备工作就绪,最后就是开干了,当然后面开发过程中还有一些不明确的点,也会临时组织一些会议,让大家达到统一的认知;

需求

【并行需求】 在系统重构的开发过程中,因为占用的周期会很长,投入的资源会比较多,所以难免还会遇到这样一个问题;

一天产品经理急冲冲的跑过来说,现在有一些需求很着急,需要将重构停一停,先把这些需求排期上线;至于为什么会这么着急,咱也不知道,咱也不敢问呀;

这时候就会面临一个恶心的局面,这次开发的需求,肯定需要在原先的服务中进行开发,那么重构的服务中,是不是还需要再写一遍?

王德发!说归说,骂归骂,办法总比困难多!这时候有一种方式,那就是派出个谈判专家,以三寸不烂之舌,动之以情,晓之以理;多说说技术难点、痛点,占用的资源,实现的难度等等,去打动他,说服他,让这次需求排在重构后面;

如果你成功了,那么恭喜你,这次重构任务,大家帮你分担一半

什么?产品经理不同意;那就没有办法,只能在改动老服务的基础上,记录调整的点,最终在写新服务业务代码的时候,将改动的点一并加上去;

最后在提测的时候,通知测试同学,这些需求相关的地方,在重构的新服务上线之前都需要重新走一遍流程;

当然,还有一种情况,就是像我之前说的,需求即伴随着重构;这种需求也肯定是大版本,开发周期较长;这种情况是最理想化的,只需要按部就班的进行开发即可;

选型

【技术选型】 这个话题,在上面的会议模块已经提到过了,这里再来详细的聊一聊;

当然,这次也仅限于比较流行的分布式微服务的技术选型;单服务的话,额,暂且不说吧;

其实也没什么好争议的,对于Java系统而言,无非就是spring全家桶无脑往上堆,服务器资源拉满;就像买汽车一样,越豪华的车,堆的配置越多,价格越贵,但是舒适性和操控性也就越好;

但是还需要考虑一些因素:成本、产品开源与否、技术社区活跃度、产品的成熟度、结合实际情况等等;

成本:时间成本、学习成本、金钱成本;

事情是人做的,代码也是人敲的;需要考虑团队成员擅长哪些技术栈,如果一门技术只有少部分人会或者都不会,那就需要结合开发时间,去斟酌需不需要用它,即便是热门的技术;

当然啦,如果老板愿意出钱,直接买各种付费的技术就行,我们只需要搭好基础的框架,然后往里面填充业务代码即可;如果不行,那就老老实实看看免费版,出了啥问题,只能自己排查去解决;

产品开源与否:说到这个,开源是关键,最香的词“开源免费”;

就像上面说的,一旦遇到了什么问题,其实有必要去看这门技术的源码去解决的;比如典型的SpringBoot自定义starter,需要定义“META-INF/spring.factories”文件,将文件中配置的类型信息加载到 Spring 容器;

一旦你闭源,那怎么玩?我怎么知道你内部怎么去实现的?只能看官方文档,看你自己吹什么什么功能;

技术社区活跃度:这个因素其实和产品开源可以归为一类,都讲的是一旦这个框架或产品出了问题,我们就可以去它的官方社区上找类似的解决方案;如果一个产品连社区都没,或者活跃度很低,那也就意味着你只能靠自己去解决了,那花的时间成本可就......

产品的成熟度:我们选用一款合适的产品,首先它得符合我们主要的需求,而后它才有其他额外的功能,这当然是最好的;因为一旦咱有一些特殊的业务场景,恰巧这些额外功能正好符合你这个场景,岂不美哉;这样就不必要接入额外的产品或组件了;

比如Redis,大家都不陌生;相信这个工具大家最多的是用它做缓存,它确实也是一款优秀的缓存工具;

但是,我之前在做OA软件时,碰到这么一个需求;用户申请一个会议,并且指定了参会人员,当在会议开始前15分钟,前5分钟,分别对参会人员发送一个提醒;

典型的发布、订阅的模式,然后正好Redis就有现成的功能,当时就是用了Redis的这个功能;

但Redis适合小型应用,如果是大型架构,相信还是会使用rabbitMQ或者kafka等更专业的MQ队列软件

结合实际情况:说到这个,有句老话说:“只有适合自己的才是最好”;在考虑需要用到哪项技术时,最理想的情况那就是直接付费购买,出啥问题,都是有技术支持滴,我出钱了,你得为我解决,但是,成本有限啊;

当然,有些人会说,技术选型这玩意,那是技术主管或者技术总监他们决定的,我们小码农只管拧好螺丝就行;是的,往往很多时候就是这样;但是,你不去大胆说出你的想法和意见,你怎么知道他们不会听取呢?格局打开,往上一层考虑就对了;

划分

【服务划分】 这块内容其实可聊的没多少,但是,也是最难的一块;

首先,划分;怎么分?按什么标准去分?分多少服务合适?分好之后怎么去管理?安排那些人去管理?等等一堆问题;

我也只能浅谈下我之前做过的一次划分经历,因为每个公司遇到业务和场景是不同的,最终得到的结果也是不同的;而且,服务并不是一次划分就能结束,它是一个持续不断的过程;

当时我们是全部参与这次重构的开发一起拉会议的,在此之前,已经确定好了技术栈,就是热门的springcloud全家桶;技术主管先是浅谈一波他自己的想法,然后让大家在他划分服务的基础上,提出自己的看法,并且每个人都要说,因为不可能想法都是一致的;

我记得比较清楚的是,他划分中,有个序列号的服务,就是这个服务专门去生成序列号;后面几个人都点名这个服务也包括我;其实当时想的是,我们的系统业务里没那么大,能省一个服务,也是省一点资源;最终大家统一共识,将这种场景写一个工具类去处理;

最终讨论后,达到的共识,也只是一个初步的共识;随着业务需求的增长,一部分业务服务必将变得臃肿,那时候第二波拆分也就随之到来了;

规范

【开发规范】 上面提到,造成系统重构的因素有很多,其中一个因素就是代码混乱,大家各有各的习惯和规范;

其实不难发现问题了,并不是每个人不知道开发规范,而是大家没有统一的一套规范,大家只是没有达成共识;

那就来解决这个问题,制定一套统一的标准,让大家都没有异议的一套规范;

这时候就不得不提到阿里的规范,现在广为流传的《阿里巴巴Java开发手册》,甚至基于这套规范,在IDEA上还有专门的检查插件可以安装;

当然,这样做确实省事,省去了leader对代码的review时间,包括我之前待过的一家公司,也确实就是这么做的;

其实仔细用过几次这款插件就会发现,有些检测其实大可不必,但是你又不得不去遵守它的规则,这时候反而徒增工作量;

遇到这种情况就需要看上级了,可以把这个疑问点反馈给他,或者反馈给身边其他的小伙伴,起码让他们GET到你要表达的点,引起大家的共鸣;相信越来越多的人反馈这些问题,上级不会坐视不管的;那么恭喜你,又去掉一块大家都认为不合适的规范,让你开发不绕弯路;

总之,总结起来就是一句话:没有标准,大家统一认可的就是标准犯规;

迁移

【数据、接口迁移】 谈到这个,除非开发的是0-1的项目,否则在平时开发中,或多或少都是会遇到的,而非重构会单独遇到的问题;

而我们在入职一家新公司,或者长期在一家公司干活,遇到0-1的项目几乎没有;如果有,那么你的运气很好;

就拿我上面的经历来说,业务推翻了之前的规则,重新制定了新的业务规则,那就不得不引出一个问题,之前的老数据怎么办?

这个时候就需要跟业务确认,这批老数据是否可以按照某种默认的规则,做统一的处理;或者说,直接废弃不管;一般来说,按前者处理的方案居多;

考虑到重构,往往你的表也是重新设计过的,也有可能是老表和新表公用,一般来说后者的情况居多;

其实,说白了就是在老表里面去修改数据,在新表里面去新增数据,而我们要考虑的是这批数据怎么去调整比较合适;

按我们之前的处理方式,如果涉及到的表,字段等较少,存储结构也颇为简单,是可以直接用SQL脚本去解决的;另外,还需要考虑如果表数据较多,SQL执行快慢的问题;

还有一种情况,要是涉及到的场景很多,处理的表也居多,可以考虑写程序去解决;比如,写个任务,去手动执行这个任务,处理掉这批数据;当然,也得考虑数据量的问题,如果数据量大,用多线程跑批,或者多服务跑批等方式;

下面,来谈谈重构中的接口迁移;

要保证一个原则,就是对接口的出入参保持不动;不然,前端直接飞过来跟你聊人生;

但是,老服务的接口很多怎么办?还能怎么办,分工处理;

上面提到过,我们是分组的方式,每个组负责一部分服务,分工比较明确,所以相对来说,也比较快;

大家整理文档,老接口的URL是什么,新接口的URL是什么,出参,入参能不变就不变;如果真的需要调整的,需要给出合理的解释,大家一致通过后,然后自行跟前端去battle;

有些老服务还会运行一段时间,所以并不是所有接口都会涉及到迁移,这种接口,那就相安无事;

在迁移的过程中,肯定也会遇到各种问题;比如举个简单的例子,之前老服务的分页插件使用的是PageHelper;而在新服务中,由于用上了Mybatis Plus框架,那自然也就用它框架自带的分页插件了;这个时候就涉及到修改,你只能自己内部消化了;

最后,当你觉得一切接口都迁移完毕的时候,别忘记对自己迁移的接口,进行一次自测;不要怕麻烦,这个自测对于后面的联调和提测流程,都可以免去很多不必要的问题,也可以大大提高团队的效率;

方案

【实现方案】 最后还有一点,业务场景的实现方案;这个听着有点抽象,其实在上面说的会议中,也会涉及;

不要以为这只是技术的决定,其实产品的参与也是很有必要的;你品,你细品;

我拿一个经典场景举例,A账户往B账户打钱,然后系统是微服务,很熟悉吧;

没错,就是分布式事务的一个经典场景,要保证数据的一致性,A被扣钱的同时,B要加上相应的钱;

分布式事务我不多说,相信很多同学随便一查,各种五花八门的方案;

但是这里我只讲一点,达到数据一致的时间节点;你是要什么时候保证这个数据达到一致,也就是说,强一致性还是最终一致性;

要求强一致必然是牺牲掉一部分性能的,要求最终一致性就有可能在一段时间内产生数据不一致的问题;

这个时候,就需要拉上产品经理,跟他阐述其中的缘由,说明技术方案上无法规避的点,让他做取舍;

有的时候,好的设计往往能规避技术上的难点,也可以节省开发的成本和时间;

所以,在讨论这块内容的时候,必须要有产品经理在场,提出设计上不合理之处;我们不能只局限于研究技术问题,其实换个角度,或者你质疑的点,就不存在了;

记得实习的时候,公司的老板讲过一句话“人人都是产品经理”,现在回过头一想,还真是那么回事;

大胆的提出自己的意见,做一个有自己主见的小码农;

总结

对于系统重构,我就以一句话结尾吧,这也是引用了一位大佬的话,“对公司现有的资源和战略妥协,在技术栈和方案之间取舍”。

文章目录
  1. 1. 前言
  2. 2. 原因
  3. 3. 会议
  4. 4. 需求
  5. 5. 选型
  6. 6. 划分
  7. 7. 规范
  8. 8. 迁移
  9. 9. 方案
  10. 10. 总结