從Git設計原理到業務系統設計與開發

餓了麼物流技術團隊發表於2019-01-11

作者簡介

近鐵Jeff Dean, 餓了麼物流研發部搬磚工, 崇拜各種大牛, 由於Jeff Dean的作品深而廣, 本文特意專注於小而美的Linus Torvalds 第二作品:Git, 希望從中汲取營養, 向大師致敬。

文介紹Git核心優點和實現思路,重點引申出對業務系統設計與開發的啟示; 由於篇幅有限,本文不詳細介紹Git命令的具體執行細節,如果需要了解執行Git命令時,底層具體發生了什麼, 請移步Git官方文件第十章 Git Internals

為什麼關注Git?

Git作為一個前無古人,很可能後無來者的內容地址跟蹤器 , 席捲各大公司,深受所有程式設計師喜愛(覺得Git難用的除外), 肯定有他的過人之處。

先搬出網際網路技術關鍵詞標配:

分散式、去中心化、可靠性、容錯性、可用性、資料一致性、效能是一個功能, 而不是一個優化等。

Git都滿足有木有! Linus用兩週就實現自舉的Git十幾年後依然健壯如初。 假如我寫一個介面或設計一個微服務或寫一個小工具, 自帶擴充套件技能,從來不掛,QPS暴增也無需推翻重構,外部依賴抖動時自動降級,資料量增大而沒有拖垮效能, 能與Git有很多共同點,豈不很有成就感?

Git最初設計時要達到的目的

分散式:1)多人同一個分支不同地點不同時間並行開發;2)單人本地多分支並行開發。

效能: 速度要快。慢是Linus Torvalds本人無法接受的,因為linux核心每天有成百上千次提交。

安全與信任: 即可靠性, 我push上去的程式碼pull下來一定還是我的程式碼, 沒有丟失或被惡意篡改過。

Git 資料建模

插播一條語錄: Bad programmers worry about the code. Good programmers worry about data structures and their relationships. - by Linus Torvarlds. (一般的程式設計師只關心程式碼,而優秀的軟體工程師更關心資料結構以及他們之間的關係。)

啟示

Linus本人寫C語言比較多,個人認為這裡的data structures and their relationships 如果脫離C語言 的特定背景,可以泛指技術對場景或業務邏輯的抽象, 比如: 物件導向建模,領域驅動, 甚至更巨集觀的架構設計或微服務怎樣劃分。

根據個人經驗,如果系統設計的好, 程式碼稍微寫low一點,整個系統的表現也不會太差, 而且程式碼的坑比較容易填; 反之,再漂亮的程式碼也很難填上系統設計的坑, 只能面臨重構。

Git 內部檔案處理:

  1. 程式碼庫裡所有檔案都是資料,以資料為中心,所有操作、儲存和處理邏輯圍繞資料展開。
  2. 跟蹤整個專案整體狀態,每次建立全域性快照,而不是跟蹤每個檔案的變更。
  3. 針對git目錄下的每個檔案計算一個hash值, 檔案內容作為value, hash值為檔案key。
  4. 如果單個檔案內容發生變化, 下次重新計算hash值。如果檔案沒有發生變更,當前快照指向歷史hash。
  5. 整個程式碼庫的變化歷史和檔案組成用樹形結構表示。
  6. Git每次commit記錄整個程式碼庫的一次快照, 當前快照包含發生變化的檔案和歷史快照(子樹)。
    圖1 - Git資料建模
    Git資料建模

Git 執行效率與時間複雜度

對於Git內部結構可以簡單的理解為: Git內部是一棵 , 每個節點都是一個指標(key), 這個指標(key)可能代表一個檔案,或一次commit或一個分支起點或一次merge或一個tag, key對應的value就是內容, 如果key是代表一個檔案, value就是檔案內容; 如果key是代表一次commit, value是一顆子樹, 包含此次commit對整個專案的snapshot。

平時很多git操作都可以近似理解為: 在樹上執行遍歷查詢O(lg(n)),切換指標O(1),然後根據指標取檔案內容O(1))。 這些操作速度都是很快的,只有在網路互動,檔案壓縮與解壓和計算diff時,人肉可以感知到有時間等待。

啟示

  1. 資料結構和演算法原來是這麼用的,基本功還是要紮實呀。
  2. LeetCode還是要常刷的。
  3. 建模很重要。越好的設計模型離被推翻重構的距離越遠。 關於架構設計和規劃的 做技術選型和概要設計,P7級別的就要做到, 1年以後當別人接手時就不需要考慮重構,如果是P8的就有信心做到2年以後,而P9的則是3年或更長時間。 Git已經經歷了超過10年的考驗了。

Git 空間壓縮與訪問效率平衡

對於Git,近期發生變化的資料屬於熱資料,Git假設這些資料會被頻繁訪問或使用到。其他資料為歷史資料。 對於熱資料, 即使發生微小變化,Git也會全量冗餘儲存,提高訪問效率。 當熱資料檔案數量達到一定值時,會觸發打包壓縮邏輯, delta差異儲存,節省空間。

啟示

對於不同的儲存介質,例如db, redis, mq, 選用不同的儲存邏輯或策略,以達到訪問效率與儲存空間的平衡。

Git 安全性與容錯性

