嘉宾 | 董晓聪、吕亚霖、蒋帅
记者 | 赵钰莹
切换背景
作业帮初期因业务快速发展,服务端采用 PHP 语言作为主要开发语言,很好支撑了业务快速的迭代发展。但随着业务发展,以 ODP 为代表的 PHP 服务端技术栈遇到了一些问题,作业帮选择了 GO 作为主推的服务端开发语言来替代 PHP。
本文,InfoQ 与作业帮基础架构部门进行了深入交流,了解切换前后需要注意的所有问题,并分享给广大开发者和企业。
内部团队的决策过程
InfoQ:谁拍板决定切换这件事情的?
董晓聪:综合业务部门及基础架构团队的诉求,与公司的技术副总裁及以下的所有业务技术团队一起沟通相关技术问题,大家共同投票决定。
InfoQ:当时有人提过反对意见吗?
董晓聪:2020 年,Go 相对成熟,公司内部技术相关的很多人员或多或少有些 Go 的项目背景,加之 kubernetes 以 Go 语言为基础,大家对选择 Go 语言没有那么反对,但对具体哪些模块切换到 Go 上是存在讨论空间的。起初,大部分业务人员处于观望状态,期望我们可以拿出过硬的成功案例,我们从业务中台入手,改造后收益明显,越来越多的团队加入。
InfoQ:我们在这个过程中向技术副总裁或者其他更高的领导汇报过这件事情的进展吗?
董晓聪:前段时间,我们将这个迁移过程整理出来向技术副总裁做了汇报。除了性能、开发效率等层面的变化,我们从最初的云原生几乎为零到现在所有架构全部云原生化。虽然从时间周期来看,两年时间有点久,但我们做了很多切换语言层面之外的事情,比如整体服务的标准化、容器化改造以及多云体系建设。如果仅仅是单纯的语言切换,云原生基础设施支持不好依旧会拖累业务发展。
为什么用 Go 重写整个架构?
InfoQ:为什么觉得 PHP 不行需要切换新的编程语言呢?
吕亚霖:业务部门希望基础架构部门实现一套 Go 的框架以及标准,主要希望解决的问题:一是 PHP 高并发场景下表现差,作业帮的并发在几十万左右,PHP 本身是一个进程模型,资源使用率非常高,业务成本大,在高并发、高性能的部分场景表现不理想。业务方希望有一个支撑高并发的语言方式,当时选择的是 Go;二是作业帮当时正在进行云原生容器化和服务治理方向的改造,PHP 的匹配度严重不足;三是业务当时的业务技术中台在迭代建设,PHP 微服务架构支持欠缺,比如 ODP 通过 PHPLIB 耦合服务,类单体架构,服务间边界模糊,框架全局部署且缺乏现代包管理工具,而 Go 天然可以和微服务很好地结合。
业务部门提出需求之后,我们对不同的编程语言进行了调研,并拜访了美团、字节跳动等企业,了解 Go 语言的实际应用情况,由于获得的反馈比较不错,我们决定启动大规模切换。
InfoQ:字节跳动的 Go 语言使用的很不错,我们当时还找过用其他语言的企业去聊吗?
吕亚霖:因为作业帮原来的架构是用 PHP 和 C++ 语言编写的,所以也不想大规模转 Java,如果转 Java 就意味着自底向上全部重构。一般来说,选择 Java 意味着很多服务都需要接入 Java 体系,比如服务治理大多使用基于 Java 的 SpringBoot,而我们是典型的多语言栈,我们只是希望用 Go 语言替换掉 PHP 的部分,但是 C++ 语言编写的底部检索系统、OCR 等部分模块可以保留,毕竟 C++ 在底层的表现还是非常不错的。
InfoQ:为什么不选择用 C++ 统一技术栈呢?
吕亚霖:C++ 的主要问题在于开发效率不是特别高,C++ 和 PHP 的组合也是 10 年前业内比较经典的做法,上层业务逻辑的部分由 PHP 去做,底层性能的部分由 C++ 负责,达到开发效率与性能的平衡。早期,PHP 开发效率高且稳定。
InfoQ:2020 年决定迁移的时候,PHP 和 GO 的生态环境大概是什么样的?
吕亚霖:PHP 的生态环境比较成熟,社区活跃度不高,作业帮当时内部的生态和工具比较成体系化,是一个典型的 ODP 体系(ODP 最早是百度提出来的,是在“鸟哥”惠新宸开发的 PHP 框架 Yaf 基础上做的调整,整合了一些 Web 框架以外的东西,比如 Nginx、PHP 扩展等)。GO 当时的生态还处在演化过程中,GO 的包管理工具 go mod 发布了测试版本,还没有正式推出,但 Go 的社区活跃度非常高,国内外的大厂在 Web 和基础组件场景都有非常优秀的实践和落地经验。
InfoQ:当时,内部研发团队的语言背景大概是什么?
吕亚霖:基础架构团队的主语言背景是 C++ 和 Go。因为当时 kubernetes 已经流行,kubernetes 体系内均基于 Go 语言实现,我们当时也在做容器化,因此在 2019 年前后招聘了很多有 Go 语言基础的工程师。业务侧研发人员较多,语言背景主要是 PHP 和 C++,整个切换也是业务线主力修改。基础架构部主要负责 Go 的运行时、框架及生态工具的建设,给出业务线案例,并完成协助工作,比如帮助业务查问题。
重写过程遇到的坎儿
InfoQ:我们大概花了多长时间完全切换过来?
蒋帅:大概用时两年,主要分为几步:一是 DevOps 整个体系由基础架构部门管控,我们对新立项的业务模块(Web 服务)开始建议使用 Go,不提供 PHP 创建的模板;二是切换的同时,我们也在做云原生改造,对高并发性能及稳定性有要求的核心模块(F0 核心链路上的模块),我们在公司内部统一发起切换,公司级统一排期、统一验收。非核心链路大概是有半年的时间,由业务线自行排期,逐步改造并验收;最后是发布系统上半年左右没有迭代的 PHP 项目,这些模块不强制切换,业务根据需求决定。最终,我们一共完成了 600 多个服务切换。
InfoQ:对核心系统切换时,如何保证整体的稳定性?
蒋帅:主要有几个方向:一是灰度放量,这是一个典型的 AB 测试过程,PHP 编写的旧模块逐步减量,GO 编写的新模块逐步增量,多机房需要分区域逐步放量。当所有流量全部切换至新系统且经过一段时间的观察没有问题之后,再下线旧系统。二是,基于云原生提供完善的观测能力。有问题及时接入解决。
InfoQ:我们在整个切换的过程中遇到过哪些比较难的问题?
蒋帅:这里面存在一些问题:一是历史债务问题,当时 PHP 已经在内部用了很久,存在大量的历史债务问题,比如 PHP 的日志都是半结构化的 KV 结构,很难支撑大数据分析和监督告警,我们后来在 PHP 里面打了结构化和半结构化两条日志,但继承过来之后发现整个日志链路的压力会增加一倍,但因为大数据监控等依赖方的存在,这种问题一直无法彻底解决,这次切换也把该问题解决掉了。
二是快速召回升级,最初的版本很容易出现问题,这就需要快速升级到新版本,但因为一个包的使用者众多,所以难以在问题出现时一一告知升级,我们建立了反向包管理工具平台。通过 CI 分析,知道所有使用方使用的是哪个包版本。对某个版本的使用方都明确建立索引关系。
三是 PHP 自身的问题,PHP 属于弱语言类型,这种弱语言类型和特种函数带来的问题都需要在 Go 里面兼容,但实际上很多问题都是上线后发现的,比如加解密的私有库。
经验复盘及后续优化
InfoQ:Go 语言哪些优势让团队觉得比较爽?
蒋帅:一是 Go 的社区非常活跃,对个人和公司来说,有很多交流和学习的可能,包括对团队的招聘的维护都有帮助;二是高性能,很多模块从 PHP 切到 Go 之后,性能提升显著,尤其是晚高峰时的性能、吞吐量以及单核支撑的 QPS;三是功率和生态在不断演进,Go 最初的包管理工具不是特别理想,后来社区推出了公共的包管理工具,而 PHP 的社区维护、工具完善度以及生态与 Go 相比存在明显差距。
与 C++ 相比,Go 语言的开发效率更高,性能略差,但属于在性能和效率之间达到了很好的平衡。
InfoQ:编程语言的调整对架构有造成什么影响吗?
蒋帅:首先,编程语言在架构中起到承上启下的作用,承上是业务规范及研发质量,启下是云原生架构落地,只有编程语言这一层没有问题,才能很好地把上下连接起来。以 PHP 为例,如果基于 PHP 对云原生做适配,PHP-FPM+nginx 以及各种私有库依赖打出来的包动辄就是几百兆,而且同时拉起几千个 POD 很容易出现延迟的问题,在 mesh 的协议支持上也不友好。而 Go 只需要 5-10 兆,对落地容器化和服务治理都十分友好。
InfoQ:事后复盘有哪些值得优化的地方?
蒋帅:一是因为切换为 Go 语言的改动非常大,尤其是在微服务架构下,需要一个稳定、高效的开发测试环境,如果没有服务治理体系的支撑,很难在本地走完整个流程。
举例来说,如果开发人员在本地开发一个模块,该模块需要依赖调用十几个模块,这里面就存在联调测试环境和本地环境互通的问题,PHP 是通过将开发环境直接挪到服务器上来,工程师在服务器上开发来解决这个问题的,但在云原生体系下,这个问题可以通过 servicemesh 解决。
二是平台及工具支持先行,不能等业务遇到问题再去解决,这样会导致业务的体验很差。早期赶进度满足业务快速迁移的过程中,我们翻译了部分公共库的方法,但效果不太好,我们后期开发了相应的代码生成工具以及配套的迁移文档,配合业务快速完成迁移,并定期在内部进行相关宣讲及答疑。
InfoQ:后续的优化方向是什么?
蒋帅:经过两年的发展,作业帮的 GO 语言从 0 演化成服务端使用数量最多的开发语言,已有 GO 项目全部基于 ZGIN 构建(ZGIN 基于 gin 衍生而来,是面向 web 服务的开发框架,提供了开箱即用的常用组件和功能,侧重通用性和稳定性,兼顾性能和时延,构建了符合公司业务场景的生态体系)。服务模块数量达 600 余个,服务 POD 数量在 1 万以上。
面向未来,我们将继续从几个方向进行优化,一是安全方向,其他语言我们都有 RASP 的防护,我们目前在 GO 上尝试通过 eBPF 内核结合用户态来运行 RASP。
二是适当优化性能,我们现有服务器,大多数以大规格裸金属服务器(256 核)为主,我们针对特定硬件的 numa 拓扑特性做 GMP 调度优化,其次我们服务基本以容器运行为主,自动适配容器场景下 POD 的 limit。
InfoQ:我们有希望 Go 社区可以增加的特性或者优化吗?
蒋帅:一是 GO 官方团队还是优化下 GC 问题,因为我们发现有时候 GO 的 GC 频繁且耗用资源较多,我们是通过自适应 GC 优化了该场景下的问题。另一个是 Go 运行时的可观测性,我们只能在内核上通过 eBPF 去搞,但实际上这个 Runtime 的观测性应该由 Go 的相关团队负责,我们是将能力往下沉了。
InfoQ:你觉得用什么样的编程语言与研发团队的规模或者协作模式之间会有关联吗?
蒋帅:我觉得和团队规模没有关系,可能与业务场景及语言生态有关系,以作业帮为例,我们在底层检索系统、搜索引擎和 OCR 上都是 C++,面向业务的 Web 服务都是 Go,大数据用的是 Java,人工智能用 Python 的比较多,我觉得一旦公司的产研到了千人规模以上,编程语言一定会呈现多样化。但是会有一个主业务语言,可能一半的人都在用该语言。
InfoQ:你认为企业处于什么样的发展阶段做这件事情会比较合适一些?
蒋帅:越早越好。切换周期非常长,投入的人力和精力非常大,如果还没有遇到实际问题,或者说现在 PHP 用的非常好,不要跟风去切,因为我们是出于多方面考虑才做的决定。如云原生改造,降本增效也有很强诉求等等。
嘉宾简介
董晓聪,作业帮基础架构负责人,负责架构研发、运维、DBA、安全、等团队。
吕亚霖,作业帮基础架构-架构研发负责人,在作业帮期间主导了云原生架构演进。
蒋帅,负责作业帮应用技术栈方向,主要推动了ODP框架容器化改造、ODP转GO及GO框架生态的建设。