天天看點

從php5.6到golang1.19-文庫App性能躍遷之路

作者:閃念基因

導讀

introduction

本文深入淺出地分享了百度文庫App服務端技術棧從PHP遷移至Go的實戰經驗,包含了技術選型、基礎建設、流量遷移的具體方案,以及核心項目案例的重構實踐。

全文6209字,預計閱讀時間16分鐘。

GEEK TALK

01

動機

長期以來,百度文庫App服務端采用 PHP 作為主要開發語言,高效地支撐了業務疊代發展。随着平台流量的持續增長,服務端的負載越來越大逐漸接近系統瓶頸。為了提升系統的負載能力,我們采取了一些優化手段,其中最快最有效的方法是增加線上叢集的執行個體數量。此外,還采用過lua開發項目,承接一些邏輯簡單而通路量大的接口來分擔負載。由于lua本身的一些局限性,不适合做複雜的業務邏輯。

伴随着IT技術的發展潮流,我們積極響應公司降本增效的号召,決定在2022年年中遷移并重構服務端技術棧。旨在更新技術架構,提升系統負載能力。

把握技術棧遷移和項目重構的時機是很難的一件事情,特别是成熟的團隊要進行大的系統改動。如果沒有出現真正的痛點,即使研發同學認為技術實作上已經出現諸多設計不合理和有風險的地方,往往并不被允許花大量時間去做技術項目。可一旦連業務人員(産品經理、銷售、營運)也覺得系統功能需要更新的時候,比如在使用者體驗上,App文檔搜尋接口延遲比較長,産品同學認為如果首屏渲染能明顯提速的話,對點選率、付費率都會有大幅的提升,但是研發這邊基于老的技術棧已經難以做優化了,那麼此刻就很适合做遷移重構。

恰逢其時,産品同學提出一些提升使用者體驗,同時适合項目重構的需求,比如:加速搜尋結果頁首屏渲染、新App首頁,AIGC智能創作。這些大需求十分有利于遷移重構工作的啟動,它們把遷移重構所需增加的額外人力占用降到最低。在已有的功能上做遷移重構,更快更穩定的接口響應帶來流暢的使用者體驗,這有利于促進整體團隊的okr目标達成。

回過頭來,梳理下當時服務端基于php5.6的技術債務:

1、底層技術:語言版本老舊,特性落後,存在執行效率低,安全風險,資源浪費的缺陷;

2、開發質效:業務邏輯交叉耦合,大量廢棄的接口和下線的業務邏輯,降低了代碼可讀性、可維護性,持續增加項目疊代的難度。

從php5.6到golang1.19-文庫App性能躍遷之路

△技術債務

GEEK TALK

02

啟動之前的狀态

服務部署上,文庫App的服務端部署方式是nginx+hhvm(HipHop VM 3.0.1;baidu version:1.1.6 (rel)),HHVM 是 Facebook 開發的高性能 PHP 虛拟機,是傳統的nginx+php-fpm的一個性能優化版本。近年已經失去了hhvm原創團隊的持續維護疊代,它支援的文法特性和執行效率相對落後,存在一定安全風險。

檢視啟動遷移之初的服務端執行個體用量,有賴于日常運維,首先确認線上服務的執行個體cpu、記憶體和磁盤使用率在合理的門檻值内,排除了使用率較低導緻資源浪費、使用率過高會有容災風險的情況。在應用層,我們一共使用了數以千計的php5執行個體。

GEEK TALK

03

遠景

重構的投入與回報并非呈線性關系。

—— 《領域驅動設計:軟體核心複雜性應對之道》

直覺的說,我們希望服務端更新能帶來更少的代碼,更穩定的系統,更高的品質效率,更佳的使用者體驗。這展現在下面幾點:

1、技術更新:采用先進的語言架構,支撐項目高效疊代提供強勁底層引擎、安全性和成熟的應用生态;

2、改善設計:梳理代碼邏輯,治理備援,解決代碼中的壞味道,建構高複用、低耦合、可擴充的業務架構;

