谈谈软件系统重构

「头条关注【Java思享汇】,面试、各种技术栈、架构设计持续更新中~」

分享初衷:工作几年之后基本都会经历过大大小小的系统重构,笔者经历过单体应用拆分微服务的系统重构,数据异构,业务系统重构。借助此次分享把之前重构的经验进行系统化整理,希望可以形成一份系统重构的SOP。还有就是“以史为鉴”,将重构过程中总结的经验应用到新系统设计开发中,使新系统扩展性更强,可维护性更高。

什么是系统重构

重构背景

系统在经过多年需求迭代后基本上会面临一种“后有追兵,前有悬崖,进退两难”的境地。
后有追兵:面对维护了数年之久的大型遗留系统,我们到底改还是不改?不改,面对越来越多的需求变更,我们维护的成本越来越高,变更变得越来越困难;面对不断涌现的新技术,使我们的系统显得越来越丑陋与落后。
前有悬崖:原本运行得好好的系统,凑合一下还可以运行几年。一不小心改出问题了,系统可能立马就歇菜儿了,面对大量的用户投诉,需要到处救火,其中将要承受的巨大风险。
难道真的“熊掌和鱼不能兼得”吗?有没有一种方法,能够既保证我们系统可以技术改造,又能有效地避免改造过程的风险吗?有,那就是系统重构。

重构定义

在不改变软件外部行为的基础上,改变软件内部的结构,使其更加易于阅读、易于维护和易于变更。定义中非常关键的前提就是“不改变软件外部行为”,这个前提非常重要,它保证了我们在改造原有系统的同时,不会为原系统带来新的BUG,以确保改造的安全。比如我们重构了一个接口,那么我们重构完后的接口需要保证在入参相同的情况下,出参也是一致的。

重构分类

重构分类大致分为如下4类:
1.平台级别重构:针对整体平台的重构,比如阿里早期采用的LAMP(Linux Apache Mysql PHP)架构,后面整体迁移到了Java平台。
2.架构级别重构:通过架构的调整和重新设计,改善原有架构的不合理之处,比如我们对单体应用的拆分,引入微服务架构,进行系统拆分;引入缓存设计提升系统整体性能;引入消息中间件对系统进行异步解耦;引入分布式事务框架解决系统间分布式事务场景等。
3.代码级别重构:使用设计模式、封装继承、抽象代码,使得代码扩展性、易读性更好,执行效率更高。如审核系统在重构过程中应用了模版方法、策略模式、工厂模式、观察者模式等。
4.数据层面重构:1.针对数据量较大的业务,采用分库分表存储;2.数据查询分析等实时性要求不高的场景,将实时计算切换为离线计算,如业绩重构过程中,将之前依赖Redjob进行多表Join实时计算的逻辑迁移到Rugal通过Hive进行离线计算,慢查询由最慢超过20S优化到1S内完成,性能和代码可读性有了极大提升;3.利用OLAP数据库,如Doris或者ES替代直接查询Mysql场景提升查询性能。

为什么要系统重构

找痛点,重构的原因是多种多样的,但无外乎以下这几个:
1.业务增长太快了,老系统之前的设计无法承载过高的请求压力(性能差)。
2.系统稳定性差,漏洞很多,有时候经常要靠重启解决。
3.经过多年的发展,充斥着大量的不合理的代码,杂乱,难以维护。
4.业务需求满足起来很困难或无法满足 。
5.老系统使用的语言无人维护了。

如何进行系统重构

重构的过程是复杂的,我们分步骤按照顺序总结。

说服业务方

业务最关注的是什么时候上线他们的新需求,满足当初定下的OKR。而会觉得系统内部的改造和他们关系不大,一般他们不会主动来推动进行系统重构的。而重构又是一个费时费力的工作,还有可能导致现在的业务需求迭代暂停,影响业务的开展。所以,我们需要极力的说服业务,陈述利弊。不去重构有可能导致系统瘫痪,需求迭代慢等。
而系统重构后的好处就太多了,比如,页面交互更加友好,可以快速满足新需求上线,趁着重构的过程可以解决现有不合理的业务流程,还能使系统高可用、高性能等。

确定重构目标

本次重构达成的最终目的是什么?
●引入合理的架构
●新的技术和框架
●提高代码质量
●系统预期的性能指标

老系统熟悉与梳理

重构是基于老系统,而不能脱离老系统,这就需要对老系统了如指掌,特别是关键的环节步骤不能有任何差错。
●旧系统资料和信息收集
●业务流程梳理
●旧系统关键代码Review
●疑难点及时沟通

任务优先级拆分和时间预估