Git對檔案內容和專案整體snapshot都使用hash值表示,hash值與內容一一對應, 如果檔案內容被篡改或硬碟損壞 導致資料丟失,hash值校驗都會失敗。 此時Git設計時已經假設:

  1. 硬碟是隨時崩潰的,即儲存是不可靠的
  2. 有人惡意引入Bug或偷偷修改程式碼

啟示

在分散式環境下,設計系統或介面,能否保持容錯性,自帶降級,建議多向自己提出假設:

  1. 網路是不可靠的。 介面呼叫timeout和失敗是必然存在的, 邏輯應該怎樣處理?是否自帶降級技能。
  2. DB是會抖動的,快取也會失效,主從延遲一定存在的,此時程式碼邏輯能否相容?如果fail fast, 是否有報警機制?
  3. 對外的介面,呼叫方的傳參要考慮最壞的場景,是否有合理的入參校驗/防重/冪等處理?

Merge 演算法思想: 三路合併

先找出兩個分支的公共祖先, 然後兩個分支分別與公共祖先diff,指出有衝突的地方。 Git merge並沒有試圖智慧的去解決衝突,只是指出衝突,然後將merge交給最合適最高效的人去解決: 即引起衝突的開發者。

啟示

  1. 選用最合理的資料結構、索引結構或演算法思路,不斷優化系統處理速度。
  2. 劃清邊界,只做自己最擅長和應該做的事情, 儘量保證高內聚,低耦合 。 微服務環境下,在設計系統時,應該多考慮: 各個服務呼叫時,不同的異常誰負責處理; 介面超時誰負責補償; 資料一致性交給誰來保證等。

Linus Torvalds眼中好的程式碼和好的軟體工程師

好的軟體工程師要有good taste, 堅持不懈的追求用正確的方式解決問題。

好的程式碼, 舉個例子:移除連結串列中某個節點

一般的程式碼, 用if else 判斷邊界值:

void remove_list_entry(entry)
{
    prev = NULL;
    walk = head;

    // Walk the list
    while (walk != entry){
        prev = walk;
        walk = walk->next;
    }

    //Remove the entry by updating the
    //head or the previous entry

    if(!prev)
        head = entry->next;
    else
        prev->next = entry->next;
}
複製程式碼

好的程式碼, 換一種寫法,使正常處理邏輯可以相容邊界值:

void remove_list_entry(entry)
{
    //The "indirect" pointer points to the
    // *address* of the thing we'll update

    indirect = &head;

    //Walk the list,looking for the thing that
    //points to the entry we want to remove

    while ((*indirect) != entry)
        indirect = &(*indirect)->next;

    // .. and just remove it
    *indirect = entry->next
}

複製程式碼

啟示

平時編碼中對邊界值的處理是否優雅?

現在一個變數的傳遞會經過多種程式語言和中介軟體,中間過程一般有序列化和反序列化, 給空物件賦預設值等邏輯, 怎樣保證實際結果與預想的完全一致? 邊界值處理不好,一是程式碼不好維護,二是容易引入Bug。 日常碰到的邊界值有: null, int預設值0等。

其他

  1. 從使用者角度出發設計系統。Linus是Git的第一個也是最粘性的使用者, 在寫Git之前他就已經瞭解了當時的各個版本控制 軟體的優缺點,他清楚Git需要實現哪些功能, 哪些功能無需實現。
  2. Git在命令列執行命令時,響應的提示資訊及時且準確。
  3. 站在巨人肩膀上,向已有系統學習。Git最初的核心命令都是複用作業系統已有的功能,例如diff, 壓縮,檔案處理等。

啟示

  1. 自己寫的系統或功能,自己要從終端使用者角度嘗試使用或模擬使用。
  2. 對於api介面,研發負責自測; 寫單元測試時,可以從呼叫方角度思考介面名稱和引數設計是否合理,是否丟擲異常, 返回資訊中是否包含錯誤碼和提示資訊。
  3. 既要有造輪子的能力,又要有不重複造輪子的覺悟。 多向開源系統學習,多研究底層原理,大部分原理或設計思路都是相通的。

彩蛋

(如下為Linus Torvalds語錄,僅供娛樂。 Linus Torvalds以噴人聞名,網路上很多人指責他人品不行, 很少有人評價他的技術水平,可能是沒有能力評價吧。)

  1. "In fact, I am a very cynical and untrusting person. I think most of you are completely incompetent". In front of a large group of Google developers. 翻譯:在一群谷歌軟體工程師面前說: 各位不要誤會, 我不是針對你, 我是說在座的各位寫程式碼的水平完全都是垃圾。
  2. "Because nobody actually creates perfect code at first time around except me, but there's only one of me. " 翻譯:實際上沒人能一次就寫出完美的程式碼,除了我。但是世界上只有一個我。ps: 這句話也是在一群谷歌軟體工程師面前說的。

參考

  1. Linus Torvalds 2007年在谷歌分享Git
  2. Linus Torvalds TED 採訪
  3. Git官方文件





閱讀部落格還不過癮?

歡迎大家掃二維碼通過新增群助手,加入交流群,討論和部落格有關的技術問題,還可以和博主有更多互動

從Git設計原理到業務系統設計與開發
部落格轉載、線下活動及合作等問題請郵件至 shadowfly_zyl@hotmail.com 進行溝通

相關文章