重构这件“小”事儿
本文以一个Web项目的业务代码重构实践作为依据,来探讨项目业务代码重构过程中遇到的开发问题,以及重构过程中的一些注意点,希望可以给项目开发和服务开发维护重构提供一些通用的参考与思路。
这里不探讨大型项目的重构实践,毕竟一个大型项目的重构,更偏重于架构体系完善更新与业务领域拆分,它所涉及的架构体系、人力资源、部门协调等等其他问题都具有很大的挑战。另外大部分开发所负责的仅是其中的一个服务或者模块,这里探讨的内容可能对拆分后的服务重构更具参考意义。
1.1 背景问题
1.2 需要改变现状
1.3 重构的初步想法
2.1 熟悉业务流程并分析问题与痛点
2.2 评估重构成本与重构推进方式
难易程度 根据 方案差异、修改难度、稳定要求、影响范围来 综合评估 紧急程度 根据 迭代复用、接口性能、异常频次、受益范围来 综合评估
处理方式分析如下表:
难度 \ 紧急程度 | |||
2.3 完善并确定流量迁移方案
在这样的通用架构里,我们既然切换了底层语言,那公司基建支撑的相应架构,就可以用起来了。
我们最终选择的具体流量迁移方案如下:
初始化新的项目仓库并完善发布部署流程
打通新老两个项目的用户认证方式并做到双向兼容(如果网关统一承接用户认证,可以省去这一步)
利用网关层的能力去配置转发规则,使用特定或者通用规则将接口流量导入新项目中(如果没有网关可以考虑简单接入Nginx配置转发)
服务端完成一个批次的接口重构迁移后,在测试环境切换流量到新项目并测试整体流程,通过测试再发布
重构中部分接口需要变更的,推进前端将调用切换到新接口
上线后跟踪流量与新接口功能状态,如有问题随时回滚
到目前为止,我们已经做好了所有的前置工作,那么现在我们 ready go !
3.1 基本运维监控体系完善
优化点 | |||
看一下下面的实例展示:
trace信息可以帮助我们追踪每一次的调用链路
日志注入trace信息可让我们对独立调用链路日志做快速筛选和时间维度分析, 根据traceId追踪同一条链路数据
{
"level":"error",
"ts":"2022-07-22T21:26:00.073+0800",
"caller":"api/foo_bar.go:38",
"msg":"[foo]bar",
"error":"Post \"https://xxx.com/abc/xxx\": context deadline exceeded",
"traceId":"0aee15dc63f617e751d17060xcf74b9c",
"content":{
"body":"json str",
"resp":""
}
}
监控体系监测业务项目运行状态
3.2 重整业务逻辑
3.3 代码重构并解决细节问题
在具体的每一个接口或者任务脚本的重构过程中,我们也总结了之前开发遇到的一些典型问题,整理如下:
优化点 | 问题 | 收益 | 典型思想 |
数据库查询网络 I/O 优化 | 列表类接口 循环网络I/O | 减少各类列表接口 RT | 最小化网络 I/O |
削减接口返回字段 | 接口返回无效字段过多 | 减少干扰、削减流量 | 减少网络带宽占用 |
统一返回值字段数据类型 | 弱类型语言类型乱用 | 统一数据类型,便于管理 | 强类型 |
分类树递归生成优化 单次查询并重构复用 | 循环多层查询数据库 I/O过多 生成算法复杂度略高 | 统一分类树生成 集成过滤规则 | O(n)时间复杂度, 递归并防止数据异常而死循环 |
非强关联逻辑功能拆分 | 部分接口业务逻辑隔离 但是接口强相关 | 剥离不同模块为多个接口 | 分治 |
解决语法类问题 | 循环迭代中改变SET 数据类型不匹配 数据存储格式 等(Python) | 提升主流程稳定性 杜绝答案提交异步流程中断 | 数据与语法兼容 |
抽离相同逻辑 达成方法重用 | 相同逻辑方法分散不兼容 部分逻辑缺失、维护难度高 | 复用方法与逻辑 统一逻辑并降低维护难度 | 复用 |
离线数据同步优化 | 全量同步数据未分批 不支持断点补偿 | 限制批次数量分批同步 支持中断恢复 | 分批、控制上限 中断补偿 |
优化批量导入数据处理 | 业务导入数据处理逻辑有问题 关联比对多次查询且不用MAP | 减少算法时间复杂度 提升处理速度 | 批量提取 MAP对比的O(1)复杂度 |
Redis慢查询优化 低效缓存优化 清理或拆解大KEY | 未选对正确的数据结构 未对数据结构选择正确操作方法 例如: 存储set数据取单个元素使用 smembers 命令读取后比对,而不是使用 sismember | 部分smembers读取切换为sismember,RT>50ms查询平均减少5个/秒, 优化完几乎无RT>100ms请求,提升吞吐效率显著 | 数据结构 算法复杂度 Redis单线程模型阻塞 |
缓存淘汰机制优化 | 部分缓存无有效淘汰机制 代码SCAN淘汰管理难性能差 | 合理设计 利用Redis自动过期机制 | 合理利用缓存淘汰机制 |
SQL索引调优 | 低效索引,交叉索引干扰 | 提升查询性能 | 索引调优 索引覆盖查询 |
异步消费脚本优化 | MQ消费任务的幂等、事务性、补偿 逻辑需要确认 | 防丢、防重、补偿处理 | 幂等、事务 等 |
配置迁移到配置中心 | 部分配置硬编码需要迁移 | 统一管理配置 代码不包含敏感信息 | 分环境隔离配置 隐藏重要信息 |
只有生产者无消费者队列待处理 | 生产向无消费队列无限注入数据 导致队列无限膨胀 | 打通生产消费流程 取消无用队列减少空间占用 | 队列有生产必有消费 |
以上这些典型问题都是经过提炼总结过后的了,看起来只是有限的几个,但项目代码库中,每一个问题点都可能出现数次甚至数十次,所以才不得不将整体的代码都重构。
下面列举一些实际的例子:
列表类接口循环网络I/O的优化
// 这里代码就不贴了,我大概做个说明:
// 数据库ORM使用时候处理粗糙,额外写的GET方法,独立查询关联表
// 在列表接口中循环获取该数据即造成数据库网络I/O的循环调用
// 而通过ORM预加载的方式,则是削减查询并二次组装的数据
// 当然我们也可以自己查询列表数据,然后先遍历提取外键,将关联数据转换为map接口,再次遍历列表来组装结果数据
大量数据处理的时候分批操作
// 大量数据处理分批次进行
// 一是考虑内存用量、二是考虑I/O交互流量、三是考虑处理分块时间、四是考虑中断恢复
// 例如:从数据库批量获取数据,然后批量上传到某处
// 注意:分页批处理需要保证每一页数据不变化,否则会有错漏或者重复
page := 1
pageSize := 10000
for {
// 判断已经存在的断点数据,将条件恢复成断点条件
// 直接 模拟查询的结构列表
dataList := make([]itemStruct{}, 0)
// 此处处理分批后的业务逻辑
......
// 处理完当前批次可以记录断点
// 断点可以缓存在数据库、Redis等其他媒介中
// 分页查询,查不到数据或者低于pageSize可以当成数据处理完了
if (len(dataList) < pageSize) {
break
}
// 查询条件切换为下一页
page += 1
}
Redis主从版大Key导致的慢查询优化前后对比
合理利用Redis的缓存淘汰机制(杜绝掉SCAN扫描的方式来淘汰KEY)
敏感数据转移到配置中心(例如这种硬编码的配置,需要迁移走)
4.1 重构的成果收益
已经全线接入trace、log、metric和告警,并可以根据这几个工具来指导项目维护和优化 业务稳定性明显提升,从刚接手时候的经常报错修BUG到现在几乎没有问题 不再有循环数据库查询I/O ,除了报表类接口,基本杜绝慢查询 部分重点接口的性能提升明显,有一些后台接口极端RT值从10s以上压缩到1s以内,C端主要接口RT的99线在150ms内(非高并发设计场景,勿喷) 可复用逻辑已尽量整合,完善并统一了之前不一致的业务逻辑,维护难度明显降低 长链路的数据提交整体流程无丢失数据的异常发生 业务方使用满意度提升
4.2 人员技能要求
熟练使用当前项目所需要的开发语言,避免产生一些基础的语言问题
对数据库有比较深的了解,能根据实际情况设计比较合理的数据结构并做到适当优化
对常用的中间件使用比较熟悉,了解一些原理,避免在使用的时候只知其一不知其二从而踩坑又难以排查
计算机的基础扎实,操作系统知识,算法与数据结构知识熟悉,可以写高效的代码
了解高并发高可用架构,可以根据一些基本思想来指导开发与优化
5.1 任务阶段化来变“小”当前事项
5.2 开发的几个主要思路
最小维护难度:系统设计结构完整、逻辑算法简洁高效
单个接口最小RT:接口性能要高,RT尽可能的小
最小限度的数据交互:接口请求参数以及返回值尽量精简,以节约网络带宽
异步削峰限流等:使用异步方式剥离额外逻辑,提升接口性能并提升用户体验
最少访问频次:了解计算机各个硬件以及网络I/O的性能层级,尽量减少长耗时的I/O,转换为程序内部处理
最少数据写入:数据写入需要尽量削减,减少流量以及无效数据的产生
缓存与缓存一致性:多级缓存、缓存一致性、缓存淘汰策略等思路
分治与隔离:该思想除了在拆分资源上体现(对象储存CDN剥离图片文件等内容),还可以在业务模块上体现出来(界定业务边界,合理拆分具体的模块与流程)
高可用性:注重稳定性,整体项目流程稳定无差错,降级与灾备需要提前考虑
5.3 提升技能与经验积累
END
🌟 活动推荐
2023 年 5 月 27-28 日,GOTC 2023 全球开源技术峰会将在上海张江科学会堂隆重举行。
为期 2 天的开源行业盛会,将以行业展览、主题发言、特别论坛、分论坛、快闪演讲的形式来诠释此次大会主题 ——“Open Source, Into the Future”。与会者将一起探讨元宇宙、3D 与游戏、eBPF、Web3.0、区块链等热门技术主题,以及 OSPO、汽车软件、AIGC、开源教育培训、云原生、信创等热门话题,探讨开源未来,助力开源发展。
长按识别下方二维码立即查看 GOTC 2023 详情/报名。
微信扫码关注该文公众号作者