关于系统重构实践的一些思考与总结

文章目录

  • 一、前言
  • 二、系统重构的范式
    • 1.明确目标和背景
    • 2.兼容屏蔽对上层的影响
    • 3.设计灰度迁移方案
      • 3.1 灰度策略
      • 3.2 灰度过程设计
        • 3.2.1 case1 业务逻辑变更
        • 3.2.2 case2 底层数据变更(数据平滑迁移)
        • 3.2.3 case3 在途新旧流程兼容
        • 3.2.4 case4 接口变更
        • 3.2.5 case5 状态机兼容
  • 三、系统重构过程中的风险控制
    • 3.1 设计阶段
      • 3.1.1 流量梳理
      • 3.1.2 异常脏数据梳理
    • 3.2 开发阶段
      • 3.2.1 单元测试
      • 3.2.2 代码CR
    • 3.3 测试&预发线上回归阶段
      • 3.3.1 线上线下流量回放&流量验证
      • 3.3.2 核心主链路回归&异常case回归
      • 3.3.3 BCP对账,反向验证数据结果
    • 3.4 开城中
      • 3.4.1 小批量验证
      • 3.4.2 数据迁移过程中的反向校验sql
      • 3.4.3 及时回滚
    • 3.5 开城后
      • 3.5.1 及时响应机制
      • 3.5.2 BCP对账
    • 3.6.开城后收尾
  • 四、回顾与反思

一、前言

前段时间一个老的系统重构项目上线,在这个过程中出现了很多问题,遂想针对在这个过程中遇到的一些问题和实践,记录总结一套系统重构的范式,并回顾反思在这个过程是否有哪些值得改进的地方。

注:这里系统重构的定义,不仅是技术上的重构,更是产品逻辑上的重构。

二、系统重构的范式

系统的重构都需要考虑对原有系统的影响,不会另起一个项目,从头写到尾,还把上下游做相应的修改,这种做法虽然"彻底干净",但是成本太大,所以本文暂不讨论这种方案。

一般来说,老系统的重构都需要做到至少接口层面的兼容,这样可以保证系统的重构不会逸散到系统之外,对于上下游而言重构是无感的。

1.明确目标和背景

对于系统重构,需要最小化影响面,而这个前提就是需要我们明确我们的目标是什么,是字段迁移,还是业务模型的变动,亦或者是业务逻辑的变动。

  • 底层存储变动?
  • 数据模型变动?
  • 业务逻辑变动?
  • 迁移过程中是否不可用?

在这里插入图片描述

在这个阶段,需要梳理出你的改动对于整个项目的影响,比如影响到哪块业务逻辑,哪些接口。

如果是对整个项目大的重构,需要梳理现有系统的流量入口。

2.兼容屏蔽对上层的影响

核心思路就是尽量在最底层去屏蔽这些影响。

比如底层存储修改,那么是否能在dao层就去兼容,让上层的调用和之前一样。

不同语言有不同的技巧,核心就是方法增强,在原先逻辑上增加附加兼容逻辑。

对于Java而言,可以用切面、框架内注册处理器(如filter),亦或者重写get/set方法等等,

对于Go而言,也是类似,虽然没有切面,但也能利用结构体嵌入、接口和组合的方式实现类似的效果。

这样的好处就是

  • 风险可控
  • 修改成本低

坏处也有:

  • 逻辑里夹杂着特殊逻辑,代码复杂度会提升

3.设计灰度迁移方案

系统重构必然存在新老系统切换的过程,尤其是如果需要灰度验证的话,那么新老系统切换的过程会很长,我们就需要考虑新老系统同时存在的影响。

3.1 灰度策略

系统重构往往需要灰度验证,出发点往往有以下几种:

  • 控制影响面

小范围验证,避免新系统的问题对业务的影响过大

  • 业务诉求

这点在B端业务里尤其常见,例如每开一些城市前,业务同学都需要和商户侧有对应的宣贯,周知到商户

灰度的策略也有很多,常见的有

by城市、by商户、by流量等,