3、降本增效:一方面底座更新,提升代碼執行效率,降低平響,提升服務可用性、可觀測性;一方面在運維實踐上,合理配置設定容器執行個體的cpu,記憶體和磁盤的配額,優化資源效能。

服務端更新的成功與否,可以從兩個方面來努力達成,分别是技術棧遷移和改善既有代碼設計的重構。

GEEK TALK

04

做技術選型

我們不打算使用較為小衆、生态孤立的語言作為文庫App服務端的技術棧。同時參考兄弟團隊的技術棧更新方向,最終進入技術選型決賽圈的是兩種廠内架構,基于php7的odp3架構(Online Develop Platform)和基于go的gdp2架構(Go Develop Platform)。

從php5.6到golang1.19-文庫App性能躍遷之路

選項一:PHP7架構和Phaster

PHP7架構是公司釋出的線上業務開發平台,其提供了标準的webserver環境、标準php環境、AP架構、基礎庫、資源通路層、通用服務等元件,統一業務的邏輯和部署結構。架構的亮點在于Phaster。Phaster能讓你使用PHP語言開發高性能的Http、Fastcgi、Nshead服務,進行高性能的RPC調用,以極低的成本實作業務代碼并行化。

Phaster和其它業界架構的對比如下。

從php5.6到golang1.19-文庫App性能躍遷之路

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 項目都有使用,共有幾千項目使用)。

從php5.6到golang1.19-文庫App性能躍遷之路

綜合對比以上對兩種架構的特點和落地的可行性等因素,最終我們更傾向于向GDP架構遷移。

GEEK TALK

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靈活,但是更嚴謹,更安全,可以在編譯階段檢查出來隐藏的絕大多數問題。

從php5.6到golang1.19-文庫App性能躍遷之路

△類型轉換

另一方面,重構項目如何治理陳舊代碼?概括的說,可以參考《重構-改善既有代碼的設計》一書提出的 23 種代碼壞味道,有針對性地對代碼進行重構,馴服成整潔和易于閱讀的代碼。

從php5.6到golang1.19-文庫App性能躍遷之路

把前期調研和遷移政策确定好了,實際的代碼開發變得得心應手。在遷移老接口流量的時候,我們需要在新接口用go重新實作一遍,調用方式上完全等同老接口,包括path、method、驗簽、header規則、參數結構、響應結構、錯誤碼。隻有應用層上的虛拟域名不同。

5.4 品質保障

代碼ready了,差別于php項目的正常測試流程,go不能繞過性能測試。因為我們寫php幾乎不需要關注GC和記憶體洩露,但是go需要,有時候手動測試和黑盒測試是OK的,但是到線上遇到有一定并發的業務場景,就會暴露問題,常常表現為執行個體的cpu或者記憶體使用率持續上漲,直至當機。

應對go的記憶體洩露問題。一方面需要在測試流程中增加壓測環節;一方面需要日常多關注一下監控儀表盤的執行個體資源使用率、接口平響、穩定性名額是否符合預期,因為有的隐藏bug即使壓測也不能覆寫到。這時需要提升go服務的可觀測性,以便及時發現風險。

Go品質保障能力全景矩陣如下:

從php5.6到golang1.19-文庫App性能躍遷之路

建構線下品質保障能力:

從php5.6到golang1.19-文庫App性能躍遷之路

建構線上品質保障能力:

從php5.6到golang1.19-文庫App性能躍遷之路

5.5 流量遷移

從php5.6到golang1.19-文庫App性能躍遷之路

如上圖所示,go項目上線後,實際流量還在老項目承接。開始做流量遷移,使用者流量首先到達接入層,在這一層我們根據不同的通路域名和路由,分流到不同的應用層load balancer ,為了相容老版本的App,需要在域名路由不變更的情況下,完成流量遷移。在接入層網關做分流,把分流到php的規則應用到go應用層load balancer 上,就完成了流量遷移。注意,如果是非常核心的接口,我們需要進行灰階釋出,可以采用nginx+lua的方式實作,或者采用著名的開源網關ApiSix、BFE項目,它們都支援灰階釋出。