系统重构往往是复杂、繁琐的,涉及到功能点很多,我们不可能“一口吃完”,所以重构过程中需要对任务进行拆分,确定好优先级,并做好重构里程碑(阶段时间点),申请到需要的资源(产研、测试等),采用“小步快跑”方式进行。

系统整体设计

技术选型

一般老系统技术栈都相对落后,在系统重构的过程中,可以结合公司当前的技术栈甚至行业流行的技术栈进行技术选型,选择系统应用后性能更好、使用更便捷,接入成本更低的技术来满足我们的需求。列举几个之前选型的case:
注册中心选型
在这里插入图片描述
背景:在单体应用拆分微服务过程中,拆分出来的微服务多达十几个,其中涉及到解决前端跨域、服务鉴权、请求审计日志统一记录、接口限流熔断等问题,因此搭建应用网关来解决上述问题,网关在做服务发现过程中涉及到使用注册中心,基于此对业内注册中心进行技术选型,最终选择了通过Nacos来作为注册中心使用,选择主要原因是注册中心在一致性协议上选择AP模型,同时Nacos支持配置中心,还有比较友好的管理界面。
消息中间件选型
在这里插入图片描述
背景:也是在单体应用拆分微服务过程中,需要借助MQ来进行服务间的异步解耦和流量削峰填谷,其中重点关注指标为吞吐量、开发语言和实效性,最终选择了RocketMQ,比较重要的原因是之前负责的业务为财务方向,存在分布式事务的场景,因此正好借助RocketMQ的事务消息来处理分布式事务。
OLAP引擎选型
在这里插入图片描述
背景:最近在审核系统重构过程中,需要将物料审核数据由Mongo(1608万)迁移到Mysql数据库中,但是物料审核列表查询条件有很多,数据量也很大,不可能将这些查询字段都建索引,计划将物料数据实时同步到OLAP数据库一份,目前也是在调研选择Doris或者是ES,如果Doris实时性满足的话,优先选择Doris,因为其查询语法和Mysql基本一致,后期开发维护成本非常低。

数据迁移

在系统重构过程中,如果涉及到对数据进行处理,一般分为以下3种情况:
1.对历史数据库中的表按照业务进行拆分(单体->微服务)该场景对表结构没有太大改动,基本上是将数据由之前的老库迁移到业务划分后的新库,但在业务划分阶段需要重点考虑数据表迁移后造成的分布式事务问题。
2.为了提升系统性能,将数据同步到OLAP引擎数据库中,该场景也不会对表结构有太大改动,核心点是关注数据变更的实时性和数据同步的check工作。
3.数据库重构,比如数据由Mongo迁移到Mysql;数据容量变大需要分库分表处理。以上2中场景是相对比较复杂的,因为要保证系统运行的稳定性,支持可回滚,需要进行数据双写,所以在重构前期需要考虑以下几点:
a.双写过程中保证数据不丢失(监听binlog同步支持重置位点或接口回刷)
b.如果监听binlog同步数据,需要保证消费数据的顺序性
c.双写过程中保证数据不重复(找到数据联合唯一索引)
d.数据双写需要对数据进行染色,避免数据双写死循环问题
e.分库分表需要确定好数据分片策略(分片键)
f.做好数据校验和数据修复方案

流程图梳理

重构过程中对系统间交互流程的梳理是很重要的,这样能够清晰、直观的了解整个系统。画流程图可以使用processon网页版即可。以物料审核入库流程为例:
在这里插入图片描述

UML设计

下面是代码(类、接口)层面的设计,主要还是通过UML来展现,画图也可借助processon进行,UML的价值是便于研发内部沟通和理解。帮助沟通设计思想,理解系统或业务流程。特别在新系统中运用一些设计模式来增强系统扩展性的时候,显得会更加直观。以审核系统重构为例:
在这里插入图片描述

系统灰度和回滚方案

系统重构进行切换是一个非常重要的一个环节,在最初做设计的时候就要考虑到如何进行切换,而且这个灰度设计需要贯穿重构始终,避免因为切换方案引起服务不可用或是引入系统BUG。
平时工作中比较常用的方式是通过配置中心设置灰度开关进行系统切换,这种方式优势:成本比较低,回退速度比较快。比如我们以城市编码作为灰度开关参数,重构系统上线后可以先打开一个城市,进行小部分流量灰度,运行一段时间没问题再逐步打开其他城市灰度,这样如果出现问题,范围可控,并且可以及时通过灰度开关进行回滚,保证系统高可用。不足:对代码有侵入性。
其他灰度方式相对复杂,比如:
蓝绿发布,需要两套服务集群,核心是利用负载均衡组件进行分流,和上面说的灰度开关原理类似。优势:升级切换和回退速度非常快,不足:需要两倍机器资源。
金丝雀发布,只需要一套服务集群,发布时先发布一小部分机器进行验证,确认符合预期再逐步将剩余机器更新。优势:相对蓝绿发布只需要一套集群,不足:发布过程中出现问题需要一定回滚时间。