这个决于业务倾向,这里面着重需要考虑的点在于

  • 选用这种灰度是否会影响数据不一致
  • 是否对业务有影响

常见的B端业务主要都是by城市维度去进行开城。

3.2 灰度过程设计

3.2.1 case1 业务逻辑变更

对于业务逻辑变更的场景,比较简单,我们往往是封装对应的开关逻辑,命中则走新逻辑,否则走老逻辑。

在这里插入图片描述

需要注意的就是在代码中的组织尽量封装成方法/函数,方便后续灰度全量时进行无用代码删除,降低代码复杂度。

3.2.2 case2 底层数据变更(数据平滑迁移)

这里的变更可能是同一张数据表字段使用迁移(典型场景就是敏感字段加密),也可能是不同表的数据。

双写方案
在这里插入图片描述

1.开启双写(老->新)

目的:让增量的数据也能同步更新到的新数据,

2.数据迁移脚本

目的:让存量的数据刷到新的数据中

经历第二步骤后,旧数据已经全量同步到新数据中。

3.开城(切读&写)

开启开城开关,流量入口开始从旧逻辑切换到新逻辑,新逻辑会读&写新数据,同时会双写老数据

双写老数据目的:切换开关后出现的数据也能同步老数据,如果出现问题,回滚开关时可以实现无损回滚

4.关闭双写

目的:双写毕竟还是冗余操作,当全量开城时,我们需要将这部分逻辑给去了,避免影响系统性能

5.下线老逻辑代码

目的:减少无用代码,降低代码复杂度

以上是较为完整的迁移过程,实际上可以根据自己的实际场景对步骤做一定程度上的简化。

比如我是典型的B端业务,实际迁移过程无需那么平滑,甚至允许一定时间内停止对外提供服务(比如停服维护),那么我们可以无需第一步的双写(老->新),只需在开城后再增量刷一遍数据即可。

在这里插入图片描述

3.2.3 case3 在途新旧流程兼容

旧系统中有一些流程结束并不是简单的通过开关就能进行新旧逻辑的切换,

比如B端的审批流,我们就需要考虑在切换新系统后,兼容老的审批回调。

这里主要有两种思路:

  • 老回调逻辑生效,双写新逻辑
  • 老回调逻辑失效,新逻辑重新推审

具体方案得根据具体业务而定。

3.2.4 case4 接口变更

原有接口如果无法承载新业务逻辑的交互,如果不能确定影响范围,那么只能在原有请求字段中新增字段,不允许修改和删减,保证向后兼容。

有一种case比较特殊,就是专门给前端交互用的接口,如果新的业务形态下,页面的交互已经变化,那么可以废弃原来的接口,另起接口去支持新的功能交互。

3.2.5 case5 状态机兼容

状态机变化需要明确新老状态之间的映射。其本质就是case2底层数据变更的思路。

三、系统重构过程中的风险控制

3.1 设计阶段

3.1.1 流量梳理

在系统重构,尤其是老系统重构的过程中,流量梳理是设计阶段必须要做的一件事情。

因为老系统往往存在大量无用逻辑,以及各种各样的复杂业务接口。在设计之前我们必须要梳理出当前系统对外部开放在使用的能力。

这里列举一些常见的流量入口:

  • http(VIP、域名)
  • rpc(如thrift&grpc、dubbo、kepler等)

有了范围,我们接下来做的就是考虑我们本次修改可能会对这些接口造成哪些影响。

3.1.2 异常脏数据梳理

线上脏数据梳理是非常容易被我们忽视的,而这造成的结果往往就是数据迁移/开城后会出现一些特殊case,严重的会引发事故。

在我们不熟悉旧系统迭代的情况下,我们很难考虑到线上数据有多少是在”我们意料的规则之外“的。

”我以为这个数据是这样的,可是线上有脏数据导致…“

”啊,还有数据是这样的?“

这往往是我们事后才发现的问题。

我们应该把这块前置,在设计阶段就要梳理出这个表/数据的用法,以及是否存在一些出乎我们意料的数据,尤其要多对比新旧逻辑下,这些数据可能造成的影响。

