1. 程式人生 > >持續整合之“依賴管理”

持續整合之“依賴管理”

轉自:

前文《分支策略(續)》中,我們討論了多元件應用程式的持續整合策略,即:為相對獨立的元件建立自己專屬的程式碼庫,然後通過現代持續整合工具進行元件間的持續整合。Joe的團隊在首次釋出之後,開始使用這種方式。然而,沒有多久,他們就遇到了一個問題:一次提交構建所花費的時間太長。

  一天,Joe就早早地來到了辦公室。因為他前一天下班前,他開發的使用者故事還有一小點就完事兒了。他想利用早上這點兒時間把它搞完,交給測試人員進行測試。他修改了某個模組的一段程式碼,在本地構建測試通過以後,就提交了, 然後起身去樓下買些早點。十五分鐘後,他回到了電腦前,令他沮喪的是,這次構建還在進行最後的階段,即所有模組整合測試和系統級測試。他只好又起身去衝了杯咖啡。然後,一邊看著螢幕上的構建進度條,一邊喝著咖啡。七分鐘後,構建終於成功結束了。雖然這是一次成功的構建,但總是覺得不爽,花了二十多分鐘才做完提交構建。於是,他開始仔細地檢視起構建指令碼和構建日誌。

  一、一次生成,多次複用

  中午吃過午飯,他把Bob和Alice叫到一起,開始討論早上他遇到的問題。

  “的確是非常煩人,現在構建時間太長了。”Alice說道。

  “我今天早上查看了一下我們的構建日誌,發現構建時間長的原因之一是:每種測試開始之前都要更新程式碼,再重新編譯一次。”Joe說道。

  Bob提出了一個解決方案,並畫在了白板上。“我們是否可以建立統一的產物庫,每次構建的產物都以一定的規則放在其中?這樣,後續的測試需要使用這些二進位制產物的話,直接從產物庫中獲取即可。”(如圖1所示)

  “聽上去不錯。然而,我們是否需要把每次構建中產生的內容都放入產物庫,這會非常快地吃掉我們的磁碟空間。”Alice不無擔心的說。

  “目前構建完成以後,所有的產物都放在那臺構建機器上。我們也遇到過因構建機器硬體問題或誤操作將所有重要歷史資訊都丟失的事情。所以,我們至少需要備份。”Bob回答道,“另外,將每次構建的產物放在統一產物庫中,我們就可以解決Joe剛才提出的重複編譯問題。當然,我們需要有選擇地將重要的構建產物放到統一產物庫中,而不是所有內容。通過在每次構建後增加一個上傳任務,讓各小組將其認為有用的資訊上傳到產物庫,比如構建日誌、測試報告、構建後的二進位制檔案等。但一些臨時檔案就沒有必要了。當然,這隻能緩解產物庫膨脹的速度。儘管持續構建的次數非常多,但我們並不是需要一直保持所有構建的產物,所以,可以定期刪除那麼沒有保留價值的構建產物,比如對那些重要構建的產物進行標記,其它的就可以刪除了。”

  Alice和Joe都點了點頭,表示同意。但Joe的眉頭馬上又皺了起來。“嗯,好象這裡還有點兒問題。”

  “什麼問題?”Alice和Bob同時問道。

  Joe說道:“對於我們平臺中的一些小遊戲元件來說,這沒有什麼問題。因為它們的構建產物都不太大,網路傳輸頻寬和速度都不是問題。但是,對於那些很大的二進位制檔案或測試資料來說,這麼做的話,可能就有問題了。”大家都點了點頭,並開始思考這個問題。

  忽然,Joe叫道:“不好意思,其實這不是個真正的問題。首先,我們的測試資料變化就不頻繁,原來也沒有放在產物庫中,而是放在了一個共享目錄中進行版本管理。所以,這部分在構建中的做法與之前沒有什麼不同。其次,對於較大的二進位制檔案,只要在需要它的構建機器上把它快取起來。那麼在下一次構建時,構建指令碼可以對這個本地版本進行驗證,如果版本正確且沒有被破壞(比如通過MD5驗證)就可以繼續使用。否則,就再從統一產品庫取出正確的檔案將其覆蓋就行了。”

  “這麼做還有一個好處,而且是非常重要的好處。”Alice補充道,“我們的手工測試版本也可以從統一的產物庫中拿到,這就保證了自動化測試所有的二進位制檔案與部署到手工測試環境中的二進位制檔案是同一個檔案了,也就不會出現因重新編譯時的環境不同而導致的不一致問題了。而當我們做上線部署時,也從這個統一產品庫中獲取,從而做到自編譯開始直到上線部署的二進位制包的一致性啦。”

  於是,Joe與團隊一起對其持續整合平臺和所有構建進行了改造,將其打造成了一個具有組織級產物庫的持續整合和釋出管理平臺。他們不但有效地縮短了每次構建的時間,還可以輕鬆地通過產物庫追蹤到每個上線版本在程式碼版本控制庫中的對應程式碼,讓問題追查變得更容易了。

  二、依賴管理

  一個月後,根據市場的需求反饋,他們開發的一個遊戲升級了,反應速度非常快,效果非常好。但引申出來的一個問題是:遊戲和平臺的升級頻率不一致,持續整合應該怎麼做。對於Joe的團隊來說,是一個非常大的問題,因為他們的開發流程嚴重地依賴於持續整合平臺。於是,Joe和團隊的核心成員打算討論一下,如何應對目前這種情況。

  在會議室的白板前,Joe畫出了當前所用的持續整合策略(如前圖所示)。

  Bob說道:“到目前為止,我們已經發布了幾次,而且最近一次只發布了一個遊戲應用。我們如何管理我們的釋出流程呢?在我之前工作過的公司中,產品會有幾個版本,包括穩定版本、已對外發布或即將釋出的版本、最新版本:用於公司內部測試。每當將要釋出新版本時,就拉出一個分支,進行內部測試,並修復嚴重的缺陷。當沒有嚴重缺陷時,才能作為穩定版本公開發布。”

  Alice答道:“對於單個的軟體交付產品來說,通常可以通過“按釋出拉分支” 的方式進行開發,正如我們最開始所使用的持續整合策略。但是,現在我們的遊戲平臺與單個交付產品不同。我們有自己的伺服器叢集,只要測試覆蓋率及測試質量足夠好,測試速度足夠快,我們就可以通過小流量試驗部署後再大規模上線的方式進行釋出。現在,我們的問題是由於各個遊戲元件的釋出頻率各不相同,元件存在依賴關係,導致很難決定在持續整合過程中,到底應該使用哪個依賴版本。尤其是我們現在還有一個公共庫,被多個元件使用。”

  Joe說道:“我們先梳理一下整個平臺上的依賴關係吧。通常來說,軟體中的依賴關係通常包括編譯時依賴、測試時依賴和執行時依賴。而從依賴形式上可以分為庫依賴和元件依賴。所謂庫依賴,是指依賴於那些不受控的庫檔案,比如我們使用了一些開源或者付費的的類庫檔案或工具,這些庫檔案的特點是更新較慢,甚至基本不需要更新。而元件依賴是指依賴於那些由自己團隊或公司內的其它團隊開發的元件,這類依賴的特點是更新頻率相對高,有些甚至非常頻繁。對於庫檔案依賴,我們可以在程式碼庫中建立一個目錄,叫做lib,並在其下建立build、test、run三個子目錄,把我們所依賴的庫檔案放到相應的子目錄中。同時,每個庫檔案的檔名中最好包含它的版本號,如nunit-2.6.0.11089.bin。這樣,就很容易看出依賴了哪些庫檔案。”

    Bob接道:“可惜我們不是用Java平臺,否則我們可以用象MavenIvy這樣的工具來管理這些外部庫依賴了。而且,同時可以在公司內部利用Artifactory或Nexus這樣的開源工具建立一個內部統一伺服器,專門管理公司內部所用的這些庫依賴。”

    Alice說道:“我們也可以自己做一個簡單的依賴管理系統。比如使用Key-value的格式用文字檔案來描述所用到的庫檔名及版本號及存放位置,然後再寫個通用指令碼讀取資訊下載到本地使用。”

    Bob接著問道:“對於這種庫檔案的依賴管理相對容易一些。而我們面臨的重要問題好象是元件依賴管理。有什麼好辦法嗎?”

    Joe想了想,說道:“方法倒是有幾個,各有優缺點。一種方法是將元件依賴轉成庫依賴。其適用的場景是該元件經過一段時間的開發的維護後已趨於穩定,變化不太多。此時就可以將這個元件打包後與其它外部依賴庫放在一起,並加入正確的描述,以便依賴於它的所有元件都可以正確地拿到正確的版本。還有一種方法是我們目前所用的方法。即每個元件各自進行持續構建,然後再做整合構建。其中存在的問題是我們如何管理各元件不同版本之間的組合關係。我們一直使用的策略是無論哪次提交,都會觸發整個構建。目前要做的有兩件事:一是將公共庫獨立出來,進行單獨構建,並且一旦構建成功,自動觸發那些依賴於它的其它元件構建,最後進行整合構建。只要我們記錄每次構建後的版本及原始碼的 revision就行,以便可以追蹤。二是將遊戲平臺的持續構建觸發其它遊戲元件的持續整合。所以,觸發關係應該是這樣的。”Joe拿起筆,在白板上重新畫了一下觸發關係圖(圖2)。

    Bob搖了搖頭,說道:“這樣還是解決不了我們之前說過的問題,即我們的釋出頻率不一致,如何來管理這些釋出之間的關係。”

    “噢,這個問題是這樣的。”Joe回答道:“我認為,我們之前單獨釋出一個遊戲元件是不對的。我們因市場壓力而將該遊戲元件直接部署到生產環境中,儘管在釋出前的評估認為,該遊戲所依賴的平臺介面沒有發生變化。正確的做法有兩種:(方案A)將平臺作為一個整體一同釋出,因為我們對平臺也做了修改,當時,所有的持續整合測試都是基於主幹的最新版本所做的。(方案B)讓所有遊戲元件依賴於遊戲平臺的最新發布的穩定版本進行開發。由於平臺的新功能開發較慢,所以只要平臺介面不發生變更,各遊戲應用都可以基於平臺的穩定釋出版本進行快速更新。但只要某個遊戲需要修改平臺的介面,就必須與平臺的最新程式碼進行持續整合,並一同釋出。”

    Alice皺了皺眉,說道:“這麼看來,對於整個軟體來說,能夠保持主幹隨時可以釋出才更容易管理元件依賴。因為每當需要釋出時,直接做主幹釋出就行了。實在不行的話,只要將所有元件在同一時間點拉出一個釋出分支,然後統一上線就行了。”

    Bob說道:“這樣也有問題。我們的部署會很麻煩,時間可能會很長。”

    Joe笑著說:“部署麻煩,我們可以通過一系統列的自動化操作來解決。部署時間長的話,我們使用的是叢集部署,因此可以採用分批替換的方式來部署。但這種釋出方式給我們帶來的益處是可以很快的響應市場需求。”

    Joe拿起杯子喝了口咖啡,接著說道:“當然,這對我們的開發工作也提出了挑戰。我們必須使用多種手段才能做到主幹持續可釋出狀態。比如(1)將新功能隱蔽起來,直到它完成為止;(2)把所有的變更都變成一次次非常小的增量式修改,每個修改都做到可釋出;(3)通過抽象達到分支的目的(Branch by Abstraction)。另外,我們的自動化測試也需要保持在較高的覆蓋率,並豐富其它型別的自動化測試,比如效能測試,壓力測試等。如果遇到特殊情況,我們再坐下來商量對策。”

    Bob仍舊有點遲疑,“這樣可能會增加我們的開發成本。不過,可以試一下,看看效果如何。”

    於是,整個團隊開始行動起來了。他們在這條道路上還會遇到什麼情況呢?讓時間來回答這個問題吧。