写点什么

从 php5.6 到 golang1.19- 文库 App 性能跃迁之路

作者:百度Geek说
  • 2023-07-06
    上海
  • 本文字数:5704 字

    阅读完需:约 19 分钟

从php5.6到golang1.19-文库App性能跃迁之路

作者 | 百度文库 App


导读

本文深入浅出地分享了百度文库 App 服务端技术栈从 PHP 迁移至 Go 的实战经验,包含了技术选型、基础建设、流量迁移的具体方案,以及核心项目案例的重构实践。

全文 6209 字,预计阅读时间 16 分钟。

01 动机

长期以来,百度文库 App 服务端采用 PHP 作为主要开发语言,高效地支撑了业务迭代发展。随着平台流量的持续增长,服务端的负载越来越大逐渐接近系统瓶颈。为了提升系统的负载能力,我们采取了一些优化手段,其中最快最有效的方法是增加在线集群的实例数量。此外,还采用过 lua 开发项目,承接一些逻辑简单而访问量大的接口来分担负载。由于 lua 本身的一些局限性,不适合做复杂的业务逻辑。


伴随着 IT 技术的发展潮流,我们积极响应公司降本增效的号召,决定在 2022 年年中迁移并重构服务端技术栈。旨在升级技术架构,提升系统负载能力。


把握技术栈迁移和项目重构的时机是很难的一件事情,特别是成熟的团队要进行大的系统改动。如果没有出现真正的痛点,即使研发同学认为技术实现上已经出现诸多设计不合理和有风险的地方,往往并不被允许花大量时间去做技术项目。可一旦连业务人员(产品经理、销售、运营)也觉得系统功能需要升级的时候,比如在用户体验上,App 文档搜索接口延迟比较长,产品同学认为如果首屏渲染能明显提速的话,对点击率、付费率都会有大幅的提升,但是研发这边基于老的技术栈已经难以做优化了,那么此刻就很适合做迁移重构。


恰逢其时,产品同学提出一些提升用户体验,同时适合项目重构的需求,比如:加速搜索结果页首屏渲染、新 App 首页,AIGC 智能创作。这些大需求十分有利于迁移重构工作的启动,它们把迁移重构所需增加的额外人力占用降到最低。在已有的功能上做迁移重构,更快更稳定的接口响应带来流畅的用户体验,这有利于促进整体团队的 okr 目标达成。


回过头来,梳理下当时服务端基于 php5.6 的技术债务:


1、底层技术:语言版本老旧,特性落后,存在执行效率低,安全风险,资源浪费的缺陷;


2、开发质效:业务逻辑交叉耦合,大量废弃的接口和下线的业务逻辑,降低了代码可读性、可维护性,持续增加项目迭代的难度。



△技术债务

02 启动之前的状态

服务部署上,文库 App 的服务端部署方式是 nginx+hhvm(HipHop VM 3.0.1;baidu version:1.1.6 (rel)),HHVM 是 Facebook 开发的高性能 PHP 虚拟机,是传统的 nginx+php-fpm 的一个性能优化版本。近年已经失去了 hhvm 原创团队的持续维护迭代,它支持的语法特性和执行效率相对落后,存在一定安全风险。


查看启动迁移之初的服务端实例用量,有赖于日常运维,首先确认在线服务的实例 cpu、内存和磁盘使用率在合理的阈值内,排除了利用率较低导致资源浪费、利用率过高会有容灾风险的情况。在应用层,我们一共使用了数以千计的 php5 实例。

03 远景

重构的投入与回报并非呈线性关系。

—— 《领域驱动设计:软件核心复杂性应对之道》


直观的说,我们希望服务端升级能带来更少的代码,更稳定的系统,更高的质量效率,更佳的用户体验。这体现在下面几点:


1、技术升级:采用先进的语言框架,支撑项目高效迭代提供强劲底层引擎、安全性和成熟的应用生态;


2、改善设计:梳理代码逻辑,治理冗余,解决代码中的坏味道,构建高复用、低耦合、可扩展的业务架构;


3、降本增效:一方面底座升级,提升代码执行效率,降低平响,提升服务可用性、可观测性;一方面在运维实践上,合理分配容器实例的 cpu,内存和磁盘的配额,优化资源效能。