数据监控和报警

系统重构后我们需要对新系统或者新功能的运行情况进行整体把控,可以配置一些指标看板或者通知告警,一旦不符合预期,我们能够及时接收到通知进行快速干预。不只是系统重构,在日常开发中也需要增加监控和告警,对异常情况进行通知。

技术方案评审

正是因为系统重构的复杂性,所以通过技术方案评审可以解决以下问题:
●发现重构系统在功能、逻辑、实现上不合理的地方。
●确认新系统重构过程中是否符合当前的开发规范。
●便于进行项目管理,方便日后查阅。

系统联调测试

联调测试也是系统重构非常重要的一环,核心有以下3点:
1.系统检查:对比新旧系统的业务接口出入参,对比新旧系统新产生的数据是否符合预期。
2.联调:包含内部、外部的系统联调。并约定好上线先后顺序和时间点。
3.测试:主要包含功能性测试,性能测试(核心高QPS接口需要进行压测),针对非常核心的系统可以借助工具录制线上流量,记录接口出入参数,进行流量回放测试。

上线清单

在上线之前做好准备工作,将核心check事项罗列清楚,涉及到多方一起上线,需要协调好上线顺序和时间点,做好数据库表、配置中心等初始化工作。举例如下:在这里插入图片描述

复盘文档

重构系统上线后,特别是上线效果没有达到预期的情况,需要进行复盘,主要有3方面价值,1.发现问题;2.总结经验;3.促进成长。举例如下:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/9240.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

总结819

学习目标: 4月(复习完高数18讲内容,背诵21篇短文,熟词僻义300词基础词) 第二周: 学习内容: 暴力英语:早上背诵《think different》记150词,默写了两篇文章&#xff0c…

Java中的Iterator底层原理实现

两个抽象方法 Iterator主要有两个抽象方法,让子类实现。 hasNext()用来判断还有没有数据可供访问。next()方法用于访问集合的下一个数据。 这两个方法不像List的get()那样依赖索引获取数据,也不像Queue的poll方法那样依赖特定规则获取数据。 迭代器的…

3月更新 | Visual Studio Code Python

我们很高兴地宣布,2023年3月版 Visual Studio Code Python 和 Jupyter 扩展现已推出! 此版本包括以下改进: 后退按钮和取消功能添加到创建环境命令默认情况下,Python 扩展不再附带 isortJupyter 笔记本中内核选择的改进Python P…

代码随想录Day49

今天继续学习动规解决完全背包问题。 322.零钱兑换 给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。 计算并返回可以凑成总金额所需的最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,…

java 线段树

线段树是一种二叉搜索树,什么叫做二叉搜索树,首先满足二叉树,每个结点度小于等于二,即每个结点最多有两颗子树,何为搜索,我们要知道,线段树的每个结点都存储了一个区间,也可以理解成…

【JavaWeb】8—过滤器

⭐⭐⭐⭐⭐⭐ Github主页👉https://github.com/A-BigTree 笔记链接👉https://github.com/A-BigTree/Code_Learning ⭐⭐⭐⭐⭐⭐ 如果可以,麻烦各位看官顺手点个star~😊 如果文章对你有所帮助,可以点赞👍…

C语言中宏的一些高级用法举例

C语言中宏的一些高级用法 文章目录C语言中宏的一些高级用法1.字符串化2.标记的拼接3.宏的嵌套替换多条语句防止头文件被重复包含宏的可变参数应用方式1方式2方式34.常用宏宏和函数的区别1.字符串化 #include <stdio.h> #include <stdbool.h> #include <string.…

测试开发常问面试题

Postman Postman实现接口关联 步骤 通过正则表达式或则JSON提取器取值的方式&#xff0c;提取需要的参数。将参数设置为全局变量或则环境变量。在之后接口中&#xff0c;通过{{全局变量/环境变量}}代替要替换的参数值。 - JSON提取器方式 var jsonData JSON.parse(respons…

【Spring6】数据校验:Validation

10、数据校验&#xff1a;Validation 10.1、Spring Validation概述 在开发中&#xff0c;我们经常遇到参数校验的需求&#xff0c;比如用户注册的时候&#xff0c;要校验用户名不能为空、用户名长度不超过20个字符、手机号是合法的手机号格式等等。如果使用普通方式&#xff0c…