也许这个脏数据在老系统规则下能正常运行,但是在新系统规则下它就是会出问题!

我们需要多写sql,去统计当前线上库的数据分布,并去分析新旧逻辑下的影响。

3.2 开发阶段

3.2.1 单元测试

好的单测是可复用的,拥有完整的mock和断言能力。

系统重构设计修改的方法最好是要有对应的单测case。

3.2.2 代码CR

代码CR可以规避部分因代码bug引发的错误。

但是实际项目开发中,因为排期紧、代码改动量过大等原因,大型系统重构的实践效果往往不是很理想。

3.3 测试&预发线上回归阶段

3.3.1 线上线下流量回放&流量验证

流量回放是用于验证老接口是否发生变化的最好工具。

其核心是用线上的流量到线下测试环境进行流量验证,对比前后差异这里依赖基础设施的建设。

如果没有完善的流量回放工具,则可以写个脚本,用来对比流量的接口。

不过对于缺失的mock能力,可以通过copy线上的数据到线下来进行模拟。

3.3.2 核心主链路回归&异常case回归

核心主链路必须要回归,避免新系统对线上核心流程造成影响

3.3.3 BCP对账,反向验证数据结果

针对核心业务流程,以及数据迁移脚本中的数据对比,都可以用BCP对账来进行反向校验。
这里主要针对一些核心的流程去写对账,对账逻辑可以依据一些特定的数据规则来写,从结果层面去验证数据的正确性。

常见实现的方式有:

  • binlog+sql
  • binlog+延迟mq+sql
  • 定时任务+hive

通过这样写反向的sql校验,可以低成本的去对结果进行兜底,及时感知到异常case。

需要注意的是,这种BCP对账的sql要注意sql耗时时间,虽然是离线库,但是性能太差也是不可接受的,运行时间过长的建议采用定时任务+hive表的方式。

3.4 开城中

3.4.1 小批量验证

正式开城时,我们必须要有小规模验证的过程。

比如数据迁移时,我针对单个商户进行迁移,然后回归具体的case,如果没有问题再逐步放量。

3.4.2 数据迁移过程中的反向校验sql

数据迁移脚本运行后,我们如何确定我们的执行结果是正确的,除了打印必要的日志和结果通知,还有准备对应的反向校验的sql,来验证我本次刷数是符合预期的。

BCP的报警其实也是同理(不过在大批量数据变更的情况下,BCP报警可能会出现延迟)。

3.4.3 及时回滚

如果出现问题及时回归,按照预期设定的回滚预案进行回滚。

具体的回滚策略如下:

  • 代码回滚
  • 灰度开关
  • 数据库刷数回退(有的公司可能会支持这种能力)

3.5 开城后

3.5.1 及时响应机制

开城后,可以建立业产研开城群,方便开城后问题的沟通,及时跟进和止损。

3.5.2 BCP对账

同前面,略

3.6.开城后收尾

多次的重构,会留下很多无用代码,徒增代码复杂度,良好的习惯就是在新系统全部开城后,删除无用代码。

四、回顾与反思

真实项目中的挑战远不止这些,还有诸如对老系统不熟悉,排期压力大等挑战,这些buff叠加进而导致老系统重构中的风险不可控。

在最近的重构需求中,我大致统计了下本次重构记录的19个线上问题,其原因分布如下

  • 未考虑到的线上脏数据 4
  • 代码bug漏出 13
  • 产品逻辑&技术方案问题 2

主要原因:

  • 旧系统逻辑产研了解都较少,这个虽然不是直接原因,但确实很大程度影响了项目风险控制
  • 无一个有效的流量回放&流量对比的工具来控制影响面
  • 测试阶段回归不全面,导致bug漏出
  • 设计阶段缺少统计线上数据分布,忽略了”脏数据“在新系统逻辑下的影响

大家说的脏数据,其实不单单是在原先系统中的不合规/不该存在的数据,也有一些是因为历史迭代而产生的”正常数据“,这些数据可能在老系统运行正常,但是在新逻辑下就不一定适用