服务端升级的成功与否,可以从两个方面来努力达成,分别是技术栈迁移改善既有代码设计的重构

04 做技术选型

我们不打算使用较为小众、生态孤立的语言作为文库 App 服务端的技术栈。同时参考兄弟团队的技术栈升级方向,最终进入技术选型决赛圈的是两种厂内框架,基于 php7 的 odp3 框架(Online Develop Platform)和基于 go 的 gdp2 框架(Go Develop Platform)。



选项一:PHP7 框架和 Phaster


PHP7 框架是公司发布的在线业务开发平台,其提供了标准的 webserver 环境、标准 php 环境、AP 框架、基础库、资源访问层、通用服务等组件,统一业务的逻辑和部署结构。框架的亮点在于 Phaster。Phaster 能让你使用 PHP 语言开发高性能的 Http、Fastcgi、Nshead 服务,进行高性能的 RPC 调用,以极低的成本实现业务代码并行化。


Phaster 和其它业界框架的对比如下。



Phaster 可以作为 http server,也可以作为 fastcgi server。相对传统 nginx+cgi 的方式,Phaster 基于以上的能力实现数倍的性能提升。具备以下亮点:


1、传统的 hhvm 或者 php-fpm 处理请求的逻辑是,每一个请求在处理时,都要先初始化 php 上下文,请求结束时清理上下文,回收各种资源。而 phaster 在开启上下文复用的情况下,可以节省类加载,文件加载,初始化等过程耗费的时间。举个例子,如果你的接口每次都要读取一个大文件配置,可以把读取操作放到初始化文件里。在 100 个请求内,这个读取操作只执行一次就够了;


2、hhvm 或 php-fpm 并不直接支持 http 协议,往往前面会加上 nginx 作为 http 服务器,两者之间通过 fastcgi 通信。而 Phaster 可以直接作为 http 服务器启动,减少一层 nginx 的处理转发;


3、协程的支持为 IO 密集型的业务场景,提供了高并发的基础。对于阻塞性的 IO,可以放入协程里做,将阻塞变为非阻塞,在使用同步编程方案的同时,享受异步效果带来的 IO 性能提升。


值得一提的是,Go 都支持这些能力


选项二:Go 框架


Go 语言是由 Google 于 2009 年发布,近几年伴随着云计算、微服务、分布式的发展而迅速崛起,跻身主流编程语言之列,和 Java 类似,它是一门静态的、强类型的、编译型编程语言,为并发而生,所以天生适用于并发编程(网络编程)。


GDP2( Go Develop Platform ) 框架是一个对厂内基础设施支持好,可扩展性好、易配置、易组装、易测试的 Go 开发框架。具备完善的 RPC Client 和 RPC Server 能力,以及配套的通用基础库,可以用来开发 API、Web 及后端服务等各种应用。具备以下亮点:


1、对厂内基础设施支持好;


2、可扩展性好、易配置、易组装;


3、易用性好、对测试友好 (易 mock,多种 testServer、testClient);


4、组件内部状态易观察 ;


5、全链路超时 &流程控制机制,稳定性好 ;


6、厂内大规模应用,稳定可靠 (基本所有 Go 项目都有使用,共有几千项目使用)。



综合对比以上对两种框架的特点和落地的可行性等因素,最终我们更倾向于向 GDP 框架迁移。

05 进行重构的关键路径

做好技术选型后,我们就开始下一步的工作。和常见的 web 项目一样,文库 App 业务迭代速度快、任务重,难以保证有充足的人力长期投入到技术项目。所以,技术栈升级重构的前提是在保证业务需求不停的情况下进行,需要有持续重构的意识,往往采用『敏捷式迭代』。

5.1 敏捷式迭代

第一步,工作量预估。通过日志聚合分析,得出当下有流量的 App 接口路由(老项目很多接口没有流量,关联需求已下线)。实际操作下,发现刚好按照 qps 值从大到小排序的 top 50 的接口的流量占比达到了总流量的 99%+,这也确定了接口迁移顺序的优先级。


第二步,制定策略。服务端技术栈 go 迁移的落地,本质上体现为由 php 承接的流量转为 go 承接,当所有流量都在 go 实例上运行,且对 php 项目无底层调用的依赖关系,即可认为升级完成。因此,不断扩大 go 实例集群在 App 服务端总的流量占比,就是我们迁移的工作目标。以此可以大概可以总结为两种方式:


