大家好哇~ 歡迎來我的神奇的“科普”頻道!
今天,我們?yōu)榇蠹医榻B程序員是如何怎么存檔并管理文件版本的。
大家要做好心理準(zhǔn)備,今天的“科普”稍有點(diǎn)點(diǎn)硬核,我想從需求分析,產(chǎn)品設(shè)計(jì),代碼實(shí)現(xiàn)等全方位角度為大家“科普”,綜合的東西較多,可能不太好看懂......
但內(nèi)容應(yīng)該還是有點(diǎn)點(diǎn)意思的,畢竟我總是寫一些亂七八糟的東西,如果暫時(shí)感覺難以消化可以考慮先收藏吖~
在開始之前,我們先為大家介紹一個(gè)概念,叫:版本管理。
我們先從日常生活講起。
阿菌這壞小子有時(shí)會(huì)想回到自己的過去,比如說回到小學(xué)時(shí)候的自己,回到初中時(shí)的自己,回到高中時(shí)的自己,或者回到大學(xué)前的自己,重讀一次大學(xué)等等......
人生的每個(gè)階段,我們都可以看成自己的一個(gè)版本,比如說小學(xué)版的阿菌,初中版的阿菌,高中版的阿菌,大學(xué)版的阿菌......
要是老天真的給我們每個(gè)階段都存了檔,那我們就可以回到過去的版本,重新開始了!
從舊版本發(fā)展出新的人生,或許我們的人生可以擁有好幾條分支路線呢......
雖然目前看來不太現(xiàn)實(shí),就算真的有得選,阿菌也絕不會(huì)回到過去的版本,因?yàn)榘⒕桓冶WC在另一條人生分支上,還能遇到波姐......
咳咳,打??!
雖然人生沒有存檔,但是電腦上的文件可以存檔!
大家考慮以下場(chǎng)景:假設(shè)學(xué)院安排我們做一次畢業(yè)晚會(huì)宣傳活動(dòng),既要有 PPT,又要有文稿,還得有海報(bào)。
于是我們高高興興地把全學(xué)院的本科生資料編寫到了 PTT,文稿和海報(bào)里。
這個(gè)時(shí)候,學(xué)院說:怎么可以只有本科生的資料?研究生的相關(guān)資料也要加進(jìn)去!
然后我們?cè)诂F(xiàn)有的 PPT,文稿和海報(bào)里,加入了研究生的資料。
接著學(xué)院說:臨時(shí)通知,研究生的晚會(huì)另外舉行!
大家可能就傻眼了:我們已經(jīng)在 PPT,文稿和海報(bào)里加了研究生的信息,而且已經(jīng)和本科生的內(nèi)容融合到了一起,這刪起來也太麻煩了吧?。?!
要是我們提前把本科生的策劃資料保存為一個(gè)版本就好了,這樣就能直接把本科生的版本交給學(xué)院,完成工作。
現(xiàn)在場(chǎng)景有了,痛點(diǎn)有了,接下來我們著手設(shè)計(jì)一款軟件產(chǎn)品來解決這個(gè)問題(有同學(xué)會(huì)說,阿菌你扯淡,我每個(gè)版本復(fù)制一份就行啦,搞個(gè)軟件出來干嘛?呃呃,堅(jiān)持看完就懂啦,它不只存檔這么簡(jiǎn)單哦。我們手動(dòng)拷貝存檔容易出現(xiàn)各種各樣的問題,比如忘了存,忘了存在哪,存的順序搞亂了等等......試過就會(huì)有體會(huì)哦)。
現(xiàn)在我們有一個(gè)文件夾,文件夾目錄下有 PPT,文稿和海報(bào),要不我們就在這里創(chuàng)建多一個(gè)文件夾用于存檔吧!把名字起名為“.jun”就好啦!
現(xiàn)在我們創(chuàng)建好了一個(gè)“.jun”文件夾用于存放當(dāng)前目錄的版本信息,接下來我們要思考的是,該如何保存各個(gè)文件的版本?
在計(jì)算機(jī)領(lǐng)域,我們來到的環(huán)節(jié)應(yīng)該叫設(shè)計(jì)底層數(shù)據(jù)結(jié)構(gòu),我們可以把“.jun”文件夾看成一個(gè)數(shù)據(jù)庫(kù),這個(gè)數(shù)據(jù)庫(kù)會(huì)用來保存當(dāng)前文件夾下文件的版本數(shù)據(jù)。
嗯,不如這樣吧!
我們就把這些.doc、.psd、.ppt文件稱為 object 吧?。ǚ凑膊恢澜惺裁春茫┪覀?cè)凇?jun”文件夾下創(chuàng)建一個(gè)“objects”文件用于存放各個(gè) object 的信息,這樣,我們就有了一個(gè) object 數(shù)據(jù)庫(kù)了!
呃,聽起來好像很厲害,其實(shí)阿菌只是創(chuàng)建了兩個(gè)文件夾......
現(xiàn)在我們想想一個(gè) object 該存些什么東西比較好,究竟什么東西才能精準(zhǔn)定位一個(gè)文件的版本信息呢?
想來想去,不如這樣吧,我們一個(gè) object 至少得包含三個(gè)信息:
1. ?文件的原本信息,我們PPT,WORD文檔中的內(nèi)容就叫原本信息,直接保存原本信息可能需要很大的空間(至少和這些文件本身一樣大),我們可以先壓縮,再保存。
2. object 的類型,考慮到我們當(dāng)前文件夾下除了有 PPT,文稿和海報(bào)之外,以后還可能放新的文件或者新的文件夾,文件和文件夾都應(yīng)該叫 object,只不過可以用不同的類型區(qū)分他們。(大家可以在這留個(gè)心眼,這是這款版本控制管理軟件的精華部分,后面就知道啦)
3. 一串字符數(shù)字,我們起個(gè)專業(yè)點(diǎn)的名字叫哈希值,用于標(biāo)識(shí)當(dāng)前的 object,每個(gè) object 都有獨(dú)一無二的哈希值(其實(shí)就一串亂七八糟的數(shù)字字符,這樣不容易重復(fù))。
看到這里,大家可能會(huì)有疑惑:你們?yōu)樯兑恢痹谡f怎么設(shè)計(jì),我們更想知道的是,為什么這么設(shè)計(jì)?
下面揭示謎底:
大家先看第一張圖,當(dāng)前文件夾下的 PPT,文稿和海報(bào),我們可以分別用三個(gè) object 表示:
由于他們放在同一個(gè)目錄下,于是我們可以用一個(gè)大的 object 來標(biāo)記他們,我們把這個(gè)大 object 的類型定義為 tree(樹干的感覺),這個(gè) tree object 對(duì)等的就不是一個(gè)個(gè)文件了,而是一個(gè)文件夾:
細(xì)心的讀者朋友會(huì)發(fā)現(xiàn):咦?這個(gè) tree object 貌似已經(jīng)包含了當(dāng)前文件夾下的所有文件信息,也就是說,這個(gè) tree object 貌似已經(jīng)可以記錄當(dāng)前文件夾的版本信息了!
我們可以通過這個(gè) tree object 找到PPT,文稿和海報(bào)對(duì)應(yīng)的 object 們,這些 object 保存有PPT,文稿和海報(bào)某個(gè)時(shí)間點(diǎn)的原信息,我們只要把這些信息解壓出來,就能把文件夾恢復(fù)到曾經(jīng)存檔時(shí)候的樣子了。
接下來的問題是:我們不會(huì)只保留一份存檔,我們會(huì)保存很多份存檔,如何才能把一系列存檔組織起來呢?
接下來我們引入一個(gè)新的 object 類型,我們叫起名叫 commit 類型好了,就是一個(gè)提交的意思。想要把存檔串聯(lián)起來,我們得加上一項(xiàng)參數(shù),指明上一個(gè) commit object 是誰,或許我們還可以加上時(shí)間等信息,這樣一來,每一次提交就是一個(gè)版本:
上面這個(gè)圖由于位置不夠,畫得不夠直觀,我們?cè)佼嬕粋€(gè)圖,大家明白每個(gè) commit object 都指向一個(gè) tree object 就行了。也就是說:commit object 只是 tree object 的一層封裝,雖然還原出一個(gè)文件夾過去的存檔我們只需要 tree object,但封裝成了 commit object 后,通過“father”這個(gè)屬性,就能把一系列的存檔連起來,而且還能蓋上時(shí)間戳,這樣整個(gè)存檔記錄就很清晰了。
講到這里不知道大家會(huì)不會(huì)冒出一些奇怪的想法,假設(shè)學(xué)院布下了兩個(gè)任務(wù):我們本科生,既要和研究生搞一次畢業(yè)聯(lián)誼,又要和博士生搞一次畢業(yè)聯(lián)誼,我們這套版本管理系統(tǒng)還能用上嗎?(也只有阿菌才能想出這樣奇奇怪怪的活動(dòng))
當(dāng)然可以啦,請(qǐng)看下圖:
我們可以創(chuàng)建兩個(gè) commit object(兩個(gè)新的存檔),分別指向最開始保存了本科生資料的存檔,然后我們就能分別在兩個(gè)新的存檔上干活啦,而且兩個(gè)存檔互不影響,可以繼續(xù)在兩個(gè)存檔之后建立新的存檔,就像下面這樣:
我們可以給上面的功能起個(gè)好聽的名字,叫“分支”。
分支的功能還可以這樣用,我們考慮下面這種場(chǎng)景:
學(xué)院不再搞花樣了,只要做好本科生的畢業(yè)活動(dòng)策劃就行。但是學(xué)院規(guī)定的時(shí)間很短,阿菌一個(gè)人做不完,他找來了他的同學(xué)阿叉和阿勾一起做,三個(gè)人分別負(fù)責(zé)PPT,文稿和海報(bào)。
為了不影響最開始的版本,他們?nèi)齻€(gè)每人拉出來一條分支進(jìn)行工作,每個(gè)工作階段的內(nèi)容照樣進(jìn)行版本管理:
等各自的工作完成后,合并出最新的版本:
這樣一來,大家協(xié)同工作起來就方便多啦。
可能有同學(xué)會(huì)問:阿菌,你設(shè)計(jì)的 commit object 不是有一個(gè)父指針么?每個(gè)父指針指向上一個(gè) commit object,在上面的圖里,最終版的父指針指向誰呀?貌似一共有三個(gè)父節(jié)點(diǎn)?
呃,這個(gè)確實(shí)是阿菌疏忽了,圖沒有畫好......
是這樣的,有時(shí)候我們數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)好了,最好就不要改了,遇到現(xiàn)有數(shù)據(jù)結(jié)構(gòu)不能掌控的場(chǎng)景,我們就要設(shè)計(jì)處理流程,這就是所謂的“算法”吧......(畢竟增刪改查某種程度也算是算法......)
我們總是說:軟件 = 數(shù)據(jù)結(jié)構(gòu) 算法,下面阿菌帶大家還原這個(gè)處理流程的設(shè)計(jì)。
因?yàn)槲覀兊?commit object 只能指向一個(gè)父節(jié)點(diǎn),于是我們?cè)O(shè)計(jì)的合并流程是這樣子的:最終的合并,交給一個(gè)人處理(站在一條分支上處理,假設(shè)叫它主分支)。
從上圖可以看到,我們以阿叉制作PPT的分支作為主分支,把阿勾的分支內(nèi)容合并到了阿叉的分支上,這樣阿叉的分支就多出來一個(gè)合并節(jié)點(diǎn),這個(gè)合并節(jié)點(diǎn)指向的是阿叉分支上的前一個(gè)節(jié)點(diǎn),這樣一來,PPT 和文稿內(nèi)容就合并到主分支上了。
滴滴滴~ 下面我們把阿菌那壞小子制作的海報(bào)也合并上去:
這樣一來,我們既沒有破壞原來的數(shù)據(jù)結(jié)構(gòu),也沒有破壞軟件的設(shè)計(jì):一個(gè)存檔版本管理軟件,我們可以在阿叉這條主分支上找到所有的內(nèi)容。
當(dāng)然阿菌這樣的設(shè)計(jì)不太好,我們其實(shí)可以設(shè)計(jì)得更好,比如說單獨(dú)抽出來一條主分支,阿叉,阿勾,阿菌制作各自內(nèi)容的時(shí)候單獨(dú)拉分支(一共四條分支),每個(gè)人都制作好了,再合并回主分支。這樣主分支就會(huì)非常干凈利落,而不是像現(xiàn)在這樣,在主分支上,還能看到阿叉的各個(gè)版本......
呃呃,軟件開發(fā)就是這樣的啦,在摸索中不斷總結(jié)最佳實(shí)踐!
估計(jì)大家看著看著就能看出來啦:阿菌,你這講的不是 Git 么?
沒錯(cuò),阿菌今天介紹的就是一款叫 Git 的分布式版本控制軟件-版本控制部分的底層設(shè)計(jì)原理,現(xiàn)在大多數(shù)程序員都是基于 Git 進(jìn)行協(xié)同開發(fā)的。和上面例子不同的是,程序員寫的是代碼文件,而不是文稿和PPT。有時(shí)候一個(gè)功能往往會(huì)有好幾個(gè)程序員開發(fā),大家可以理解為分組開發(fā)。常見主流的協(xié)作流程會(huì)是這樣的:
簡(jiǎn)單介紹一下:
一、master 分支存儲(chǔ)了正式發(fā)布的歷史版本,是一個(gè)功能完整且隨時(shí)可以發(fā)布到線上進(jìn)行部署的可用分支。
二、Hotfix 分支是用來修復(fù)線上bug,快速打補(bǔ)丁的。
三、Release 是一個(gè)發(fā)布分支。
四、Develop 一般作為功能的集成分支。
五、 Feature 分支則是功能分支。
至于每個(gè)分支具體的用法,大家可以到網(wǎng)上搜索,根據(jù)阿菌的經(jīng)驗(yàn),其實(shí)看了也沒啥用。只有真正到公司里參與到開發(fā)項(xiàng)目中,才能真正領(lǐng)會(huì)到各個(gè)分支的意義。
今天介紹的內(nèi)容,是 Git 這款軟件最基本的原理,在搞清楚了這個(gè)的基礎(chǔ)上使用 Git,會(huì)輕松很多。Git 的另一個(gè)重要的特色是:分布式。也就是說,它是用于多人(公司或團(tuán)隊(duì))協(xié)同進(jìn)行存檔和版本控制的。
有位大神看了我們的文章后認(rèn)為如果我們能講講分布式,那這篇文章會(huì)更加加分,那肯定沒問題。
我們現(xiàn)代的程序員,寫代碼的時(shí)候上來就是用 Git,理所應(yīng)當(dāng)以為版本管理系統(tǒng)都是現(xiàn)在分布式的樣子。殊不知,以前的版本管理系統(tǒng)都是集中式的。
我們先簡(jiǎn)單介紹一下什么叫集中式,還是用本文的案例:
像上圖這樣,有一個(gè)集中的地方管理所有文件,每個(gè)人開發(fā)只要拉取特定文件進(jìn)行開發(fā),這叫集中式開發(fā)。
集中式開發(fā)的弊端大致有以下兩點(diǎn):
- 效率。中央倉(cāng)庫(kù)出了問題,所有人都無法正常工作了,因?yàn)榇蠹叶家蕾囁『屯扑臀募?/span>
- 穩(wěn)定性。中央倉(cāng)庫(kù)掛了,存檔就沒了。
針對(duì)以上弊端,我們能想到的,最直接的處理方式是:每個(gè)人都保存一份倉(cāng)庫(kù)的拷貝。如下圖所示:
有同學(xué)會(huì)問,欸,阿菌,那我還有必要學(xué) SVN 那些集中式版本管理軟件么?
當(dāng)然不用,Git 的出現(xiàn),已經(jīng)完全顛覆了過去的集中式版本管理系統(tǒng)。SVN 的版本管理策略和 Git 還是有很大區(qū)別的,阿菌沒有說 SVN 不好,大家要知道,這里涉及的原因很多。這是時(shí)代變化所引起的變革,以及大環(huán)境所需導(dǎo)致的變遷,不存在孰好孰壞的問題。
我們嘗試開辟一個(gè)角度想(不一定對(duì)):在以前的年代,內(nèi)存,磁盤,計(jì)算資源都是很寶貴的,當(dāng)時(shí)的機(jī)器根本就不適宜支持我們?cè)诿颗_(tái)機(jī)器上,保存完整的工程文件存檔,也不可能采用 Git 這種壓縮保存整個(gè)文件原信息的策略(增量保存能省很多空間,代價(jià)是犧牲性能)。所以,使用 SVN 這樣集中式的版本管理系統(tǒng),或許是個(gè)很好的選擇。
現(xiàn)在機(jī)器越來越好了,磁盤大,網(wǎng)絡(luò)快,直接就能在每個(gè)人的機(jī)器上保存完整副本。更重要的是,Git 本身的設(shè)計(jì)非常優(yōu)良,它站在 Linux 操作系統(tǒng)肩上發(fā)家(最初是因?yàn)橄敕植际介_發(fā) Linux 而創(chuàng)造的 Git),后來還發(fā)展出了 Github 這樣的開源社區(qū)。慢慢地,大家都愿意遷移到 Git 上開發(fā)了。
再換一個(gè)角度:大家想想,如果一個(gè)工程真的非常非常龐大,單臺(tái)機(jī)器不能拉取整個(gè)工程進(jìn)行開發(fā),集中式的版本控制無疑是更好的選擇。
但是現(xiàn)在業(yè)界流行微服務(wù),系統(tǒng)的拆分解耦是大勢(shì)所趨,這也注定了大工程會(huì)被拆分成小工程。而體量小的工程,恰好非常適合使用分布式版本控制。
存在即合理,任何一項(xiàng)技術(shù),我們?cè)谠u(píng)價(jià)它時(shí),都不能脫離時(shí)代背景和現(xiàn)實(shí)需求。
我們這篇文章不教操作,關(guān)于 Git 操作的文章,網(wǎng)上一抓一大把,各種奇技淫巧,應(yīng)有盡有。
各位如果想自己玩出奇技淫巧,那就跟著阿菌一起深入數(shù)據(jù)結(jié)構(gòu)探索原理吧,那些只教奇技淫巧的博客,通常都不怎么說原理,懂了原理才能更好地發(fā)掘奇技淫巧吖!
有同學(xué)可能會(huì)問:阿菌,原理懂了,但是不太能和操作對(duì)應(yīng)起來,我們平時(shí)用 Git,就是一條 pull 和一條 push,兩條指令走天下。分支我是懂了,但這些分布式操作我還不太懂,不太能聯(lián)系起來。。。
阿菌實(shí)習(xí)時(shí)的導(dǎo)師,曾教給阿菌一個(gè)非常重要的學(xué)習(xí)方法:當(dāng)我們看到一個(gè)沒接觸過的東西的時(shí)候,我們首先要想 ——?如果這個(gè)東西交給你來做,你會(huì)怎么實(shí)現(xiàn)?
阿菌看過一點(diǎn)點(diǎn) Git 的源碼,也翻過官方文檔,但是不可能在這里把這些東西全倒出來,我們嘗試引導(dǎo)大家一點(diǎn)點(diǎn)思考。(最終的深入精確學(xué)習(xí),還是建議大家看源碼文檔,想要深入學(xué)一個(gè)東西,這苦是不得不吃的)
我們沿用這個(gè)圖進(jìn)行講解,在看之前,大家務(wù)必先搞清楚 object 的底層數(shù)據(jù)結(jié)構(gòu),以及怎樣把 object 串聯(lián)起來形成分支。我們先回顧一下當(dāng)時(shí)的總覽圖:
之前我們提到以阿叉作為主分支,是一個(gè)不太好的設(shè)計(jì),但是不影響我們講解,我們假設(shè)阿叉已經(jīng)開發(fā)完了,遠(yuǎn)程倉(cāng)庫(kù)就是阿叉的倉(cāng)庫(kù)。
現(xiàn)在阿勾已經(jīng)開發(fā)完了,阿勾會(huì)把他的本地倉(cāng)庫(kù)推送到遠(yuǎn)程倉(cāng)庫(kù),阿勾的倉(cāng)庫(kù)是這樣子的:
阿菌能猜到一些小伙伴看到這懵逼了,千萬不能懵逼,一定要全文串聯(lián)回憶,我們的版本管理軟件,就是一條鏈表加樹,這里的每個(gè)圈圈都是一個(gè) commit object,他們是對(duì)應(yīng)的,阿菌只是用一個(gè)小圈圈代替,以節(jié)省位置:
明白了這個(gè)之后,我們來看一下合并的流程,首先阿勾會(huì)把自己的本地倉(cāng)庫(kù)推送到遠(yuǎn)程倉(cāng)庫(kù)(主分支):
現(xiàn)在,大家明白,為什么合并分支會(huì)多出來一個(gè)提交了吧?
還有一些常用的指令,什么 rebase 吖,不就是調(diào)整鏈表么?不懂的話可以去刷刷算法題,打打基礎(chǔ)。
不過,多人一起辦公難免會(huì)遇到一些問題,比如說大家同時(shí)修改了一個(gè)文件,發(fā)生了沖突。但懂了原理后這都很好解決,大家溝通一下用誰修改的版本就好了,協(xié)商出一個(gè)不沖突的版本,然后合并就行。合并的原理上文已經(jīng)提到啦,一通百通。
也正由于是多人協(xié)同辦公,注定我們的軟件要具備聯(lián)網(wǎng)的能力,涉及許多網(wǎng)絡(luò)交互,這是分布式軟件必不可少滴內(nèi)容。但大家要相信,最底層原理懂了,具體如何使用,如何與他人在網(wǎng)絡(luò)中合作,那都是很簡(jiǎn)單的內(nèi)容,學(xué)一個(gè)軟件關(guān)鍵是要學(xué)透它的底層數(shù)據(jù)結(jié)構(gòu),學(xué)明白后,上層操作都會(huì)引刃而解的。