放弃PHP转投Go,10万行代码重构升级一步到位!
👉导读
腾讯新闻底层页服务是重要的核心场景请求 QPS 3.5万+,单日请求量10亿+。涉及到五大场景:腾讯新闻客户端、腾讯新闻微信与 QQ 插件、腾讯网、腾讯新闻分享页、腾讯新闻小程序。由于之前项目是位于 PHP、Go-gin 代码仓库当中,存在诸多问题,历史包袱重、技术框架不统一、服务稳定性低、开发效率差,极大影响着业务运行的稳定性和效率。因此我们迫切需要对底层页进行服务升级,本文是对升级过程中的思考和总结。👉目录
1 业务场景介绍2 所面临的问题3 清理历史债务-重构 10W 行 PHP 代码4 提升研发效率-配置化设计5 提升稳定性-性能优化6 底层页服务设计的思考7 结语01
02
2.1 代码历史债务高
底层页接口代码历史债务高,代码总量约 10W+,缺乏设计、业务逻辑混乱、层次关系不明确、代码复杂度极高(核心函数长度超过2000行),甚至存在明显的 bug 等。 历史遗留的代码很多,存在大量实际功能已下线但是还保留的代码,理解和维护成本极高,例如存在330版本之前的代码(10年之前版本)。
2.2 研发效率低
各场景同一个需求需要各自开发,例如底层页增加点赞开关控制、增加点赞类型需要全场景统一生效的需求需要各场景开发,五个场景需要5人日开发,极大减缓业务迭代效率,统一场景后只需要1人日开发。 底层页各场景开发框架不统一,分别使用 butterfly、gin、sodoo(PHP) 框架,这些框架与公司基础能力契合度不高而且维护成本高。
2.3 服务稳定性差
03
3.1 面临的挑战
Apache HTTP Server: Apache HTTP Server 是一个广泛使用的 Web 服务器软件,其代码中,圈复杂度的平均值大约在10到20之间。 OpenSSL: OpenSSL 是一个用于加密和安全通信的开源库,代码总行数在 50W 行数,圈复杂度的平均值大约在10到20之间。
3.2 解决思路
3.3 工具助力,加速升级
3.3.1 使用 xhprof 生成调用流程图
3.3.2 使用 Xdebug 生成代码执行路径
(绿色为请求覆盖代码,粉色为非覆盖代码)
两个方式极大加速了我们重构的进度,更好地分析之前服务代码情况。尤其是第二个能力,我们经常会在代码里遇到分支条件的判断如版本号、上游下发的标识、文章类型、开关等各种条件判断,使用第二种能力我们可以模拟不同的请求,查看具体代码执行路径。另一方面一些服务内部的交互不容易通过接口梳理。
另外结合 trpc-gateway 流量回放插件,进行流量的 copy,我们对新 copy 的流量到开启代码覆盖检测配置的新服务中,可以将采样所有的请求聚合生成对应的覆盖代码文件,这样我们可以基本得到接口各种参数情况下所执行的代码路径。
网关流量回放插件:
04
根据请求场景,文章类型加载配置,实现根据不同需求返回不同数据响应,实现差异化配置,分为四层配置体系:
全场景统一生效的配置:一些全场景全文章类型核心的字段控制需要统一进行管理。
相同文章类型统一配置:为了更好使相同文章类型的通用字段管理。
分场景的不同文章类型配置:文章类型配置,不同文章类型数据协议不一样,返回的数据,这一层主要是针对各场景的差异化处理。
子场景的不同文章类型配置:与父级场景公用核心的配置,但是需要针对父场景作进一步差异化处理,例如落地页场景的字段需要在不同层级字段下发。
4.1 配置动态库
user.Age in 18..45 and user.Name not in ["admin", "root"]
foo matches "^[A-Z].*"
tweets | filter(.Size < 280) | map(.Content) | join(" -- ")
filter(posts, {now() - .CreatedAt >= 7 * duration("24h")})
4.2 配置详细实现
{
"mapper": "ExprEngineMapper",
"dst_path": "x",
"source_path": "pathC",
"desc": "描述信息",
"ext": "news(src_path) == 1 || news('pathA') == 1 || news('pathB') == 1 ? 1 : nil ",
"data_source": "ResourceInfo"
}
{
"mapper": "ExprEngineMapper",
"dst_path": "x",
"desc": "描述信息",
"ext": "pre(news('pathA'), news('pathB'),'腾讯新闻')"
}
{
"mapper": "ExprEngineMapper",
"dst_path": "x",
"desc": "下载链接",
"ext": "'http:\/\/xxxx'"
}
{
......
"filter": "req.Apptype == 'android' && req.Appver >= 7260"
}
最后请注意不是任何业务逻辑都能够通过简单配置动态库实现,如果是很复杂的业务逻辑也是需要通过代码实现,否则动态库会变得冗余、复杂导致难以维护。需要建立一套标准什么是可以动态配置实现什么是可以通过配置实现,例如配置的代码长度、前后依赖关系复杂程度等。
更多功能见:https://github.com/expr-lang/expr
4.3 配置如何管理
配置管理遇到问题开发时提交配置未经正确验证测试,直接发布上线。其次是 配置发布管理的编写的问题,不能达到自动化,需要人工操作。为了解决上述两个问题引入 Rainbow 七彩石配置管理能力放入对应的代码。代码层面接入了 rainbow as code,可以保证配置的可测,执行的正确性。
配置代码目录
05
5.1 针对客户端请求请求缓存
5.2 针对上游服务数据请求缓存
06
6.1 逻辑流表达与设计
未来可能演进方式:
数据加载没有批次限制,每个节点都支持数据加载触发。
更灵活的数据获取和映射,数据获取完全不被限制,调度全部配置化,面对复杂场景的聚合效率进一步提升。
6.2 落地页场景差异化实现
新增:Filter。
在每个阶段增加 Hook 机制,监听每个阶段执行触发相应的 Filter。
请求流程增加 Hook 机制:
07