1、根据业务需求,结合接口重要性和流量占比确定优先级,进行迁移;


2、新需求的代码实现和 php 项目不存在强依赖关系,直接在 go 项目开发。


有相当长的一段时间是处于 php+go 进行混合编程的共存状态。由于 App 的 B/S 架构特性,重构完的接口需要通过接入层网关做代理转发,切换服务端承接流量的具体应用层集群(php->go),让客户端保持 path 不变,从而实现 App 老版本的高可用。采取混合编程的思路在重构初期,可能会一些比较特殊的需求情况,比如:同一段业务逻辑,需要用 go 写一遍,用 php 写一遍,无疑增加了一定的工作量,当然这也是避免不了的。


在重构的时候避免走到一个误区:瀑布模型,一口气把整个项目都重构了。从时间、人力成本和稳定性上来讲,这种方式风险比较大,不推荐。综合来看接口粒度的分批进行重构,这样不管是内化的技术迭代,还是外化的业务影响,都是有明显感知的。用作实现流量迁移的方式更为合适。

5.2 配套 golang 的基础建设

区别于 php 项目的 work flow,有以下几点不同。


1、脚手架:除了定义路由、逻辑分层、生成配置等框架属性外,go 需要额外对协程进行封装,提供给研发同学一个开箱即用的脚手架。


2、发布:封装 build 逻辑,实现打包编译、环境变量管理。


3、部署:是整个二进制文件覆盖,需要重启服务,使用热重启模块,可以实现无损上线,以及更快的上线速度。


4、流量:CS 架构的 App 的接口迁移需要接入层做路由重写,协调网关变更。


5、监控:日志分级,微服务间保持 trace 透传,各类日志落盘符合 agent 采集的格式规范。

5.3 写第一个接口

一方面,在开始技术栈迁移的时候,需要了解到 go 语言层面支持并发,可以很轻松的开发异步程序强类型语言。go 是强类型的静态语言,编译时确定类型,不如 PHP 灵活,但是更严谨,更安全,可以在编译阶段检查出来隐藏的绝大多数问题。



△类型转换


另一方面,重构项目如何治理陈旧代码?概括的说,可以参考《重构-改善既有代码的设计》一书提出的 23 种代码坏味道,有针对性地对代码进行重构,驯服成整洁和易于阅读的代码。



把前期调研和迁移策略确定好了,实际的代码开发变得得心应手。在迁移老接口流量的时候,我们需要在新接口用 go 重新实现一遍,调用方式上完全等同老接口,包括 path、method、验签、header 规则、参数结构、响应结构、错误码。只有应用层上的虚拟域名不同。

5.4 质量保障

代码 ready 了,区别于 php 项目的常规测试流程,go 不能绕过性能测试。因为我们写 php 几乎不需要关注 GC 和内存泄露,但是 go 需要,有时候手动测试和黑盒测试是 OK 的,但是到线上遇到有一定并发的业务场景,就会暴露问题,常常表现为实例的 cpu 或者内存利用率持续上涨,直至宕机。


应对 go 的内存泄露问题。一方面需要在测试流程中增加压测环节;一方面需要日常多关注一下监控仪表盘的实例资源利用率、接口平响、稳定性指标是否符合预期,因为有的隐藏 bug 即使压测也不能覆盖到。这时需要提升 go 服务的可观测性,以便及时发现风险。


Go 质量保障能力全景矩阵如下:



构建线下质量保障能力:



构建线上质量保障能力:


5.5 流量迁移


如上图所示,go 项目上线后,实际流量还在老项目承接。开始做流量迁移,用户流量首先到达接入层,在这一层我们根据不同的访问域名和路由,分流到不同的应用层 load balancer ,为了兼容老版本的 App,需要在域名路由不变更的情况下,完成流量迁移。在接入层网关做分流,把分流到 php 的规则应用到 go 应用层 load balancer 上,就完成了流量迁移。注意,如果是非常核心的接口,我们需要进行灰度发布,可以采用 nginx+lua 的方式实现,或者采用著名的开源网关 ApiSix、BFE 项目,它们都支持灰度发布。

