👉目录
1 背景介绍
2 现状分析
3 “重写”设计
4 “重写”收益
5 “重构”的时机
6 总结
//////////
1 月 7 日晚 7:30,腾讯云开发者视频号「鹅厂程序员面对面」直播间,为你揭晓过去一年狂飙、烧钱的大模型背后,是如何为企业降本增效,应用落地的,预约观看准时抢鹅厂周边好礼!
2.2 当前服务整体问题
2.2.1 维护成本高、开发迭代效率低
服务语言框架不统一、框架老旧
共2个服务,2种框架,都不是 trpc-go 框架,学习和维护成本高;
数据链路不统一,同一种功能多种实现
主要功能与端内相似,分为信息流、底层页、用户三大类接口,却与端内完全分散在两个服务中。同时相同的功能还分散在了不同的服务中,实现不统一,甚至同一服务中的相同功能实现也不统一。例如:信息流接口,分散在两个服务中,10+个接口,6种接口格式;评论接口,分散在2个服务中;
测试环境搭建困难,联调效率低
非 trpc-go 框架,无框架配置,下游依赖和业务配置都没有单独的配置文件,服务地址写死在代码中,与下游测试环境联调困难;
核心服务是老发布平台 sumeru 迁移服务,现发布平台123不支持搭建个人测试环境,多人开发环境隔离困难;
配置分散:40+个配置分散在4个配置平台,维护效率低
2.2.2 数据不一致问题
取值逻辑不一致
由于信息流10+个接口6种接口格式,使得前后端的卡片格式化部分都无法复用,出现多场景取值逻辑不一致问题。
下游依赖数据获取链路不一致
信息流获取文章相关信息部分,下游依赖众多,与端内场景需要的数据源一致,却分开管理,存在个别场景数据源老旧导致不一致问题;
2.2.3 稳定性低
服务框架老旧,本身没有 tp99 和 slo 监控;23年6月前未接入业务网关,也无法通过网关观测到 slo 指标,只能通过故障单汇总、报警和网关的 cls 日志统计稳定性:
(1)重构完成前1年内插件端有1次新闻内部 x 级故障,x 次 oncall
(2)接口成功率99.89%,不足3个9;
依赖老旧的平台服务
ons-agent 当前无人维护,其故障造成一次插件 slo 不达标;
服务可观测性差,定位问题困难
框架老旧,无法接入伽利略,导致无 tp99 监控、slo 监控,无异常错误 trace 日志,链路日志不完善。由于定位问题时间长,导致插件一次内部 x 级故障(MTTR 2hour);
框架老旧,性能差,稳定性保障机制不完善:
依赖的 tab-sdk 无法使用 trpc 请求,导致需要远程访问时性能差,造成一次 slo 降低问题;
无法使用 trpc-go 框架自动支持的故障熔断机制,下游部分节点故障时无法自动隔离问题节点,阻止问题扩散。
2.2.4 工程质量差
单测覆盖率低于10%,无接口测试;
代码重复率高,接口众多,框架老旧、代码质量差,导致添加单测和接口测试成本极高;
代码规范问题 x 个,研发指标得分73.69;
2.3 信息流接口设计问题
先介绍一下信息流接口重要阶段和功能:
provider:信息流接口内容列表提供层,负责获取各种来源的内容列表;
loader:文章数据加载层,从各种来源并发获取的文章数据,例如:从文章池拿文章基本信息、事件服务拿文章相关事件信息、用户服务拿文章作者相关信息等
packer:文章数据打包层,按照从 provider 步骤获取的文章顺序,用 loader 到的内容,给各个卡片打包;
然后再看下当前的模块设计:
有以下问题:
信息流各接口格式不统一:
历史原因信息流10+个接口接口协议有6种,甚至相同接口不同场景下的返回格式也不一致(例如二级页);
provider 层分层逻辑不对
provider 层功能都是从推荐、配置等数据源获取数据列表,却一个接口一套实现,管理维护难度大;
packer 层分层逻辑不对
由于接口格式不统一,packer 层也是每个接口都独立实现,相同卡片在不同场景下 format 的实现逻辑不统一,维护难度大,且存在逻辑不一致问题;
✓ 接口接入业务网关 smartGW;
并借助网关的限流插件和安全拦截插件进行流量安全治理;
✓ 将6种 web 端信息流接口格式统一为1种;
✓ 除信息流外主要场景与端内统一服务;
内容类:图文/视频/问答底层页、专题/事件底层页、直播、合集;
用户类:个人页/媒体页(用户服务)、评论;
✓ 信息流由于各场景业务复杂,单独部署服务处理,核心逻辑与端内对齐;
与端内使用相同的信息流基础打包服务(feed),简化下游依赖管理;
✓ 当前配置均迁移至七彩石统一管理;
✓ 服务个数收敛为1个,采用 trpc-go 框架;
协议统一,添加协议转换层提供给手 Q 插件 hippy 老版本使用;
provider 层插件化,将推荐、配置、热点榜等内容提供方,抽象成插件,各场景按需使用;
卡片内容数据加载和基础打包功能,与端内统一到 feed 服务上;
card 层插件化,构建基础卡片 basecard 用于一般场景逻辑复用,特殊场景继承自 basecard,实现特殊卡片组装逻辑;
以下两种类型的需求,占日常产品需求的70%。重构前单个需求平均规模4人日,重构后单个需求平均规模2人日,迭代效率提升至少50%。例如以下需求:
(1)底层页相同场景的同一功能,一处开发,多处可用
case1【图文/视频】: 底层页新增xxx标签;
重构前:端内底层页、插件底层页分别开发;4.5人日 。
重构后:底层页服务统一,开发一次即可; 1.5人日。
case2【问答】: 发布问题支持摘要、文章引用能力;
重构前:需要端内接入层、插件接入层各自开发,2人日。
重构后:数据链路一致,1人日。
(2)新增信息流场景
case1【插件】:外显推送热点精选页面;
重构前:每个场景的处理逻辑是单独的,可复用性差,新增场景需要单独开发,4人日。
重构后:有信息流处理模版方法,有基础的处理逻辑,各步骤也都有不同的插件可复用。0.5人日。
其他需求:无特殊逻辑的频道,只需配置;有特殊逻辑时,只需针对某一步骤开发特殊插件;
case1:视频封面图尺寸与端内不一致;
case2:手q724板块里视频的缩图模糊;
统一数据链路后,插件与端内,各业务下相同尺寸的图片取图逻辑一致;
1、重构前后代码量对比统计
对比 | 服务名 | 总代码量 | 非测试文件代码量 |
升级前 | xxx1 | 102189 | 94302 |
升级后 | xxx2 | 57870 | 25955(-68347) |
仅统计了由插件独立维护的服务,与端内共用的服务例如图文底层页、个人页、评论等,与端内相同接口,特殊逻辑不多,这里未做统计。
2、代码质量对比
服务名 | 研发指标得分 | 单测覆盖率 |
stream | 73.69 | <10% |
web_feed | 100(+26.31) | 66.57%(+56.57%) |
3、完善接口文档和接口测试用例
(1) 接口成功率
插件接口成功率从约 99.89% 提升至 99.99% 以上,升级后核心接口 SLO 未出现不达标的情况。
旧服务未接入业务网关,自身上报数据也不全,通过旧服务接入的腾讯云 CLS 日志统计上看,插件存在很多异常状态码的访问。从状态码上看,插件接口的整体成功率在 99.89% 左右(还是在做过初步治理的情况下)。
插件旧服务 CLS 统计
新服务监控:
成功提升的同时,核心接口耗时与之前持平。
由于插件之前就是 golang 的服务,除推荐外,获取数据都有缓存,故耗时提升空间有限;
旧服务:
新服务:
(2) 线上故障数量减少
重构完成前1年内插件端有1次新闻内部 x 级故障,x 次 oncall,重构升级后至今1.5年没有故障,x 次 oncall,oncall 数量下降73%;
故障:重构前 xxx 故障。
原因:代码 bug 导致,定位时间长(mttr 2h)升级为内部 x 级故障。排查时间长的原因为日志打印不完善,缺少 trace。
重构后:借助 trpc-go 框架生态的伽利略和鹰眼日志,可快速定位问题,预计 10min 内定位;
原有系统仅有通过加代码的方式主动上报主调被调监控,以及在此基础上的基本告警。
(1)伽利略监控系统
完善 tRPC-go 监控,协程数量、gc 耗时、gc 停顿时间;
主被调监控,异常率、超时率、成功率、调用量、p99 耗时与相关告警;
中心化的日志查询工具;
链路日志追踪工具;
自定义监控及报警。
(2)染色 trace 日志
借助网关染色插件,打通接入层、微服务和推荐的染色日志,极大方便联调和 case 排查
(1)接入门神
端外主要接口接入门神系统,借助公司已有系统,对基本的常见漏洞攻击做好防护。
(2)通过业务网关做接口限流、并接入平台治理工具
接入业务网关,对遇到异常流量的接口提交给平台治理侧分析,并加入安全拦截插件。目前有6个接口加入安全拦截插件,2个 feed 流接口加入限流插件。
(1)插件服务器成本
CPU 资源节省:xxx-xxx=xxx 核(-49%)
内存资源节省:xxx-xxx=xxxG(-20.6%)
(2)插件 redis 成本优化
插件通过梳理代码、优化逻辑,服务调用的 redis 数量从xx个缩减至x个,缩减比例91%;
共下线/缩容 redis 资源 x 个,容量共 xxxGB,预计节省 2w+元/月。
重构升级极大依赖前端同事的大力支持,在升级过程中前端也在持续优化,也同样有不小的收益。主要体现在代码清理和开发效率提升。
重构优化清理无用代码16000+行,打包主 JS 文件从491kb减少到307kb,降幅37%,整页平均耗时降低10%;
信息流接口格式统一,信息流组件可复用,降低开发成本,开发效率提升20%;
信息流接口开启gzip,耗时降低100ms,降幅11%。
其实重写的收益不仅仅体现在上面的这些指标,更是在重写后的一年中,每次需求评审时,我们都能明显感受到其带来的便利。例如,某些功能点在端内需求中能够自动支持,而另一些功能则无需开发,只需联调即可。
然而,不得不承认,重写项目的周期较长,长达9个月,且工作量庞大,同时还需要前端团队的配合。在重写项目进行的同时,我们还面临着需求压力,尤其是前端团队也在进行他们自己的优化项目。经历过成本如此高的项目之后,大家自然希望下一次重写的时间能够尽量推迟。
那么,我们如何才能推迟下一次重写的时间呢?我的建议是随时进行真正的“重构”。接下来,我们来讨论一下真正的“重构”应该在何时进行以及如何观测。根据个人经验和一些资料的查询,在以下几个点上我们进行实践的落地:
1. 代码质量下降时
当代码的可读性、可维护性和可测试性显著下降时,重构是必要的。例如:代码中出现大量重复代码;函数或类的复杂度过高,难以理解;代码中存在过多的注释来解释复杂的逻辑。
观测方法:定期观测代码 codecc 指标
2. 功能扩展或修改时
在实现新功能时,发现现有代码结构不适合扩展;修改某个功能时,发现相关代码的耦合度过高,影响了其他功能。
观测方法:每个需求记录代码开发量、需改动的模块数、开发工期,对于异常的部分,随需求进行重构改动;
3. 代码审查或测试反馈时
在代码审查或测试过程中,团队成员可能会发现代码中的问题或改进建议;当测试覆盖率低或者发现单元测试添加困难时,大概率代码需要进行结构优化;
观测方法:严格的 code review 机制,所有改动必须 review;流水线必须添加单测覆盖率检查,代码开发人员感觉单测添加困难时,及时优化或者向组内其他成员反馈并讨论优化方案;
4. 性能问题时
当系统出现性能瓶颈时,例如:发现某些算法效率低下,代码中存在不必要的复杂性时;
观测方法:添加成功率和耗时变动监控;
5. 技术栈或工具更新时
当项目的技术栈或工具发生变化时,例如:升级到新的框架或库时,可能需要重构以适应新特性;采用新的开发工具或流程时,重构可以帮助提高开发效率。
观测方法:定期调研使用的框架或库是否有更新;根据 codiumAI、工蜂 AI 等工具的建议进行检测和优化代码;
6. 技术栈或工具更新时
定期进行技术审查和代码评估,可以帮助团队识别需要重构的部分。通过定期的代码质量检查,团队可以主动发现问题并进行重构。
观测方法:各模块 owner 按季度维护和更新架构图,包括调用流程图和类图,组织组内评审,并排期优化;
将上面的结论来看,重构的时机基本是“随时”。有一句俗语,“最好的时机是昨天,其次是现在”,对于重构来讲也是很适用的。最好的时机是之前做设计的时候,但往日不可追也,过去也不可能预见现在的变化;其次是现在,每当发现技术债务的时候,尽快小范围重构。
📢📢欢迎加入腾讯云开发者社群,享前沿资讯、大咖干货,找兴趣搭子,交同城好友,更有鹅厂招聘机会、限量周边好礼等你来~
(长按图片立即扫码)