对于及时感知线上问题方面,其中BCP报警起了不可或缺的作用,上述问题中有7个是通过BCP报警发现,由此可见BCP对账对于系统重构的重要性。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Microsoft Power BI:融合 AI 的文本分析

Microsoft Power BI 是微软推出的一款功能强大的商业智能工具,旨在帮助用户从各种数据源中提取、分析和可视化数据,以支持业务决策和洞察。以下是关于 Power BI 的深度介绍: 1. 核心功能与特点 Power BI 提供了全面的数据分析和可视化功能&…

【机器学习】自定义数据集 ,使用朴素贝叶斯对其进行分类

一、贝叶斯原理 贝叶斯算法是基于贝叶斯公式的,其公式为: 其中叫做先验概率,叫做条件概率,叫做观察概率,叫做后验概率,也是我们求解的结果,通过比较后验概率的大小,将后验概率最大的…

AMS仿真方法

1. 准备好verilog文件。并且准备一份.vc文件,将所有的verilog file的路径全部写在里面。 2. 将verilog顶层导入到virtuoso中: 注意.v只要引入顶层即可。不需要全部引入。实际上顶层里面只要包含端口即可,即便是空的也没事。 引入时会报warni…

OpenAI o3-mini全面解析:最新免费推理模型重磅发布

引言 2025年1月31日,OpenAI重磅发布全新推理模型o3-mini。这款模型作为OpenAI推理系列的最新突破,不仅在性能和性价比方面实现跨越式提升,更是首次全面开放免费使用。这一重大举措彰显了OpenAI在人工智能技术普及和成本优化领域的创新决心。…

文件读写操作

写入文本文件 #include <iostream> #include <fstream>//ofstream类需要包含的头文件 using namespace std;void test01() {//1、包含头文件 fstream//2、创建流对象ofstream fout;/*3、指定打开方式&#xff1a;1.ios::out、ios::trunc 清除文件内容后打开2.ios:…

TensorFlow 示例摄氏度到华氏度的转换(一)

TensorFlow 实现神经网络模型来进行摄氏度到华氏度的转换&#xff0c;可以将其作为一个回归问题来处理。我们可以通过神经网络来拟合这个简单的转换公式。 1. 数据准备与预处理 2. 构建模型 3. 编译模型 4. 训练模型 5. 评估模型 6. 模型应用与预测 7. 保存与加载模型 …

99.24 金融难点通俗解释:MLF(中期借贷便利)vs LPR(贷款市场报价利率)

目录 0. 承前1. 什么是MLF&#xff1f;1.1 专业解释1.2 通俗解释1.3 MLF的三个关键点&#xff1a; 2. 什么是LPR&#xff1f;2.1 专业解释2.2 通俗解释2.3 LPR的三个关键点&#xff1a; 3. MLF和LPR的关系4. 传导机制4.1 第一步&#xff1a;央行调整MLF4.2 第二步&#xff1a;银…

此虚拟机的处理器所支持的功能不同于保存虚拟机状态的虚拟机的处理器所支持的功能

1.问题&#xff1a;今天记录下自己曾经遇到的一个问题&#xff0c;就是复制别人虚拟机时弹出来的一个报错&#xff1a; 如图&#xff0c;根本原因就在于虚拟机版本的问题&#xff0c;无法对应的上&#xff0c;所以必须升级虚拟机。 2.问题解决&#xff1a; 1.直接点击放弃,此时…

Linux命令入门

Linux命令入门 ls命令 ls命令的作用是列出目录下的内容&#xff0c;语法细节如下: 1s[-a -l -h] [Linux路径] -a -l -h是可选的选项 Linux路径是此命令可选的参数 当不使用选项和参数,直接使用ls命令本体,表示:以平铺形式,列出当前工作目录下的内容 ls命令的选项 -a -a选项&a…

10 Flink CDC

10 Flink CDC 1. CDC是什么2. CDC 的种类3. 传统CDC与Flink CDC对比4. Flink-CDC 案例5. Flink SQL 方式的案例 1. CDC是什么 CDC 是 Change Data Capture&#xff08;变更数据获取&#xff09;的简称。核心思想是&#xff0c;监测并捕获数据库的变动&#xff08;包括数据或数…