5.6 核心功能重构实践

这次重构比较突出的亮点,体现在百度文库 App 的全新首页和搜索结果页优化。


(1)定制化新首页


文库用户个性化需求较分散,希望通过将垂类用户内容需求共性抽象,对连续型特征且使用较高的内容进行提取,采用中心化集中推荐的方式,提高用户垂类内容结构化满足,进而提升用户留存率及续费意愿。重构了 App 首页的布局和内容展示。增加了个性化的『我的资料库』,『教学进度』,『推荐频道』,定制化展现文档榜单和文件夹榜单。


App 新首页的技术方案是全新的,重构的动机来自"业务驱动",而非"质量驱动"。需求实现上,底层不依赖 php 老项目。所以直接在 go 项目开发上线,提供接口服务。这样上线后,go 自然替换掉了 php 原本承接的首页流量。



△文库新首页


(2)搜索结果页优化


服务端这边主要重构对象是一个搜索接口,实际开发中,和产品沟通是否可以下线不要的 tab 列表和内化的推荐逻辑;清理多处已经下线的 AB 实验的业务逻辑,去掉已经推全 AB 实验的代码判断;优化文档排序算法,和产品、前端同学对齐当前必须的字段,去除冗余;善用协程优化串行逻辑。


结合前端去除懒加载代码,图片本地化,使用端能力缓存接口数据,搭建离线包服务等技术手段,搜索结果页优化取得了不错的成果。大幅降低搜索结果页的加载速度,安卓平均降低延迟 41%;IOS 平均降低延迟 43% ,搜索结果点击率和成交的订单量也有一定提升。



△【新老搜索结果页】AB 实验时的白屏时长统计

06 目标达成

从 2022 年 8 月启动 go 迁移至今,接近完成 App 服务端的全部流量迁移工作。


1、技术迭代:得益于 go 语言特性先进、内存管理和丰富的生态,提升了代码执行效率、安全性和可观测性;通过梳理业务逻辑,治理冗余,清理代码中的坏味道,封装公共类等方法,提升质量效率,代码可读性和可维护性;


2、提升性能:一方面通过协程、通道技术改变同步阻塞的代码执行方式;另一方面,编译后的二进制文件执行效率远高于 nginx+php-cgi 的网络模型。平均减少了约 30%的接口耗时,TP90 减少了 35%的耗时;


3、降本增效:得益于 go 语言高性能的特性,应用层实例的负载能力得到提升。流量迁移后,文库 App 服务端在线集群缩减了约 50%的的实例数量。

07 思考与总结

1、手机 App 属于 CS 架构的应用,在迁移过程中要保证老版本 Client 可以使用服务;


2、在面对一个长期项目时,拆解目标是很重要的,快步试错即时反馈也是互联网思维的一部分;


3、迁移理论无损,但需要把风险同步 pm 同学,及时关注各业务指标,同时制定预案,保证可回滚的灵活性;


4、接口刚上线或者 AB 实验推全后,迁移的接口流量上升,要养成经常观察可用性仪表盘的习惯,及时处理 http status 异常的问题,避免风险扩大化为故障。

08 结语

知而不行,是为不知;行而不知,可以致知。


回想项目迁移重构的整个过程,最有意思的是做技术选型和讨论流量迁移具体实行方案的起步阶段,那时面对臃肿庞大的 php 单体项目如何进行迁移,是有些迷茫的。在实践的摸索过程中逐渐加深对项目的理解,通过所得的启发来推导制定下一步的行动,形成正向循环。希望本文的内容对大家的工作实践有所帮助。


——END——


推荐阅读:


扫光动效在移动端应用实践


Android SDK安全加固问题与分析


搜索语义模型的大规模量化实践


如何设计一个高效的分布式日志服务平台


视频与图片检索中的多模态语义匹配模型:原理、启示、应用与展望


百度离线资源治理

发布于: 刚刚阅读数: 4
用户头像

百度Geek说

关注

百度官方技术账号 2021-01-22 加入

关注我们,带你了解更多百度技术干货。

评论

发布
暂无评论
从php5.6到golang1.19-文库App性能跃迁之路_golang_百度Geek说_InfoQ写作社区