TenserRT(三)PYTORCH 转 ONNX 详解

第三章&#xff1a;PyTorch 转 ONNX 详解 — mmdeploy 0.12.0 文档 torch.onnx — PyTorch 2.0 documentation torch.onnx.export 细解 计算图导出方法 TorchScript是一种序列化和优化PyTorch模型的格式&#xff0c;将torch.nn.Module模型转换为TorchScript的torch.jit.Scr…

unicloud 模糊查询解决方案

序 1、where和aggregate的模糊搜索 2、第一种是“你好”去匹配“你好啊大家” 3、第二种是“家啊”去匹配“啊&#xff01;你家呢” 只要有1个字匹配就匹配 4、第三种是“家啊”去匹配“啊&#xff01;你家呢” 必须有“家”又有“啊”才匹配” 想看效果&#xff0c;大家可以自…

ROBOGUIDE教程:FANUC机器人摆焊焊接功能介绍与虚拟仿真操作方法

目录 摆焊功能简介 摆焊指令介绍 摆焊功能设置 摆焊条件设置 机器人摆焊示教编程 仿真运行 摆焊功能简介 使用FANCU机器人进行弧焊焊接时&#xff0c;也可以实现摆动焊接&#xff08;简称摆焊&#xff09;。 摆焊功能是在机器人弧焊焊接时&#xff0c;焊枪面对焊接方向…

面试字节,三面HR天坑,想不到自己也会阴沟里翻船....

阎王易见&#xff0c;小鬼难缠。我一直相信这个世界上好人居多&#xff0c;但是也没想到自己也会在阴沟里翻船。我感觉自己被字节跳动的HR坑了。 在这里&#xff0c;我只想告诫大家&#xff0c;offer一定要拿到自己的手里才是真的&#xff0c;口头offer都是不牢靠的&#xff0…

【CE】Mac下的CE教程Tutorial:进阶篇(第8关:多级指针)

▒ 目录 ▒&#x1f6eb; 导读开发环境1️⃣ 第8关&#xff1a;多级指针翻译操作验证其它方案&#x1f6ec; 文章小结&#x1f4d6; 参考资料&#x1f6eb; 导读 开发环境 版本号描述文章日期2023-03-操作系统MacOS Big Sur 11.5Cheat Engine7.4.3 1️⃣ 第8关&#xff1a;多…

MySQL数据库中的函数怎样使用?

函数 是指一段可以直接被另一段程序调用的程序或代码。 也就意味着&#xff0c;这一段程序或代码在MySQL中已经给我们提供了&#xff0c;我们要做的就是在合适的业务场景调用对应的函数完成对应的业务需求即可。 那么&#xff0c;函数到底在哪儿使用呢? 我们先来看两个场景&a…

【FPGA-Spirit_V2】基于FPGA的循迹小车-小精灵V2开发板

&#x1f389;欢迎来到FPGA专栏~基于FPGA的循迹小车 ☆* o(≧▽≦)o *☆嗨~我是小夏与酒&#x1f379; ✨博客主页&#xff1a;小夏与酒的博客 &#x1f388;该系列文章专栏&#xff1a;FPGA学习之旅 文章作者技术和水平有限&#xff0c;如果文中出现错误&#xff0c;希望大家能…

Android下载apk并安装apk(用于软件版本升级用途)

软件版本更新是每个应用必不可少的功能&#xff0c;基本实现方案是请求服务器最新的版本号与本地的版本号对比&#xff0c;有新版本则下载apk并执行安装。请求服务器版本号与本地对比很容易&#xff0c;本文就不过多讲解&#xff0c;主要讲解下载apk到安装apk的内容。 一、所需…

Socket套接字编程(实现TCP和UDP的通信)

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了 博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点!人生格言&#xff1a;当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友一起加油喔&#x1f9be;&am…

(链表)移除链表元素(双指针法)

文章目录前言&#xff1a;问题描述&#xff1a;解题思路&#xff08;双指针法&#xff09;&#xff1a;代码实现&#xff1a;总结&#xff1a;前言&#xff1a; 此篇是针对链表的经典练习题。 问题描述&#xff1a; 给你一个链表的头节点 head 和一个整数 val &#xff0c;请…

Js:apply/call/bind、作用域/闭包、this指向(普通,箭头,JS/Vue的this)

目录1、apply/call/bind2、作用域、作用域链和闭包核心1、预处理&#xff08;解析阶段&#xff09;——JS执行“代码段”之前2、生成执行上下文环境——对代码段(全局/函数体)进行处理3、执行上下文环境小结4、多个执行上下文环境5、作用域6、作用域和执行上下文7、从【自由变量…