【Redis】set 和 zset 类型的介绍和常用命令

1. set 1.1 介绍 set 类型和 list 不同的是&#xff0c;存储的元素是无序的&#xff0c;并且元素不允许重复&#xff0c;Redis 除了支持集合内的增删查改操作&#xff0c;还支持多个集合取交集&#xff0c;并集&#xff0c;差集 1.2 常用命令 命令 介绍 时间复杂度 sadd …

happytime

happytime 一、查壳 无壳&#xff0c;64位 二、IDA分析 1.main 2.cry函数 总体&#xff1a;是魔改的XXTEA加密 在main中可以看到被加密且分段的flag在最后的循环中与V6进行比较&#xff0c;刚好和上面v6数组相同。 所以毫无疑问密文是v6. 而与flag一起进入加密函数的v5就…

【etcd】二进制安装etcd

由于生产服务器不能使用yum 安装 etcd ,或者 安装的etcd 版本比较老&#xff0c;这里介绍一个使用二进制安装的方式。 根据安装文档编写一个下载脚本即可 &#xff1a; 指定 etcd 的版本 提供了两个下载地址 一个 Google 一个 Github&#xff0c; 不过都需要外网 注释掉删除保…

MediaPipe与YOLO已训练模型实现可视化人脸和手势关键点检测

项目首页 - ZiTai_YOLOV11:基于前沿的 MediaPipe 技术与先进的 YOLOv11 预测试模型&#xff0c;精心打造一款强大的实时检测应用。该应用无缝连接摄像头&#xff0c;精准捕捉画面&#xff0c;能即时实现人脸检测、手势识别以及骨骼关键点检测&#xff0c;将检测结果实时、直观地…

学术总结Ai Agent中firecrawl(大模型爬虫平台)的超简单的docker安装方式教程

之前开源了学术总结ai agent&#xff0c;但是对非计算机专业来说&#xff0c;门槛有点高&#xff0c;再加上docker hub镜像被屏蔽&#xff0c;更是不容易上手啊。也有考虑用dify或者扣子去复刻一个&#xff0c;但是从专业用户的角度出发通过界面来拖拽配置实在是不高效&#xf…

交易股指期货有什么技巧吗?

交易股指期货有啥窍门呢&#xff1f;其实啊&#xff0c;追涨杀跌这招&#xff0c;虽然能挣点小钱&#xff0c;但风险也不小&#xff0c;一不小心就可能亏大了。我说的追涨杀跌&#xff0c;不是那种天天追着价格跑的小打小闹&#xff0c;而是要看大趋势&#xff0c;做宏观操作。…

Java线程认识和Object的一些方法ObjectMonitor

专栏系列文章地址&#xff1a;https://blog.csdn.net/qq_26437925/article/details/145290162 本文目标&#xff1a; 要对Java线程有整体了解&#xff0c;深入认识到里面的一些方法和Object对象方法的区别。认识到Java对象的ObjectMonitor&#xff0c;这有助于后面的Synchron…

linux 函数 sem_init () 信号量、sem_destroy()

&#xff08;1&#xff09; &#xff08;2&#xff09; 代码举例&#xff1a; #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #include <unistd.h>sem_t semaphore;void* thread_function(void* arg) …

ComfyUI中For Loop的使用

研究了半天&#xff0c;终于弄明白了如何使用For Loop。 1、在For中节点&#xff0c;必须有输出连接到For Loop End的initial_value点&#xff0c;才能确保节点执行完毕后才 进入下一轮循环&#xff0c;否则&#xff0c;可能导致节点没执行完&#xff0c;就进入下一个循环了。…

UbuntuWindows双系统安装

做系统盘&#xff1a; Ubuntu20.04双系统安装详解&#xff08;内容详细&#xff0c;一文通关&#xff01;&#xff09;_ubuntu 20.04-CSDN博客 ubuntu系统调整大小&#xff1a; 调整指南&#xff1a; 虚拟机中的Ubuntu扩容及重新分区方法_ubuntu重新分配磁盘空间-CSDN博客 …