5.6 核心功能重構實踐

這次重構比較突出的亮點,展現在百度文庫App的全新首頁和搜尋結果頁優化。

(1)定制化新首頁

文庫使用者個性化需求較分散,希望通過将垂類使用者内容需求共性抽象,對連續型特征且使用較高的内容進行提取,采用中心化集中推薦的方式,提高使用者垂類内容結構化滿足,進而提升使用者留存率及續費意願。重構了App首頁的布局和内容展示。增加了個性化的『我的資料庫』,『教學進度』,『推薦頻道』,定制化展現文檔榜單和檔案夾榜單。

App新首頁的技術方案是全新的,重構的動機來自"業務驅動",而非"品質驅動"。需求實作上,底層不依賴php老項目。是以直接在go項目開發上線,提供接口服務。這樣上線後,go自然替換掉了php原本承接的首頁流量。

從php5.6到golang1.19-文庫App性能躍遷之路

△文庫新首頁

(2)搜尋結果頁優化

服務端這邊主要重構對象是一個搜尋接口,實際開發中,和産品溝通是否可以下線不要的tab清單和内化的推薦邏輯;清理多處已經下線的AB實驗的業務邏輯,去掉已經推全AB實驗的代碼判斷;優化文檔排序算法,和産品、前端同學對齊目前必須的字段,去除備援;善用協程優化串行邏輯。

結合前端去除懶加載代碼,圖檔本地化,使用端能力緩存接口資料,搭建離線包服務等技術手段,搜尋結果頁優化取得了不錯的成果。大幅降低搜尋結果頁的加載速度,安卓平均降低延遲41%;IOS平均降低延遲43% ,搜尋結果點選率和成交的訂單量也有一定提升。

從php5.6到golang1.19-文庫App性能躍遷之路

△【新老搜尋結果頁】AB實驗時的白屏時長統計

GEEK TALK

06

目标達成

從2022年8月啟動go遷移至今,接近完成App服務端的全部流量遷移工作。

1、技術疊代:得益于go語言特性先進、記憶體管理和豐富的生态,提升了代碼執行效率、安全性和可觀測性;通過梳理業務邏輯,治理備援,清理代碼中的壞味道,封裝公共類等方法,提升品質效率,代碼可讀性和可維護性;

2、提升性能:一方面通過協程、通道技術改變同步阻塞的代碼執行方式;另一方面,編譯後的二進制檔案執行效率遠高于nginx+php-cgi的網絡模型。平均減少了約30%的接口耗時,TP90減少了35%的耗時;

3、降本增效:得益于go語言高性能的特性,應用層執行個體的負載能力得到提升。流量遷移後,文庫App服務端線上叢集縮減了約50%的的執行個體數量。

GEEK TALK

07

思考與總結

1、手機App屬于CS架構的應用,在遷移過程中要保證老版本Client可以使用服務;

2、在面對一個長期項目時,拆解目标是很重要的,快步試錯即時回報也是網際網路思維的一部分;

3、遷移理論無損,但需要把風險同步pm同學,及時關注各業務名額,同時制定預案,保證可復原的靈活性;

4、接口剛上線或者AB實驗推全後,遷移的接口流量上升,要養成經常觀察可用性儀表盤的習慣,及時處理http status異常的問題,避免風險擴大化為故障。

GEEK TALK

08

結語

知而不行,是為不知;行而不知,可以緻知。

回想項目遷移重構的整個過程,最有意思的是做技術選型和讨論流量遷移具體實行方案的起步階段,那時面對臃腫龐大的php單體項目如何進行遷移,是有些迷茫的。在實踐的摸索過程中逐漸加深對項目的了解,通過所得的啟發來推導制定下一步的行動,形成正向循環。希望本文的内容對大家的工作實踐有所幫助。

作者:百度文庫App

來源:微信公衆号:百度Geek說

出處:https://mp.weixin.qq.com/s/xASk7bjfC-O3lbQ_nccKNA