腾讯广告 3000+万行大代码库主干开发实战
作者:phongchen,騰訊 CDG 后臺開發工程師
互聯網行業競爭激烈,產品迭代快,其中研發效能越來越成為跑贏競爭對手的重要影響因素。需求兩天就能上線和兩個星期才能上線,結果可能大相徑庭。本文總結了騰訊廣告系統主要采用的目前業界標桿公司引領的單代碼倉庫+主干開發+城際快線發布模式,供大家參考,以此作為對我個人兩年多以來專職從事工程效能工作的一個總結,也歡迎大家多提寶貴意見。
基本概念
單一代碼倉庫
相信很多人都看過這篇文章:
其實不止 Google,硅谷很多大大小小的公司,比如 Facebook,Twitter 也都是用的單一代碼倉庫。但是如果把全公司的代碼放在一個倉庫里,對代碼管理系統是一個巨大的挑戰。
Google 用的是自己開發的piper系統:
https://medium.com/@gitship/which-version-control-tool-is-google-using-how-is-it-proving-useful-for-it-7fbde4296fbf
Facebook 為此魔改了 mercurial 代碼管理系統:
https://engineering.fb.com/2014/01/07/core-data/scaling-mercurial-at-facebook/
而 Microsoft 則選擇了在 git 上做文章,開發了 git 虛擬文件系統:
https://docs.microsoft.com/en-us/azure/devops/learn/git/git-at-scale
項目倉庫管理模型:單項目單倉庫、多項目多倉庫與單倉庫多項目為什么這些公司都選擇了單一代碼倉庫呢?簡單說來,單代碼倉庫的主要好處有:
方便代碼復用,減少重復勞動
方便跨語言共享代碼,提高開發效率
讓有問題的代碼盡早暴露,降低修復成本
方便實施原子提交,減少 CI 失敗
方便實施代碼重構,減慢代碼腐化
不用考慮兼容舊版本,消除了可怕的菱形依賴
方便實施統一的工具檢查,減少不同技術團隊重復建設
方便實施主干開發模式
有工蜂加持,雖然我們搞不定全公司的單倉,但是經過努力,保持了在廣告系統內半個 BG(AMS)單倉的持續運轉,也頗不容易,背后的故事值得一說。
主干開發模式
主干開發,顧名思義,就是在主干上開發。但是主干開發并非完全不要分支,在 SVN 時代,我們確實是在主干上直接修改、發 CR、提交的,但是在切換到 git 后,我們用短生命周期的分支(變更不超過數百行,工作量不超過一天)來實現主干開發。
分支開發模式與主干開發模式在主干開發分支模式下,還要有發布分支,發布評審系統自動/手動從主干上合適的點拉出發布分支,保持其相對穩定,只有 bugfix 和緊急需求才會從主干上 cherry-pick 的方式合入新的變更。
主干開發模式下,配合持續集成系統,可以得到最快的反饋速度,有 bug 的代碼很快就會被發現,也更容易被定位和修改。當然如果缺乏足夠的保障機制,也更容易引發問題。
單一代碼庫往往采用主干開發模式,因為在大量開發人員頻繁修改的大代碼庫上合并長期分支的難度是地獄級的,而主干開發模式離開單一代碼庫,其價值也大打折扣。
城際快線發布模式
在喬梁老師的《持續交付 2.0》中,從特性、時間、質量三個角度總結了三種發布模式:
? 項目制發布模式(Project Release Mode) 先規劃好版本的功能點和期限,比如 Windows 的各個版本。? 傳統版本火車模式(Release Train Mode) 按季度/半年交付,比如 JDK 半年一個版本。? 城際快線模式(Intercity Express Mode) 兩周~一天的發布節奏,甚至更快。
其中城際快線發布模式是目前最適合互聯網產品節奏的發布模式。
軟件發布模式之“不可能三角”我們的實踐
我們經過長期的學習摸索實踐以及工具平臺建設,在廣告系統內實現了單一代碼庫主干開發,城際快線發布模式。支持了重要的核心業務模塊一天一發甚至一天多發(不少服務因為業務需求沒那么多本身發布頻率較低,按需發布的模式更合適),有力地支持了業務需求的快速上線。
代碼庫管理
廣告系統內并非只有一個代碼倉庫。不過至少后臺部分的絕大部分代碼都在一個叫 main 的倉庫里,這個代碼庫的規模:
只統計了最常見的幾種語言
包含第三方代碼
不包含生成的文件
Git 的挑戰
目前代碼庫 clone 后占 37GB,其中.git 目錄占 15G。我們的代碼庫原來在 SVN 上,遷移 git 前,發現直接遷移問題比較大,分析后發現有不少二進制文件(歷史遺留原因),采取了如下措施:
截斷歷史,只保留到 2019 年 7 月 1 日,之前的仍然到 SVN 上去查。
使用 LFS 存放大文件,并且通過下述 legit 工具限制大文件必須用 LFS。
在代碼評審和合并階段控制大文件進入。
工蜂團隊對廣告系統代碼庫從 SVN 遷移到 GIT 做了大量的服務、幫助和指導,特此感謝。
用 OWNERS 文件控制權限
由于 GIT 的權限控制模型,在單一代碼庫下每個用戶要么完全沒有權限,要么只讀,要么可以讀寫。我們鼓勵“代碼共有”,但是不同的項目和子項目還是要有明確的負責人。
我們用 OWNERS 機制來管理代碼權限:
左側是我們用的分散式 OWNERS 文件,可以放在不同的目錄之下,右側是 github 上支持的倉庫級別的 CODEOWNERS 文件,顯然不適合大倉庫。
OWNERS 機制并非原創,而是來自 Google(在 Chromium 和 k8s 項目中也能看到它的影子,我們參考的是Chromium 項目的語法并做了擴充,而k8s 則用的 YAML 格式,看起來更好一些,也許考慮將來跟進),公司的 SVN 系統上本來也支持類似的語法,不過工蜂系統里不再支持了。因此我們遷移到了新的語法,并在底層換成了下述的 legit 來保證實施。
通過 OWNERS 文件,即使你不是某個目錄或者文件的 OWNER,只要其 OWNERS 通過你的 CR,就能修改其中的代碼,實現了代碼的可控公有。通過 OWNERS 文件,也能明確項目各自的歸屬,方便后續做各種數據度量和驅動工作。
保密代碼構建系統
雖然我們確保代碼盡量開放,但是作為涉及到大量利益的商業產品,廣告系統其實也是有一些代碼需要保密的,比如一些重要的算法、策略等,這部分代碼不開放給所有的人。但是我們不希望把這些代碼編譯成庫放到代碼庫里,這不但可能導致二進制兼容問題(我們的基礎庫只保證源代碼兼容不保證二進制兼容),而且還會成為編譯器升級的障礙,并增加代碼丟失的風險。
因此我們把這些代碼放在多個分散權限控制的倉庫里,并且通過保密代碼構建系統在有專用的服務器上用專用賬號構建這些代碼,并返回二進制的構建結果(.o 文件)。
保密代碼構建系統保密代碼在遠程編譯時,使用的是開發者本地的頭文件,從而確保二進制兼容性。
質量保證體系
代碼評審
騰訊廣告系統從 2013 年就強制要求代碼評審,養成了良好的技術氛圍,做到了"讓 CodeReview 成為一種習慣"。我們在 legit(下面會詳細描述)上加入了 Push 前的代碼檢查,攔截了大量有問題的 Push,讓評審者看到的代碼基本上沒有這些低級問題,提高了評審的質量和效率。
Legit 一個月內攔截了大量有問題的 push* 由于 legit 對 js 檢查還有些欠缺,最左側的中心更依賴藍盾上的 codecc 工具,因此數據表現有差異。
持續集成
因為代碼庫較大,拆成了多個 premerge 流水線以增加并行度,premerge 失敗的 MR 不能合入主干。
CI premerge hook MR單元測試
我們很早就要求寫單元測試,通過 OWNES 文件自動識別模塊歸屬,落實到各個中心/小組。
UT月報示例回歸測試
我們開發了 Lego 的系統進行環境搭建回歸測試。lego 基于容器技術,能夠在服務調度的拓撲圖中根據需要創建出一套隔離的子集環境。
Lego環境自動管理平臺基于 lego 系統,測試團隊搭建了自動化回歸平臺,可以對每次代碼合入對請求和回應的影響做出精確對比,方便業務開發人員確認是否有問題。
潘多拉的Diff確認功能特性開關
主干開發模式下,代碼庫中會有開發中的特性,這些特性并不適合發布到線上。業界通常用特性開關(FeatureFlags)來解決這個問題。
特性開關在主干開發中的應用特性開關系統本質上只是一種工程實踐,可繁可簡。最簡單的實現其實就是一個 bool 類型的配置項。在過去我們用 google gflags 庫來實現特性開關。功能開啟或者關閉,修改后重發配置文件即可(可能要重啟)。我們的服務框架也支持通過 HTTP 請求修改。
規范地講,特性開關的生存期不應該太長,否則會引起“特性開關技術債務”,因為每個有效的特性開關在代碼中都至少對應著一條 if 語句,引起函數的圈復雜度增加。過于已經固化的特性,應當限期清理掉。但是實際上卻有很多特性開關不敢輕易刪除。
大量早已應該清理的過期開關其中原因之一,就是業務開發人員如果不是對代碼特別熟,可能不知道這個開關是否真的還在使用,貿然刪除可能會導致錯誤乃至事故。如果每個開關都引入監控項,使用成本又增加了很多。
因此我們開發了在線的特性開關管理系統。對每個開關的訪問情況作出統計。
騰訊廣告特性開關管理系統系統在開關快要到達期限前,就會自動通過企業微信通知其開發人員刪除,并且在真正刪除之前也會檢查是否還有有效的請求。
特性開關管理系統也支持以配置文件為主的方式,此時在線系統就成為特性開關的查看和監控系統,了解系統有多少特性開關,使用情況如何。
特性開關系統也支持對一些上下文參數(IP,用戶 id 等,廣告位 id)等通過開發人員配置的表達式動態判斷是否開啟。
發布評審和自動發布
我們開發了 LeFlow 發布評審系統和自動發布系統。
每日例行發布特性開關確認TAPD需求確認支持以手動和自動兩種模式拉起發布評審,評審確認后,可以自動部署到發布系統中。目前后臺需求頻繁的幾個關鍵模塊,比如 mixer(業務邏輯總管服務)、sunfish(廣告檢索系統)做到了每天一發,投放端有些模塊甚至做到了按需發布。
效率優化
只有質量,沒有效率顯然也不行。我們在效率方面也做了大量的工作。
開發效率
開發環境升級
我們會不定期地跟蹤開發工具升級,確保代碼庫能用編譯器構建。比如 C++編譯器:
在單一代碼庫主干開發模式下,讓代碼庫適應新版編譯器是一件很容易的事情,只需要花點時間修改所有新編譯器引發的構建錯誤,同時跑兩套持續集成系統,從一些模塊開始試用新版編譯器發布上線,過一段時間全都換到新版編譯器即可。2018 年那次,luobogao 同學幾小時就改完了所有的受影響的代碼,花了兩個星期觀察后就全面替換了,如果沒有單一代碼庫這是很難做到的。
基礎庫
為何大家都覺得 Python 效率高?因為他有大量方便易用的基礎庫。廣告系統大量使用 C++和 Java 代碼,Java 有龐大的 maven 倉庫,但是 C++就慘了點,因此我們開發了一系列的基礎庫(包含字符串處理,加密解密,壓縮編碼,網絡庫,HTTP 庫等等),幫助業務開發盡量能用更少的代碼開發業務邏輯。
第三方庫
代碼庫中充斥著大量不同版本的第三方庫是代碼管理混亂的重要特點,我們不允許這種事情發生,制定了第三方庫管理規范,嚴格把控第三方庫的使用、升級、舊版清理。
開發框架
我們開發了基于現代 C++風格的低延遲高性能服務框架——Flare,簡化了業務代碼開發的難度,保證了質量。
代碼清理
要保證代碼庫的健康,就需要經常檢查代碼庫中的問題。廢棄的代碼要清理,break 的構建要修復,編譯器升級可能要改代碼,工程效能團隊要有權力、有自信、有勇氣改動業務的代碼。
比如最近半年我刪的代碼比改的多構建效率
Blade 優化
Blade 構建系統是我主導開發的(也是主力開發者之一),2013 年廣告系統開始使用 Blade,而微信用的是很早就 fork 出去后自己做了大量修改過的 Blade 系統。
2017 年,由于構建速度的問題,微信選擇了改用 Bazel,花了 2 年多時間才完成。
我們則選擇了優化了 Blade 構建系統(原因之一是那時候的 Bazel 還很不成熟,其次是確實沒有動力和人力去改已經存在的幾千個 BUILD 文件),包括用 Chrome 和 Android 的構建后端 ninja 替換了原來的 scons 后端,大幅度優化了構建代碼生成等等多種手段,讓整個代碼庫全量構建的啟動時間從幾分鐘縮短到二十秒。根據 luobogao 在 Yadcc 上的測試,Bazel 在數百個進程并發構建下的調度和 CPU 占用表現都不如 ninja。
Yadcc 分布式編譯器
當代碼庫規模很大時,又基本是全源代碼編譯,一個底層的被依賴庫的變動(比如基礎庫和基礎的 pb 文件)就會引發大量的編譯。因此我們在調查了一些業界的解決方案,包括開源項目后,開發了 Yadcc 分布式編譯器。
YADCC系統架構最大的特點是除了我們自己的機器資源外,我們還可以把使用者的計算資源也納入到集群中,因此用戶越多反而越快。目前集群已經有 2000 多核,再加上高命中率的緩存,能夠輕松支持 CI 和本地開發環境中大量單個構建批次上并發任務高達 500 多的任務的并發構建。目前只支持 C++,但是我們正在開發對 Java 和 scala 的支持。
代碼評審效率
如果代碼評審不夠效率,那么開發人員的工作時間就就白白損失在等待上。
為了提高代碼評審的效率,我們開發了 Legit Git 增強工具,具備以下功能:
? 分支管理,包括命名規范和過期清理 ? 自動代碼檢查 ? 增強代碼評審 ? 自動合并通過的 MR ? 自動催辦待評審變更
legit 已加入《代碼工具平臺》Oteam。
legit命令行界面legit代碼檢查自動上報工蜂Legit 內置 14 種代碼檢查工具,包括公司代碼規范尚未覆蓋的 scala,bash,markdown 等,其中 protobuf,bash 以及 cpplint 的增強版是我自己寫的。每個歷史悠久的倉庫都免不了有不少技術債務,我們采用了主要檢查增量代碼的策略。對于可能比較難以解決的問題,允許繼續 Push,展示在工蜂頁面上,供人工審查和討論。
legit會給MR自動打標簽Legit 創建出來的 MR 會有整齊劃一的標題以及漂亮的標簽,以方便評審人員。比如我們給 MR 的變更規模設置了 XS-XL 的標簽,就能方便利用碎片時間快速評審一些小的變更。而把大塊時間留給較大的變更。而 LINT-ERROR 標簽提醒評審人員注意確認 MR 中的代碼檢查問題。
我們強烈不鼓勵大規模的單次變更,不但入職培訓會講,而且大的變更會自動引發評審委員會的評審,沒有合理的理由會被駁回,所以現在也來越少了。
數據收集,度量與推動
我們在整個流程的收集了大量數據,然后有專業而負責的 QA 團隊輸出各種報表,和各個業務研發團隊對齊目標,推動其改進。比如上面的 UT 覆蓋率只是其中一種。不過度量這部分目前做的確實還不好,有待加強。不過要強調的是,數據采集出來,主要用作對研發流程效率提升的分析工作,一定要謹慎考慮是否能納入度量體系,以免起到負面引導作用。
未來的挑戰
廢棄代碼與廢棄模塊
在多倉庫模式下,廢棄代碼會自然淘汰。但是在單一代碼倉庫下卻不會,需要結合變更歷史和發布系統來識別定位廢棄模塊。
依賴債務清理
不必要的依賴,會造成構建速度變慢,不必要的構建和測試增多。需要通過構建系統結合源碼分析技術來識別并清理。這對 C++相當的困難。
代碼庫存儲空間問題
由于 git 的天性,需要開發者本地保存整個代碼庫的 clone,即使有稀疏檢出,檢出深度控制等,效果也不明顯。如果代碼倉庫繼續變大,可能就需要引入虛擬文件系統,虛擬大倉庫等做法。有些公司比如 twitter 采用了截斷歷史的做法。
總結
我們學習業界先進實踐經驗,結合自身特點,在廣告系統的范圍內實現了單一代碼庫主干開發,城際快線的發布模式,在研發效能上達到了比較先進的程度。這離不開團隊小伙伴的努力和業務團隊的配合,但是由于人力/經驗等的限制,和業界做的最好的公司比,我們依然有很多需要提高的地方,在整個公司范圍內統一這種模式,目前看更是遙不可及。但是對于有較強技術把控能力的團隊,建議嘗試這種模式。
騰訊技術在此呈現:
總結
以上是生活随笔為你收集整理的腾讯广告 3000+万行大代码库主干开发实战的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 火速围观!鹅厂中间件产品遭遇暴风吐槽
- 下一篇: MySQL 深入学习总结