如何在微服務團隊中高效使用 Git 管理代碼?

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用了 Git 多年,優勢和挑戰都是深有體會。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"話不多說,直接上問題:如何在微服務團隊中高效使用 Git 管理代碼?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"繼續不多話,直接上答案:分支管理。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":""}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Git 的分支管理有很多實踐,有些是從 SVN 類的集中式版本管理工具繼承的,有些是根據 Git 自己的特性總結的,目前市面上比較有名的三種 Git 分支管理模型是:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TrunkBased:主幹在手,天下我有。所有代碼都往主幹上招呼,發版也只用主幹。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"GitFlow:嚴謹、規範、難用,主要是記不住該往哪個分支合併了。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"AoneFlow:前兩種都不行,那就借鑑各自的優點,達到陰陽平衡,中庸也。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"TrunkBased"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TrunkBased,又叫主幹開發,有一個網站專門介紹這種開發方式:"},{"type":"link","attrs":{"href":"https://trunkbaseddevelopment.com/","title":""},"content":[{"type":"text","text":"Trunk Based Development"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TrunkBased 是持續集成思想所崇尚的工作方式,它由單個主幹分支和許多發佈分支組成,每個發佈分支在特定版本的提交點上從主幹創建出來,用來進行上線部署和 Hotfix。在 TrunkBased 模式中,沒有顯性的特性分支。當然實際上 Git 的分佈式特徵天生允許每個人有本地分支,TrunkBased 也並非排斥短期的特性分支存在,只不過在說這種模式的時候,大家通常都不會明確強調它罷了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/12/12f50392f5238664900be052ca37619d.jpeg","alt":"TrunkBased","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用主幹開發後,我們的代碼庫原則上就只能有一個 Trunk 分支即 master 分支了,所有新功能的提交也都提交到 master 分支上,保證每次提交後 master 分支都是可隨時發佈的狀態。沒有了分支的代碼隔離,測試和解決衝突都變得簡單,持續集成也變得穩定了許多。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是這種方案缺點也比較明顯,如果大家都在主幹進行開發,當代碼提交合並時,將會異常痛苦,一不小心就會出現衝突。而且,這種因爲這種方式沒有明顯的特性分支,想要移除已經上線的特性會變得非常困難。(如果你說把不要的功能註釋,重新發版,那就當我什麼也沒說。)還有一種方案是引入特性開關,通過開關控制特性是否啓用和關閉,但是增加開關就引入了複雜性,引入複雜性就引入了出 bug 的風險,畢竟多增加的每行代碼都有可能是一個新的 bug。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"GitFlow"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"GitFlow 來源於 Vincent Driessen 提出的 "},{"type":"link","attrs":{"href":"https://nvie.com/posts/a-successful-git-branching-model/","title":""},"content":[{"type":"text","text":"A successful Git branching model"}]},{"type":"text","text":",整體來說,是一套完善的版本管理流程。缺點就是太過嚴格,不太適合喜歡自由而且懶的程序猿。當然,在程序猿這種物種中,沒有完美的解決方案,總有那麼一小撮人會覺得不好。參考 Ant、Maven 和 Gradle。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先上圖:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f3/f3ecc3cf5b3902dbb1ff2356fe65e67e.png","alt":"GitFlow:A successful Git branching model","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"GitFlow 常用分支:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主幹分支("},{"type":"codeinline","content":[{"type":"text","text":"master"}]},{"type":"text","text":"):最近發佈到生產環境代碼的分支,這個分支只能從其他分支合併,不能再 Master 分支直接修改。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主開發分支("},{"type":"codeinline","content":[{"type":"text","text":"develop"}]},{"type":"text","text":"):包含所有要發佈到下一個 Release 版本的代碼。可以在 Develop 直接開發,也可以將 Feature 的特性代碼合併到 Develop 中。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f5/f5d4d66820a5b77ad7bc363a3503b28e.png","alt":"主開發分支","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"特性分支("},{"type":"codeinline","content":[{"type":"text","text":"feature/*"}]},{"type":"text","text":"):功能項開發分支,以"},{"type":"codeinline","content":[{"type":"text","text":"feature/"}]},{"type":"text","text":"開頭命名分支,當功能項開發完成,將被合併到主開發分支進入下一個 Release,合併完分支後一般會刪掉這個特性分支。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/4d/4dc8ef862b4e1e70fc45f6d00773160b.png","alt":"特性分支","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"發佈分支("},{"type":"codeinline","content":[{"type":"text","text":"release/*"}]},{"type":"text","text":"):基於主開發分支創建的一個發佈分支,以"},{"type":"codeinline","content":[{"type":"text","text":"release/"}]},{"type":"text","text":"開頭命名分支,用於測試、bug 修復及上線。完成後,合併到主幹分支和主開發分支,同時在主幹分支上打個 Tag 記住 Release 版本號,然後可以刪除發佈分支。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ae/aeb5cb758836d84cbbd9b319a462825a.png","alt":"發佈分支","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"熱修復分支("},{"type":"codeinline","content":[{"type":"text","text":"hotfix/*"}]},{"type":"text","text":"):用於解決線上 Release 版本出現的 bug,以"},{"type":"codeinline","content":[{"type":"text","text":"hotfix/"}]},{"type":"text","text":"開頭命名分支,修復線上問題,完成後,合併到主幹分支和主開發分支,同時在主幹分支上打個 tag。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fa/fa159868df4a416d391c6fd832851f9a.png","alt":"熱修復分支","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根據上面描述,GitFlow 是一套完整的從開發到生產的管理方式,但是各種分支來回切換及合併,很容易把人搞暈,所以用的人也是越來越少。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"AoneFlow"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"AoneFlow 是阿里內部的一套版本管理模型,兼顧了 TrunkBased 易於持續集成和 GitFlow 易於管理需求的特點,又規避了 GitFlow 分支繁瑣的缺點,也就是中庸。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"AoneFlow 使用三個分支:主幹分支("},{"type":"codeinline","content":[{"type":"text","text":"master"}]},{"type":"text","text":")、特性分支("},{"type":"codeinline","content":[{"type":"text","text":"feature/*"}]},{"type":"text","text":")、發佈分支("},{"type":"codeinline","content":[{"type":"text","text":"release/*"}]},{"type":"text","text":"),以及三條原則:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"規則一,開始工作前,從主幹分支創建特性分支。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這條規則借鑑了 GitFlow,每當開始一件新的工作項(比如新的功能或是待解決的問題,可以是一個人完成,或是多個人協作完成)時,從代表最新已發佈版本的主幹分支上創建一個通常以"},{"type":"codeinline","content":[{"type":"text","text":"feature/"}]},{"type":"text","text":"前綴命名的特性分支,然後在這個分支上提交代碼修改。也就是說,每個工作項對應一個特性分支,所有的修改都不允許直接提交到主幹。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/9f/9f2440f805c5ff703410547b646d4dc2.jpeg","alt":"從主幹創建特性分支","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"特性分支不止承擔了新功能,也是待解決問題的分支。對於我們團隊,爲了避免歧義,會將新功能以"},{"type":"codeinline","content":[{"type":"text","text":"feature/"}]},{"type":"text","text":"爲前綴,待解決問題以"},{"type":"codeinline","content":[{"type":"text","text":"hotfix/"}]},{"type":"text","text":"爲前綴,除了名稱外,其他都按照規則一執行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"規則二,通過合併特性分支,形成發佈分支。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"GitFlow 先將已經完成的特性分支合併回主幹分支和主開發分支,然後在主幹分支上打 Tag 記錄發佈信息。TrunkBased 是等所有需要的特性都在主幹分支上開發完成,然後從主幹分支的特定位置拉出發佈分支。而 AoneFlow 的思路是,從主幹上拉出一條新分支,將所有本次要集成或發佈的特性分支依次合併過去,從而得到發佈分支,發佈分支通常以"},{"type":"codeinline","content":[{"type":"text","text":"release/"}]},{"type":"text","text":"前綴命名。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/85/85f2947ab195a3554689e89693e8987c.jpeg","alt":"合併特性分支,形成發佈分支","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們可以將每條發佈分支與具體的環境相對應,"},{"type":"codeinline","content":[{"type":"text","text":"release/test"}]},{"type":"text","text":"對應部署測試環境,"},{"type":"codeinline","content":[{"type":"text","text":"release/prod"}]},{"type":"text","text":"對應線上正式環境等,並與流水線工具相結合,串聯各個環境上的代碼質量掃描和自動化測試關卡,將產出的部署包直接發佈到相應環境上。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外,發佈分支的特性組成是動態的,調整起來特別容易。在一些市場瞬息萬變的互聯網企業,以及採用“敏捷運作”的乙方企業經常會遇到這種情況,已經完成就等待上線的需求,隨時可能由於市場策略調整或者甲方的一個臨時決定,其中某個功能忽然要求延遲發佈或者乾脆不要了。再或者是某個特性在上線前發現存在嚴重的開發問題,需要排除。按往常的做法,這時候就要來手工“剔代碼”了,將已經合併到開發分支或者主幹分支的相關提交一個個剔除出去,做過的同學都知道很麻煩。在 AoneFlow 模式下,重建發佈分支,只需要將原本的發佈分支刪掉,從主幹拉出新的同名發佈分支,再把需要保留的各特性分支合併過來就搞定,而且代碼是乾淨的,沒有包含不必要的特性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此外,發佈分支之間是松耦合的,這樣就可以有多個集成環境分別進行不同的特性組合的集成測試,也能方便的管理各個特性進入到不同環境上部署的時機。松耦合並不代表沒有相關性,由於測試環境、集成環境、預發佈環境、灰度環境和線上正式環境等發佈流程通常是順序進行的,在流程上可以要求只有通過前一環境驗證的特性,才能傳遞到下一個環境做部署,形成漏斗形的特性發布流。當然,這種玩法比較適合有完整開發集成平臺的公司,小團隊玩不轉,比如我們團隊就玩不動這種高級玩法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"規則三,發佈到線上正式環境後,合併相應的發佈分支到主幹,在主幹添加 Tag,同時刪除該發佈分支關聯的特性分支。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當一條發佈分支上的流水線完成了一次線上正式環境的部署,就意味着相應的功能真正的發佈了,此時應該將這條發佈分支合併到主幹。爲了避免在代碼倉庫裏堆積大量歷史上的特性分支,還應該清理掉已經上線部分特性分支。與 GitFlow 相似,主幹分支上的最新版本始終與線上版本一致,如果要回溯歷史版本,只需在主幹分支上找到相應的版本標籤即可。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/26/264f42057b170d56ed0c30ed9ad8f318.jpeg","alt":"合併相應的發佈分支到主幹,在主幹添加 Tag,同時刪除該發佈分支關聯的特性分支","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除了基本規則,還有一些實際操作中不成文的技巧。比如上線後的熱修復,正常的處理方法應該是,創建一條新的發佈分支,對應線上環境(相當於 Hotfix 分支),同時爲這個分支創建臨時流水線,以保障必要的發佈前檢查和冒煙測試能夠自動執行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"再囉嗦幾句廢話"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不管哪種方式,既然存在,都會有一定合理性。所以,不管最終翻了哪個牌子,不是因爲這個好看,而是因爲這個更適合自己。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/9e/9e305acd1cca75053c144cb28adc6061.png","alt":"公衆號:看山的小屋","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章