作者介紹
馬陽(yáng)陽(yáng):去哪兒網(wǎng)基礎(chǔ)架構(gòu)組資深開發(fā)工程師、公司云原生 SIG 成員,專注于云原生、效能領(lǐng)域。負(fù)責(zé)組件市場(chǎng)、測(cè)試環(huán)境治理平臺(tái) Noah、代碼瘦身平臺(tái)。優(yōu)化過(guò) Noah,使環(huán)境構(gòu)建成功率提升 35%、耗時(shí)降低 30%。22 年深度參與的“線上服務(wù)瘦身50%”項(xiàng)目獲得公司級(jí)技術(shù)型一等獎(jiǎng),指導(dǎo)多個(gè)團(tuán)隊(duì)完成系統(tǒng)精簡(jiǎn),積累了大量經(jīng)驗(yàn)。畢業(yè)后曾就職于 BES,從事 PaaS 云、DevOps 平臺(tái)開發(fā)工作。
一、背景
去哪兒早在 05 年就開始做機(jī)票相關(guān)業(yè)務(wù),在這十幾年間業(yè)務(wù)快速發(fā)展,后端系統(tǒng)也在不斷迭代,目前公司內(nèi)擁有大量五年以上的系統(tǒng)。為適應(yīng)快速變化,公司的組織架構(gòu)調(diào)整和人員流動(dòng)較多,導(dǎo)致目前開發(fā)者們維護(hù)的系統(tǒng),大部分都是交接過(guò)來(lái)的,對(duì)系統(tǒng)全貌掌控力有限。
另一方面,公司的很多業(yè)務(wù)具備短周期特征,比如臨近五一假期了,我們會(huì)在去哪兒旅行 App 上增加五一相關(guān)的活動(dòng)業(yè)務(wù)。一旦五一假期結(jié)束后,這些業(yè)務(wù)會(huì)立刻被下掉,不再產(chǎn)生實(shí)際價(jià)值。而對(duì)應(yīng)的后端代碼并不會(huì)清理,這就產(chǎn)生了一個(gè)有趣的現(xiàn)象,我們的代碼只增不減、系統(tǒng)復(fù)雜度單調(diào)遞增。
隨著時(shí)間的推移,維護(hù)和優(yōu)化現(xiàn)有代碼的耗時(shí)會(huì)越來(lái)越多,導(dǎo)致開發(fā)新業(yè)務(wù)的時(shí)間減少,業(yè)務(wù)開發(fā)越來(lái)越慢,跟不上業(yè)務(wù)發(fā)展的速度,急需解決!
從數(shù)據(jù)上看,公司有數(shù)千個(gè)常用應(yīng)用,數(shù)千萬(wàn)行代碼,平分到每個(gè)開發(fā)者頭上,人均要維護(hù)多個(gè)應(yīng)用、十幾萬(wàn)行代碼,可以感受到這個(gè)維護(hù)負(fù)擔(dān)是比較重的。
如何解決維護(hù)成本高、業(yè)務(wù)迭代慢的痛點(diǎn)呢?我們?cè)谌ツ耆觊_展了系統(tǒng)瘦身項(xiàng)目,制定了兩個(gè)目標(biāo)指標(biāo),對(duì)全司代碼、全司應(yīng)用均精簡(jiǎn) 50%,也就是都砍掉一半。要完成這個(gè)目標(biāo),有三個(gè)很大的挑戰(zhàn):
- 目標(biāo)定的非常高,需要在一年內(nèi)刪除掉千萬(wàn)余行代碼;
- 這是全司級(jí)別的項(xiàng)目,牽扯幾十個(gè)業(yè)務(wù)團(tuán)隊(duì)、幾千個(gè)線上應(yīng)用,如何在這么廣的影響范圍下,高效、低風(fēng)險(xiǎn)地達(dá)成目標(biāo),將是個(gè)挑戰(zhàn);
- 網(wǎng)上找不到可以參考的案例,這就要求我們要有很強(qiáng)的創(chuàng)造力和技術(shù)實(shí)力。
要實(shí)現(xiàn)這個(gè)目標(biāo),我們做了兩個(gè)規(guī)劃:
- 時(shí)間上,分兩階段,從 5 月份到 6 月底進(jìn)行服務(wù)精簡(jiǎn),從 7 月初到 11 月底進(jìn)行代碼精簡(jiǎn)。之所以服務(wù)放在前面,是因?yàn)榉?wù)精簡(jiǎn)的同時(shí)也會(huì)帶來(lái)代碼的精簡(jiǎn);
- 架構(gòu)上,將整個(gè)目標(biāo)分配到各個(gè)業(yè)務(wù)線,每個(gè)業(yè)務(wù)線完成 50% 精簡(jiǎn)的目標(biāo),分而治之。另外,創(chuàng)建了一個(gè)虛擬組織“瘦身技術(shù)支撐團(tuán)隊(duì)”,提供通用瘦身工具和技術(shù)支持,加速業(yè)務(wù)線的瘦身效率。本人也是該虛擬組織中的一員。
接下來(lái)依次對(duì)服務(wù)精簡(jiǎn)、代碼精簡(jiǎn)做詳細(xì)介紹,會(huì)先分析可以被精簡(jiǎn)的資源有什么通用特征,然后利用這些通用特征批量、自動(dòng)化地找到可能能被精簡(jiǎn)的資源全集,這個(gè)過(guò)程稱之為 “找得到”;有了目標(biāo)全集之后,進(jìn)行正真的精簡(jiǎn),也就是要 “刪得好”。
二、服務(wù)精簡(jiǎn)實(shí)戰(zhàn)
1、可精簡(jiǎn)服務(wù)特征分析
在進(jìn)行服務(wù)精簡(jiǎn)實(shí)戰(zhàn)之前,需要先分析出可精簡(jiǎn)服務(wù)所具備的特征,有了一些通用的特征后,就能根據(jù)特征批量找到全部的目標(biāo)服務(wù)。
服務(wù)精簡(jiǎn)的手段有兩種,合并服務(wù)和刪除服務(wù),這兩種手段所處理的服務(wù)特征不一樣,因此分開進(jìn)行分析。
合并服務(wù)
哪些服務(wù)能進(jìn)行合并?想直接回答這一問(wèn)題并不容易,可以反過(guò)來(lái)思考,哪些服務(wù)要進(jìn)行拆分?開發(fā)同學(xué)對(duì)微服務(wù)拆分都很熟悉,常見拆分原則有單一職責(zé)、界限上下文、復(fù)用性、自治性等等,常見服務(wù)拆分維度有 “業(yè)務(wù)” 和 “質(zhì)量”,根據(jù)業(yè)務(wù)拆分的常見手段包括:
- 按業(yè)務(wù)域:賣票和售后屬于不同業(yè)務(wù),在微服務(wù)架構(gòu)中考慮拆分成兩個(gè)微服務(wù)
- 按業(yè)務(wù)流程:對(duì)于訂單業(yè)務(wù),按流程可以拆分成 生單、計(jì)費(fèi)、支付、物流、通知 等多個(gè)子服務(wù)
- 按業(yè)務(wù)重要性:訂單業(yè)務(wù)一般是核心業(yè)務(wù);評(píng)價(jià)業(yè)務(wù)則沒(méi)那么重要,所以訂單和評(píng)價(jià)通常是兩個(gè)微服務(wù)
根據(jù)質(zhì)量拆分的常見手段包括:性能、穩(wěn)定性、可用性、安全邊界、異構(gòu)等。
理論上,規(guī)避了上述服務(wù)拆分特征的服務(wù)就允許進(jìn)行合并。
判斷當(dāng)前服務(wù)是否符合服務(wù)合并的特征,需要對(duì)服務(wù)所提供的業(yè)務(wù)有深刻理解才能進(jìn)行判斷,難以通過(guò)已有的數(shù)據(jù)資產(chǎn)分析得出,只能是系統(tǒng)開發(fā)者進(jìn)行判斷,因此服務(wù)合并主要由各業(yè)務(wù)線自行推進(jìn)。
刪除服務(wù)
哪些服務(wù)能被直接刪除?從業(yè)務(wù)價(jià)值角度很容易得出結(jié)論,無(wú)價(jià)值甚至低價(jià)值服務(wù)可以進(jìn)行刪除,更為具體的表現(xiàn)有三種,滿足其中一種就可能是低價(jià)值服務(wù),注意是 “可能而非一定”:
- 沒(méi)流量:服務(wù)雖然在線上,但沒(méi)有業(yè)務(wù)流量,業(yè)務(wù)價(jià)值自然是低的
- 不迭代:這個(gè)特征不好理解,可以通過(guò)價(jià)值模型加深理解。價(jià)值分為 “存量?jī)r(jià)值” 和 “增量?jī)r(jià)值”,服務(wù)一旦沒(méi)有迭代,就沒(méi)有了增量?jī)r(jià)值。先利用這個(gè)特征將服務(wù)掃出來(lái),再人工判斷一下其存量?jī)r(jià)值,如果存量?jī)r(jià)值較高那么就不做刪除,反之則反
- 已下線:服務(wù)已經(jīng)下線了,但沒(méi)有被刪除
判斷當(dāng)前服務(wù)是否可被刪除,是可以通過(guò)已有數(shù)據(jù)分析得出的,因此作為瘦身基礎(chǔ)支撐團(tuán)隊(duì),提供了兩個(gè)通用工具,第一個(gè)工具的作用是 “找得到”,即找出符合特征的服務(wù)全集;第二個(gè)工具的作用是“刪得好”,自動(dòng)化將服務(wù)進(jìn)行刪除。最終達(dá)到快速刪除服務(wù)的作用,下面對(duì) “找得到”、“刪得好” 進(jìn)行介紹。
2、找得到
現(xiàn)在需要按照之前分析得出的特征,通過(guò)已有數(shù)據(jù)資產(chǎn),批量找到符合特征的服務(wù),這些服務(wù)就是最終要?jiǎng)h除的備選目標(biāo)。
沒(méi)流量
將流量全集分為三類:
- 南北方向流量:南北方向流量通常是指從客戶端到服務(wù)器端的流量,也就是來(lái)自于外部用戶或者應(yīng)用的請(qǐng)求流量,它通過(guò)負(fù)載均衡器或API網(wǎng)關(guān)被路由到不同的微服務(wù)實(shí)例上
- 東西方向流量:東西方向流量通常是指微服務(wù)之間的內(nèi)部通信流量,也就是微服務(wù)之間的請(qǐng)求和響應(yīng)流量,這種流量通常發(fā)生在內(nèi)部微服務(wù)的調(diào)用或者微服務(wù)之間的數(shù)據(jù)交換過(guò)程中。這里我將東西間流量進(jìn)一步細(xì)分,拆成兩類:
- 東西方向 – 服務(wù)之間流量
- 東西方向 – 單服務(wù)內(nèi)流量
三類都沒(méi)有流量則可以認(rèn)為服務(wù)是完全沒(méi)有流量的,盡可能保證判斷的全面性。
針對(duì)每種類型,可以通過(guò)分析現(xiàn)有數(shù)據(jù)資產(chǎn),來(lái)得出是否有特定類型的流量。
- 根據(jù)網(wǎng)關(guān)的歷史訪問(wèn)日志,可以找出一段時(shí)間內(nèi),沒(méi)有南北向流量的服務(wù)集
- 根據(jù)歷史 Trace 的拓?fù)湫畔?,可以分析出一段時(shí)間內(nèi),沒(méi)有東西向服務(wù)間流量的服務(wù)集
- 最后,根據(jù)服務(wù)是否有生效的定時(shí)任務(wù),可以找出沒(méi)有服務(wù)內(nèi)流量的服務(wù)集
最終對(duì)這三個(gè)集合取交集,即為全部 “沒(méi)流量” 的服務(wù)。
不迭代
從業(yè)務(wù)上看,不進(jìn)行服務(wù)迭代有兩種可能,業(yè)務(wù)非常穩(wěn)定了不需要迭代、業(yè)務(wù)不值得再花時(shí)間迭代。無(wú)論哪種情況,不迭代的具體表現(xiàn)是沒(méi)有變更,同時(shí)滿足下面兩種情況我們就認(rèn)定服務(wù)是無(wú)變更的:
- 代碼無(wú)變更:根據(jù)發(fā)布系統(tǒng)中的發(fā)布記錄,找到一段時(shí)間內(nèi)沒(méi)有代碼發(fā)布、或發(fā)布次數(shù)很少的服務(wù)
- 配置無(wú)變更:根據(jù)分布式配置中心的變更記錄,找到一段時(shí)間內(nèi)沒(méi)有配置發(fā)布、或發(fā)布次數(shù)很少的服務(wù)
已下線
服務(wù)是否已下線 很好判斷,只需要取一下當(dāng)前全部服務(wù)的狀態(tài),篩選出下線的服務(wù)。為了更精準(zhǔn),可以取兩個(gè)時(shí)間點(diǎn)的狀態(tài),這兩個(gè)時(shí)間點(diǎn)要有一定的跨度,當(dāng)兩個(gè)時(shí)間點(diǎn)服務(wù)都是下線狀態(tài),則判定服務(wù) “已下線”。
3、刪得好
找到了全部可能可以被刪除的服務(wù)后,接下來(lái)就是刪服務(wù)了,刪服務(wù)要保證三個(gè)點(diǎn):
- 刪得準(zhǔn):禁止誤刪
- 刪得全:刪除服務(wù)時(shí),要把服務(wù)相關(guān)資源都清理掉,比如服務(wù)的域名、機(jī)器等
- 刪得快:刪除服務(wù)的效率要高
為此,我們制定了服務(wù)刪除標(biāo)準(zhǔn)流程,搭建了 “應(yīng)用瘦身平臺(tái)”。
在流程中增加人工確認(rèn)步驟,保證 “刪得準(zhǔn)”;整理服務(wù)刪除所牽扯的相關(guān)資源清理過(guò)程,沉淀到瘦身平臺(tái)中,通過(guò)設(shè)計(jì)服務(wù)刪除全流程 與 自動(dòng)化刪除能力,保證 “刪得全” 和 “刪得快”。
平臺(tái)的關(guān)鍵設(shè)計(jì)點(diǎn)是服務(wù)刪除的流程標(biāo)準(zhǔn)化,我們將服務(wù)刪除分為了四個(gè)周期、十個(gè)步驟,全流程如下圖:
三、代碼精簡(jiǎn)實(shí)戰(zhàn)
1、可精簡(jiǎn)代碼特征分析
精簡(jiǎn)代碼的思路和精簡(jiǎn)服務(wù)一樣,從已知的、具體的操作入手,分析出可精簡(jiǎn)代碼的通用特征,然后基于特征、利用工具批量找到可精簡(jiǎn)代碼全集,最后執(zhí)行精簡(jiǎn)代碼的操作。
常見的可精簡(jiǎn)代碼方法有三個(gè):
- 靜態(tài)代碼分析,刪掉未被調(diào)用的方法。在 IDEA 里對(duì)這類方法是直接能提示出來(lái)的
- 重構(gòu)代碼,包括簡(jiǎn)化代碼寫法、精簡(jiǎn)邏輯、減少重復(fù)代碼等
- 想辦法找出 長(zhǎng)期沒(méi)有線上流量的代碼,這些代碼大概率是能被刪除的
這三個(gè)手段都能精簡(jiǎn)掉代碼,但效果并不一樣,因此我抽象了兩個(gè)指標(biāo),從 ”量大“ 和 ”通用性“ 上來(lái)評(píng)估每個(gè)手段的優(yōu)劣。為什么是這兩個(gè)指標(biāo),意義何在?“量大” 決定了能刪除的代碼數(shù)量,“通用性” 影響的是能否自動(dòng)化進(jìn)行,決定了刪除代碼的效率,最終會(huì)先選擇同時(shí)具備這倆指標(biāo)的手段,這樣能最快的刪除最多的代碼,具體的評(píng)估數(shù)據(jù)如下圖:
通過(guò)對(duì)已有代碼的全量度量,我們發(fā)現(xiàn) “靜態(tài)未被調(diào)用的方法” 占比是很少的,因此量不大
重構(gòu)是件復(fù)雜的事情,有時(shí)涉及到架構(gòu)重構(gòu),牽扯多個(gè)微服務(wù),需要對(duì)業(yè)務(wù)很熟悉,所以只能由業(yè)務(wù)線同學(xué)自行推進(jìn),難以找到通用的手段 自動(dòng)完成重構(gòu);重構(gòu)能減少的代碼行數(shù)是很多的,因此這也是達(dá)到最終目標(biāo)的抓手之一
經(jīng)過(guò)實(shí)際的代碼分析,發(fā)現(xiàn)沒(méi)有流量經(jīng)過(guò)的代碼是很多的,尤其對(duì)于歷史悠久的系統(tǒng),以及活動(dòng)類的系統(tǒng),業(yè)務(wù)沒(méi)了但代碼通常不會(huì)刪掉,這就產(chǎn)生了大量無(wú)流量的廢棄代碼。對(duì)于如何找到這些無(wú)流量的代碼,并進(jìn)行刪除,都具有通用性,因此作為工具平臺(tái)組,我們?cè)谶@個(gè)方面做了大量工作,也是分為 “找得到” 和 “刪得好”,下面依次進(jìn)行介紹
2、“找得到” 方案選型
常規(guī)方案
需要通過(guò)技術(shù)手段找到?jīng)]有線上流量的代碼,比較容易想到的方案有兩個(gè):
1、AOP:
利用 Java 中的 AOP 技術(shù),動(dòng)態(tài)增強(qiáng)每個(gè)方法,在其中增加訪問(wèn)計(jì)數(shù)邏輯,將源碼中的方法全集減去有計(jì)數(shù)的方法,得到?jīng)]有線上流量的方法,示例代碼如下:
2、Agent 字節(jié)碼插樁:
通過(guò)自實(shí)現(xiàn) Agent,對(duì)源碼進(jìn)行字節(jié)碼插樁,增加記錄訪問(wèn)日志的邏輯,并在 JVM 啟動(dòng)時(shí)設(shè)置 -javaagent 參數(shù)來(lái)加載 Agent,剩下 “計(jì)算沒(méi)有線上流量的方法” 和 AOP 方案類似,不再贅述。流程圖如下:
上面兩個(gè)方案比較容易想到,除此之外,我們找到了第 3 個(gè)方案,基于 SA 工具
3、SA 方案
先不著急介紹 SA 是什么,而從我們熟悉的入手,一步步推理、引出 SA。
在 JVM 中,Java 代碼可以通過(guò)解釋執(zhí)行(Interpreted Mode)和編譯執(zhí)行(Compiled Mode)兩種方式來(lái)運(yùn)行,默認(rèn)情況下使用混合模式(Mixed Mode)來(lái)運(yùn)行應(yīng)用程序,即 將解釋執(zhí)行和編譯執(zhí)行相結(jié)合。JVM 在應(yīng)用程序啟動(dòng)時(shí)會(huì)先進(jìn)行解釋執(zhí)行,同時(shí)使用即時(shí)編譯器來(lái)編譯熱點(diǎn)代碼。當(dāng)熱點(diǎn)代碼被編譯執(zhí)行后,就可以使用本地代碼來(lái)代替解釋執(zhí)行,從而提高應(yīng)用程序的性能。如果有新的熱點(diǎn)代碼出現(xiàn),即時(shí)編譯器會(huì)對(duì)其進(jìn)行編譯執(zhí)行。
那么 JVM 如何判斷出熱點(diǎn)代碼?猜測(cè)一下,有一個(gè)方法粒度的計(jì)數(shù)器,統(tǒng)計(jì)了方法執(zhí)行次數(shù),當(dāng)這個(gè)次數(shù)超過(guò)閾值時(shí)觸發(fā)編譯。
通過(guò)查看 hotspot 源碼完成了驗(yàn)證,在 Method 類中確實(shí)有方法計(jì)數(shù)相關(guān)的字段。
有辦法通過(guò) java 代碼讀取到 JVM 中方法計(jì)數(shù)相關(guān)的字段嗎?我們找到了一個(gè)工具 Serviceability Agent,這是 JVM 開發(fā)者為了調(diào)試 JVM 而制作的工具 Agent,主要功能是暴露運(yùn)行時(shí) JVM 中的 Java 對(duì)象和數(shù)據(jù)結(jié)構(gòu),因此它能把 JVM 中 Method 對(duì)象的全部字段都暴露出來(lái),使用 Java 代碼進(jìn)行讀取。
SA 官方介紹地址:https://openjdk.org/groups/hotspot/docs/Serviceability.html
SA 并不高深,常見的 JVM 工具 如 jstack、jmap,底層也是用了 SA
方案選型
對(duì)于上面三個(gè)方案的選型,從三個(gè)維度進(jìn)行評(píng)估:
對(duì)于方法執(zhí)行的計(jì)數(shù),SA 方案利用 JVM 已有機(jī)制就記下來(lái)了,因此該方案可以達(dá)到 對(duì)業(yè)務(wù)服務(wù)沒(méi)有任何性能損耗的效果,零風(fēng)險(xiǎn)。并且拿到的直接是方法執(zhí)行次數(shù),不需要額外的計(jì)算和聚合,實(shí)現(xiàn)復(fù)雜度低,因此最終選用了 SA 方案。
從結(jié)果上看,SA 方案確實(shí)非常好,最大優(yōu)點(diǎn)就是零風(fēng)險(xiǎn),整個(gè)項(xiàng)目推進(jìn)過(guò)程中,沒(méi)有產(chǎn)生過(guò)任何故障。
3、SA 方案詳細(xì)設(shè)計(jì)
本節(jié)將對(duì) SA 方案中的幾個(gè)關(guān)鍵設(shè)計(jì)點(diǎn)進(jìn)行詳細(xì)介紹。
性能無(wú)損跑數(shù)
使用 SA 對(duì)運(yùn)行中的 JVM 進(jìn)行方法計(jì)數(shù)探測(cè),這個(gè)過(guò)程稱之為 “跑數(shù)”。直接使用 SA 觀測(cè)線上的 JVM 是會(huì)對(duì)性能有影響的,因?yàn)樵谔綔y(cè)期間,用戶線程處于 STW 狀態(tài),內(nèi)存占用也會(huì)適當(dāng)增加,想要做到完全的性能無(wú)損跑數(shù),需要利用好服務(wù)上下線,具體過(guò)程如下圖:
首先隨機(jī)挑選一個(gè)線上的實(shí)例,進(jìn)行實(shí)例下線,下線只會(huì)讓線上流量不再打到該實(shí)例上,JVM 還是跑著的。然后對(duì)下線后實(shí)例,使用 SA 進(jìn)行跑數(shù),完成跑數(shù)后等待幾分鐘,對(duì)實(shí)例進(jìn)行上線,這樣就不會(huì)因?yàn)榕軘?shù),而對(duì)線上流量有性能影響了。
跑數(shù)有三個(gè)避坑點(diǎn)需要注意:
SA 跑數(shù)時(shí)會(huì)消耗一定內(nèi)存,因此在跑數(shù)前 JVM 必須留有足夠的剩余內(nèi)存(通常 大于 500M),這樣才能成功跑出數(shù),否則 JVM 會(huì)發(fā)生 OOM 甚至長(zhǎng)時(shí)間卡住的問(wèn)題
服務(wù)必須是多實(shí)例的,對(duì)于單實(shí)例服務(wù)直接跑數(shù)會(huì)導(dǎo)致服務(wù)全部下線,影響業(yè)務(wù)。這種情況建議擴(kuò)容到多個(gè)實(shí)例,或者放棄跑數(shù)
有時(shí) prod 類型的環(huán)境會(huì)有多個(gè),它們使用的是同一份代碼,此時(shí)需要對(duì)每個(gè)環(huán)境都挑實(shí)例進(jìn)行跑數(shù)
跑數(shù)代碼實(shí)現(xiàn)
SA 就是一個(gè) jar 包,提供了觀測(cè) JVM 的 API,本節(jié)主要介紹跑數(shù)的代碼實(shí)現(xiàn)。
JVM 對(duì)于解釋執(zhí)行和編譯執(zhí)行,方法的結(jié)構(gòu)是不一樣的,因此需要區(qū)分處理,代碼如下:
代碼中的 API 運(yùn)用了觀察者模式,其中 InvocationCounterVisitor 和 CompiledMethodVisitor 是我們自定義的觀察者,兩者的邏輯類似:
- 實(shí)現(xiàn)觀察者的接口,實(shí)現(xiàn)核心的觀察(visit)方法
- 通過(guò)方法入?yún)?,獲取到 Method 對(duì)象
- 從 Method 對(duì)象中取出方法計(jì)數(shù)器值,保存到結(jié)果集中
核心代碼如下圖:
計(jì)算可精簡(jiǎn)方法集
跑一次數(shù),能探測(cè)到 “從 JVM 啟動(dòng)到跑數(shù)時(shí)刻” 這段時(shí)間內(nèi),每個(gè)方法的執(zhí)行次數(shù)。跑完數(shù)之后,得到了一個(gè)方法集合,包含方法標(biāo)識(shí)、執(zhí)行次數(shù)等信息:
但是僅跑一次數(shù)是不夠的,可能在 “從 JVM 啟動(dòng)到跑數(shù)時(shí)刻” 這段時(shí)間之后,有方法被第一次執(zhí)行了,此時(shí)就會(huì)產(chǎn)生遺漏。因此要多次、有時(shí)間跨度地跑數(shù),比如每天跑一次,持續(xù)跑一個(gè)月,這樣才能獲取到更準(zhǔn)確的數(shù)據(jù),減少遺漏概率。
有了多次跑數(shù)結(jié)果后,現(xiàn)在開始計(jì)算 “可精簡(jiǎn)方法集”。首先,要對(duì)多次的跑數(shù)結(jié)果取并集,獲得全部 “有流量的方法集”;然后,分析工程源碼,利用開源工具(如 Spoon)獲取 “工程方法全集”;最后,在 “工程方法全集” 中排除掉 “有流量的方法集”,得到最終的 “可精簡(jiǎn)方法集”,過(guò)程如下圖:
校準(zhǔn)可精簡(jiǎn)方法集
如之前所述,采用了隨機(jī)抽取實(shí)例進(jìn)行跑數(shù)的方式,這就可能產(chǎn)生遺漏問(wèn)題,例如:某服務(wù)線上實(shí)例個(gè)數(shù)為 600 個(gè),有一個(gè)定時(shí)任務(wù)定向的分發(fā)到指定的一臺(tái)機(jī)器上,此時(shí)理想情況下,隨機(jī)跑數(shù) 600 次后就會(huì)命中一次定時(shí)任務(wù)所在的實(shí)例,抓取到定時(shí)任務(wù)方法的執(zhí)行記錄。但很有可能,跑了 600 次也沒(méi)跑到定時(shí)任務(wù)所在的機(jī)器,這就產(chǎn)生了有流量方法集的遺漏,最終導(dǎo)致計(jì)算出來(lái)的可精簡(jiǎn)方法集有誤判。
要解決這個(gè)問(wèn)題,第一反應(yīng)是能不能對(duì)所有實(shí)例都跑數(shù)?這樣一定能避免遺漏,但根據(jù)實(shí)際經(jīng)驗(yàn),這種方式風(fēng)險(xiǎn)過(guò)高,試想一下,每天跑一次數(shù),每次都需要把服務(wù)的全部實(shí)例輪著下線、上線一遍,操作之繁瑣、風(fēng)險(xiǎn)之大可想而知。
我們最終使用的方案是,對(duì)于 SA 跑出來(lái)的可精簡(jiǎn)方法集,在源碼層面、通過(guò) IDEA 批量加上打點(diǎn)(具體如何增加,下文會(huì)介紹),發(fā)布到線上后再跑一段時(shí)間,根據(jù)打出的點(diǎn)校準(zhǔn)可精簡(jiǎn)方法集,這樣就能確保百分百的準(zhǔn)確性了,在代碼中增加的打點(diǎn)如下圖:
源碼中加打點(diǎn),不會(huì)帶來(lái)性能損耗嗎?確實(shí)存在損耗,但基本可以忽略不計(jì),因?yàn)檫@些要加打點(diǎn)的方法,是 SA 長(zhǎng)期跑出來(lái)的可精簡(jiǎn)方法集,代表方法執(zhí)行頻率極低甚至沒(méi)有,因此對(duì)這些方法增加打點(diǎn),能產(chǎn)生的性能影響極少。
4、整體方案
最后,將上面的點(diǎn)串聯(lián)起來(lái)看一下整體的方案,業(yè)務(wù)流程圖如下:
- 定時(shí)任務(wù)平臺(tái),定期觸發(fā)瘦身服務(wù)的跑數(shù)邏輯
- 瘦身服務(wù)通過(guò)發(fā)布平臺(tái),隨機(jī)下線一臺(tái)實(shí)例
- 瘦身服務(wù)通過(guò) salt 平臺(tái),下發(fā)跑數(shù)的腳本任務(wù)
- 在已下線的實(shí)例上完成跑數(shù),將結(jié)果上報(bào)到瘦身服務(wù)
- 瘦身服務(wù)將跑數(shù)的原始結(jié)果(每個(gè)方法的執(zhí)行次數(shù)等信息)進(jìn)行存庫(kù)
- 瘦身服務(wù)通過(guò)發(fā)布平臺(tái),將下線的實(shí)例進(jìn)行上線
- 瘦身服務(wù)克隆源碼,獲取 “工程中方法全集”
- 從打點(diǎn)監(jiān)控平臺(tái)(watcher)上取到全部瘦身相關(guān)打點(diǎn),轉(zhuǎn)換成有打點(diǎn)的方法集合
- 將 SA 跑出的結(jié)果方法集 和 有打點(diǎn)的方法集,從 工程方法全集 中排除,得到最終的 “可精簡(jiǎn)方法集”,存庫(kù)
5、刪得好
代碼想要?jiǎng)h得好,自動(dòng)化就少不了。我們?cè)O(shè)計(jì)并提供了兩種代碼刪除方式,分為 全自動(dòng) 和 半自動(dòng),以適配不同重要性的業(yè)務(wù)團(tuán)隊(duì)。
全自動(dòng)
整個(gè)代碼刪除過(guò)程完全自動(dòng)化,包含 克隆代碼、創(chuàng)建瘦身分支、刪除可精簡(jiǎn)代碼、推送分支 4 個(gè)步驟。最后需要開發(fā)同學(xué)進(jìn)行 CR,沒(méi)問(wèn)題后走后續(xù)的驗(yàn)證、發(fā)布過(guò)程。
刪除代碼時(shí)有個(gè)注意點(diǎn),需要保證刪除后的代碼可正常編譯。如果直接刪除方法簽名和方法體,就可能導(dǎo)致無(wú)法分支無(wú)法正確編譯,這是不能被接受的。
半自動(dòng)
在全自動(dòng)方案中,開發(fā)者只有在最終 CR 時(shí)才能看到被刪除的代碼,此時(shí)如果有大量需要恢復(fù)的代碼,操作起來(lái)會(huì)比較麻煩,需要手動(dòng)回滾。同時(shí),為了讓開發(fā)對(duì)刪除的代碼有更強(qiáng)的掌控力,因此設(shè)計(jì)了半自動(dòng)方案。
半自動(dòng)方案中,我們開發(fā)了一個(gè) idea 插件,該插件能掃出可精簡(jiǎn)的方法集合,提供批量刪除方法、置空方法體、增加打點(diǎn)等功能。開發(fā)者利用該插件,手動(dòng)、高效地進(jìn)行代碼刪除工作,并且對(duì)每個(gè)方法都有精確掌控,風(fēng)險(xiǎn)更低。
驗(yàn)證
代碼刪完、人工 CR 確認(rèn)后,仍需要做好測(cè)試、驗(yàn)證工作 才能最終發(fā)布到線上,具體包含以下六個(gè)步驟:
- beta 發(fā)布:先在 beta 環(huán)境進(jìn)行發(fā)布
- 自動(dòng)化測(cè)試:自動(dòng)化測(cè)試平臺(tái)對(duì)精簡(jiǎn)后的服務(wù),在 beta 環(huán)境完成自動(dòng)化測(cè)試
- 故障演練:故障演練是為了保障強(qiáng)弱依賴沒(méi)有問(wèn)題
- 灰度發(fā)布:保險(xiǎn)起見,對(duì)線上環(huán)境先進(jìn)行灰度發(fā)布,并進(jìn)行觀察
- 全量發(fā)布:灰度發(fā)布、觀察沒(méi)問(wèn)題后,進(jìn)行全量發(fā)布
- 觀察指標(biāo):全量發(fā)布完成后,需要對(duì)各種指標(biāo)觀察一段時(shí)間,發(fā)現(xiàn)問(wèn)題及時(shí)回滾
四、最終效果
最終我們成功精簡(jiǎn)了 49.87% 的代碼,代碼總量減少超千萬(wàn),取得了超預(yù)期的效果,具體如下:
- 開發(fā)估時(shí)平均降低約 10.9%:在刪除冗余代碼、精簡(jiǎn)系統(tǒng)后,每個(gè)需求的估時(shí)和開發(fā)耗時(shí)明顯降低,預(yù)計(jì)每年可以節(jié)省近萬(wàn) PD 成本;
- 發(fā)布效率提升約 9.5%:代碼大量減少后帶來(lái)了更快的發(fā)布速度,從克隆到編譯到運(yùn)行,各階段耗時(shí)均有縮短。單次提升看似有限,但實(shí)際每月有幾萬(wàn)次發(fā)布,綜合收益是很可觀的。
綜上所述,系統(tǒng)瘦身是一項(xiàng)有價(jià)值的工作,且實(shí)際價(jià)值超出預(yù)期。
五、未來(lái)展望
前面分別從服務(wù)精簡(jiǎn)和代碼精簡(jiǎn)角度,分別介紹了特征分析、找得到、刪得好、實(shí)施經(jīng)驗(yàn),希望能夠幫助大家將代碼量降下來(lái),降低維護(hù)成本,提升開發(fā)效率。在未來(lái),還有以下五個(gè)方面的工作可以繼續(xù)探索和實(shí)施:
- 代碼行粒度精簡(jiǎn):目前的方案是方法粒度的,更進(jìn)一步可以細(xì)分為行粒度,有利于圈復(fù)雜度的降低
- 長(zhǎng)周期代碼精簡(jiǎn):一段代碼如果很長(zhǎng)時(shí)間才執(zhí)行一次(如 一年一次),執(zhí)行的時(shí)候不在 SA 跑數(shù)期間,就會(huì)產(chǎn)生誤判,該方法應(yīng)該保留
- 依賴精簡(jiǎn):對(duì)服務(wù)依賴的 jar 包進(jìn)行精簡(jiǎn),減少依賴數(shù)量,避免依賴冗余、減少不兼容的情況
- 配置精簡(jiǎn):清理服務(wù)所依賴的配置項(xiàng)
- 服務(wù)合并:探索服務(wù)合并的通用路徑,完成自動(dòng)化服務(wù)合并功能
作者:馬陽(yáng)陽(yáng)
來(lái)源:微信公眾號(hào):Qunar技術(shù)沙龍
出處:https://mp.weixin.qq.com/s/H-QedPzfsT88w0n33g6ZhA
版權(quán)聲明:本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻(xiàn),該文觀點(diǎn)僅代表作者本人。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如發(fā)現(xiàn)本站有涉嫌抄襲侵權(quán)/違法違規(guī)的內(nèi)容, 請(qǐng)發(fā)送郵件至 舉報(bào),一經(jīng)查實(shí),本站將立刻刪除。