版本控制軟件種類繁多,維基百科收錄的最先的版本控制系統是 1972 年貝爾實驗室開發的 Source Code Control System。1986 年 Concurrent Versions System(CVS) 誕生,CVS 曾很是流行,但今時用之寥寥無幾,不過 OpenBSD 仍在使用 CVS。2000 年 CollabNet 建立了 Subversion 項目,2009年,Subversion 被 Apache 基金會接受成爲頂級項目並被命名爲 Apache Subversion。2005 年 Linus Torvalds 建立了 Git,2007 Github 誕生後,Git 隨着 Github 的發展愈發流行,14 年間,Git 成爲了最流行的版本控制系統,不管是 Windows 仍是 Linux 或是 Android,MySQL 等等大型軟件都使用 git 進行版本控制。縱觀版本控制系統流行史,前有 CVS 後有 SVN,今日 Git 更風流。俱往矣,數風流人物,還看今朝,版本控制系統莫不如斯。前端
與 CVS/Subversion 這種集中式版本控制系統不一樣的是,Git 的存儲庫數據會被存儲在本地,提交也是發生在本地,遠程能夠看做是本地存儲庫的一個鏡像。而 CVS/Subversion 的提交都是在線的。這就是分佈式版本控制系統的核心特徵。(理解這一問題的關聯在於區分工做樹 worktree
和存儲庫 repository
。)mysql
Git 的源碼託管在 git.kernel.org 上,Github 上也有隻讀鏡像 github.com/git/git。Git 主頁 https://git-scm.com 的網頁源碼則託管在 Github 上。一般給 git 提交 PR 須要註冊 public-inbox.org 郵件列表,而後發送補丁。者一般比較麻煩,好在有微軟開發者Johannes Schindelin 使用 TypeScript 開發 gitgitgadget ,當你在 Github 上像 gitgitgadget/git 提交 PR 時,gitgitgadget 會將你的 PR 發送到 public-inbox,一旦補丁被 git 維護者接受,gitgitgadget 則會關閉那個 PR。gitgitgadget 簡化了給 git 貢獻代碼的難度,省去了註冊 Inbox 的麻煩,這年頭開發者大多都有 Github 賬號。我就使用 gitgitgadget 給 git 提交了一個補丁用於支持 HTTP/2。git
Johannes Schindelin 此人也是 git-for-windows 的維護者。 Git 的維護者則是 Google 的開發者 Junio C Hamano。大多數 Git 開發者來自於 Google/Microsoft(包括 Github)。libgit2 的開發者主要來自 Microsoft(包括 Github)。而 JGit 的開發者則主要來自 Google。已故 JGit 的創始人 Shawn Pearce 還開發了著名的 Gerrit Code Review。這些開發者的無私奉獻才能使咱們用上這麼優秀的版本控制系統,感謝他們的付出。github
Git 與遠程存儲庫之間的傳輸協議有 HTTP, GIT(git://
),SSH. 在 《Pro Git - 2nd Edition》4.1 Git on the Server - The Protocols 中有介紹。其中 HTTP 協議包括啞協議和智能協議,因爲啞協議是隻讀協議,目前大多數代碼託管平臺均再也不提供支持。HTTP 智能協議和 GIT 協議,SSH 協議相似,都是特定幾組 客戶端/服務端 git 命令之間的輸入輸出數據傳輸和交換。Git 傳輸協議較爲簡單,以智能傳輸協議 v1 爲例,基本的 fetch/push
流程以下:web
Git 拉取流程:算法
Git 推送流程:sql
雖然在 2018 年 5 月,git 推出了 Wire Protocol
(即 Git v2 協議),增長了 Git 協議的複雜性,但在服務器上支持 git 協議(包括 v2 協議)仍然只須要在服務器上運行 git-upload-pack/git-receive-pack。這使得開發者很容易實現對 git 協議的支持。正由於 Git 協議表徵的簡單,因此針對不一樣的用戶和存儲庫數量規模,Git 也都比 Subversion,Mercurial 有更多的選擇。shell
Git 使用文件快照記錄文件變動,當對象存儲到鬆散文件目錄時,每一次大小不變的文件修改至關於存儲庫中增長特定文件的大小,Git 使用 zlib deflate 壓縮對象,對象頭包括對象類型,原始大小。基於快照的方式使得 Git 在提交代碼,檢出文件時都比較高效,但存儲庫的佔用缺比較高。但運行 git gc
時,Git 會將鬆散的對象打包到 pack 文件中,這個時候會使用特定的機制存儲一部分文件的 OFS_DELTA
,這樣就能節省一部分空間。數據庫
zlib(deflate) 壓縮算法一般來講除了沒有版權限制,不管是壓縮比仍是速度,CPU 使用率都不是一個最佳的選擇,引用來自的 https://github.com/facebook/zstd 基準測試,zlib 看起來必後起之秀 brotli
/zstd
差多了:windows
Compressor name | Ratio | Compression | Decompress. |
---|---|---|---|
zstd 1.4.0 -1 | 2.884 | 530 MB/s | 1360 MB/s |
zlib 1.2.11 -1 | 2.743 | 110 MB/s | 440 MB/s |
brotli 1.0.7 -0 | 2.701 | 430 MB/s | 470 MB/s |
quicklz 1.5.0 -1 | 2.238 | 600 MB/s | 800 MB/s |
lzo1x 2.09 -1 | 2.106 | 680 MB/s | 950 MB/s |
lz4 1.8.3 | 2.101 | 800 MB/s | 4220 MB/s |
snappy 1.1.4 | 2.073 | 580 MB/s | 2020 MB/s |
lzf 3.6 -1 | 2.077 | 440 MB/s | 930 MB/s |
當開發者要將 git 集成到其餘軟件或者系統中時,能夠經過命令行調用 git 命令捕獲輸出,也可使用 libgit2/JGit 等庫。
libgit2 最初是由 Shawn Pearce 建立了初始 commit。目前主要維護者來自微軟。libgit2 提供一些基礎的 API,功能基本上是完整的,除了一部分實現性能沒有 git 那麼好,其餘方面使人滿意,而且有多種語言綁定,包括 C++/D/Golang/Ruby/.NET/Node.js/Perl/Perl6/Ruby/Rust 等等。Gitee 原生鉤子就使用了 libgit2,Gitee-gitlab 項目使用了 rugged。
JGit 也是有 Shawn Pearce 建立的,目前屬於 Eclipse 基金會,運行在 JVM 上,國內騰訊的工峯的 TGit 也是使用的 JGit。
在 Git Rev News 第48期,編輯推薦了 gitbase 經過 SQL 的方式查詢 git 存儲庫,這個工具基於 src-d/go-git,go-git 是純 Golang 實現的,若是基於 Golang 的項目須要簡單的讀寫存儲庫,可使用 go-git。與 libgit2 的 Golang 綁定 git2go 相比,不須要使用 CGO。
固然還有一些其餘的 git 實現,大可能是實驗性的,不建議用於生產環境,好比基於 Rust 的 git-rs。
Git 最初由 Linus Torvalds 開發用來取代 BitKeeper 做爲 Linux 內核源碼的版本控制工具,因此 Git 一直和 Linux 內核源碼託管在同一個服務器上。官方地址是:https://git.kernel.org/。在 git.kernel.org 上,Git 代碼託管功能是由 git 內置的工具實現的。用戶使用 HTTPS 協議訪問 https://git.kernel.org/ 時,Nginx 會以 CGI 的方式將瀏覽器的請求轉發到 GitWeb。GitWeb 是一個使用 Perl 編寫的 CGI 程序,爲用戶提供簡單的 git 在線交互圖形界面。GitWeb 的源碼地址能夠在 Github Git 鏡像 中查看。GitWeb 界面比較不夠精美,相比於 Github 這樣的代碼託管平臺,功能寥寥無幾。當用戶須要使用 HTTP/HTTPS 協議拉取推送源碼時,Nginx 會以 CGI的方式將請求轉發給 git-http-backend 處理。git-http-backend 是 Git Over HTTP 的服務端實現。當用戶 GIT 協議 (git://
) 在 git.kernel.org 上拉取源碼是,請求會被 git-daemon 處理。git-daemon 默默的監聽 9418 端口,靜靜的等待 git 客戶端的訪問。
使用 Git 內置的 GitWeb/git-http-backend/git-daemon,咱們可以搭建一個簡易的 Git 代碼託管服務器,但這裏沒有 SSH 協議支持。而實現 SSH 協議支持也很是簡單,只須要在服務器上運行 sshd
(OpenSSH),並容許命令 git-upload-pack/git-receive-pack/git-upload-archive
命令的運行,對於 SSH 協議的驗證,咱們則可使用 authorized_keys
機制,將須要容許的用戶的 SSH 公鑰添加到 authorized_keys
文件。
這種方案一般使用 Gitolite 加強訪問控制,Gitolite 主要使用 Perl 編寫,這和 GitWeb 一致,ssh 的驗證是將 gitolite-shell 添加到 ~/.ssh/authorized_keys
中被 sshd 調用實現的。git.kernenl.org 正是使用 Gitolite 實現 Git Over SSH 訪問控制。
https://git.kernel.org/ 網站託管了 Linux 內核源碼,驅動,文檔等大概有 1000 多個存儲庫,較大的存儲庫好比 Linux 內核源碼磁盤佔用大概是 2GB,所以在理想狀況下,一塊 2TB 磁盤的服務器即可支撐 https://git.kernel.org/ 這個網站的運行(實際狀況則並非如此,因爲 Linux 內核的流行,git.kernel.org 的請求將比較多,對硬件的需求將更高一點)。基於 Git 內置功能搭建的代碼託管服務,麻雀雖小五臟俱全,不過回過頭來講,這樣的代碼託管服務功能有限,可伸縮性和擴展性不佳。
當用戶須要搭建一個幾人到幾十幾百人規模的 Git 代碼託管服務,一般有很是多的選擇,下面是幾個目前仍然比較活躍的小型 Git 代碼託管平臺。
名稱 | 平臺 | 語言 | 技術概述 |
---|---|---|---|
Bonobo Git Server | Windows Only | C# | 基於 .Net Famework 4.6(遷移到 .Net Core 的建議在 2017 年便被提出,但截至目前仍爲遷移到 .Net Core)。使用 LibGit2Sharp 操做存儲庫,但版本較老,不支持 SSH 協議訪問。 |
Gogs | Cross Platform | Golang | 基於 Golang 編寫,Web 讀寫 Git 存儲庫由 git-module 封裝 Git 命令實現,SSH 由 Golang crypto/ssh 提供,支持多種數據庫,是一個極簡的代碼託管平臺,能夠在 Raspberry Pi 上運行 |
Gitea | Cross Platform | Golang | 是 Gogs 的開源分叉,Web 讀寫 Git 存儲庫使用了 src-d/go-git,使用 gliderlabs/ssh 提供 SSH 接入功能,支持多種數據庫,能夠在 Raspberry Pi 上運行。 |
GitBucket | Cross Platform | Scala/Java | 使用 Apache Mina SSHD 實現 SSH 功能。Mina SSHD 還專門針對 JGit 實現了一個 sshd-git 模塊,但 GitBucket 是直接使用 JGit 的 transport 相關類。Eclipse JGit 主要由 Google 開發者參與貢獻。 |
除了上述定位爲代碼託管平臺的服務,還有像 Phabricator 這樣的 Web 軟件也提供 Git 代碼託管功能,但 Phabricator 的重點更可能是缺陷追蹤,代碼審覈。LLVM https://reviews.llvm.org/ 和 libssh https://bugs.libssh.org/ 就是基於 Phabricator。
隨着用戶規模和存儲庫規模的增加,達到必定級別後,上述代碼託管平臺每每變得力不從心,而下面的代碼託管平臺卻深耕於此,可以支撐巨大規模的用戶量和存儲庫數量。
Github 是全球最大的代碼託管平臺,目前 Github 官方數據顯示註冊用戶數量爲 4000萬,項目數量爲 1億。Github 網站主要的技術是 Ruby on Rails 內部進程名爲 github-unicorn
,最近他們將其升級到了 Rails 6.0。Github 使用 Spokes 負責文件系統上存儲庫的複製,同步和備份。Github 以前使用 libssh 開發 Git SSH 服務器,目前的 SSH 服務器的標識爲 babeld-*
,但不肯定 babeld 是否依然基於 libssh。Git 驗證服務爲 github-gitauth
。Github 的大多數服務都是閉源的,所以分析 Github 的技術內幕一般是 Github 官方的一些技術博客, 固然也能夠分析 Github Enterprise
去窺測 Github 內幕。
關於 Github Spokes 的大體原理能夠閱讀 Introducing DGit 和 Building resilience in Spokes。
在開發 Gitaly 以後, Gitlab 擺脫了 NFS 的禁錮,在平臺的伸縮性方面獲得了巨大的提高。要知道 Gitlab 使用 Gitaly 的緣由能夠閱讀 The road to Gitaly v1.0。Gitaly 使用 RPC 將存儲服務器上的 git 命令包轉成前端服務機器上的 git 命令,併爲 gitlab 服務提供存儲庫的讀寫。 Gitlab 的 SSH 功能仍然由 OpenSSH 提供,而一些靜態資源,文件下載,附件等功能則由 Golang 編寫的 gitlab-workhorse 實現,gitlab-workhorse 須要與 Gitaly 通訊。
Bitbucket 是 Atlassian 開發的代碼託管平臺,與 Github/Gitlab 不一樣,Bitbucket 還提供了原生 Mercurial 支持,不過最近,Bitbucket 宣佈要逐步關閉 Mercurial 的支持。Atlassian 還開發了 Jira/Sourcetree 這樣著名的軟件,Bitbucket 源碼沒有開發,推測主要使用 Java 技術棧(這個從一次 Bitbucket VFSForGit 安裝包分析可得)。
Gitee 是目前國內最大的代碼託管平臺之一,早在 2015 年便開始了分佈式改造,並編寫了一系列服務實現分佈式架構,編寫了 Nginx 路由模塊實現動態路由,基於 libssh 開發了 Basalt v1 SSH 服務器,基於 Golang 開發了 Basalt v2 SSH 服務器,還開發了 git-srv 智能服務後端,brzox Git HTTP/Archive 服務。以及 git-diamond git 協議內部傳輸服務等等。Gitee 最初代碼基於 Gitlab,幾年之間已經與 Gitlab 有了很大的差別,如今 Gitee 已經逐步將一些功能從 gitlab 中剝離,實現雲平臺的微服務,好比目前的 git/svn/hook 驗證服務是基於 Golang 編寫的 banjo。Gitee 須要以有限的硬件實現更多的用戶接入,因此在服務的設計上更傾向於提供資源使用率,對一些比較容易形成計算資源緊張的服務進行降級。
<!--SSH/HTTP/GIT, LFS, GitVFS....-->
Git 代碼託管平臺的基本服務應該包括瀏覽器接入支持和 git 客戶端接入支持,前者須要平臺開發網頁提供若干服務供用戶訪問。後者須要支持 git 客戶端推拉代碼。經過網站訪問存儲庫意味着 HTTP 服務須要經過必定的途徑讀寫存儲庫,在 GitWeb 中,這一般使用 git 命令實現,好比使用 git tree
查看 tree
,使用 git archive
打包文件等等。在 Gogs 中,使用的 git-module 一樣使用了命令讀寫存儲庫。而 Gogs 的分叉 Gitea 則使用的是 src-d/go-git 讀寫存儲庫。實際上咱們經常有那種感受,使用命令行可能會比直接調用 API 慢,而且錯誤難以處理,這一般是對的。好比咱們查看 HEAD
對應的引用,使用命令咱們能夠運行 git symbolic-ref HEAD
,運行這個命令咱們須要 fork 出一個進程,fork 成功後立刻在子進程中執行 exec git symbolic-ref,爲了讀取 git symbolic-ref 的輸出,咱們還須要建立幾對 Pipe,並檢測 git symbolic-ref 的退出值。而使用 libgit2 API 咱們只須要調用 git_repository_open
,git_reference_open
,git_reference_symbolic_target
便可拿到對應的引用。而對於服務程序而言,fork-exec 的代價可能不小。固然你也能夠直接使用 open("/path/to/.git/HEAD")
而後解析 HEAD 對應的引用。GitBucket 使用 JGit 讀寫存儲庫,Gitlab 曾經歷了 Grit (Grit 部分命令部分 Git 純 Ruby 實現,Github 曾經使用)。後來的 Rugged,到如今 Gitaly 的純命令 + Ruby Repository(Gitlab 如今的架構我對其保留意見,至少 IO 複製將增長屢次)。Github 目前使用 Rugged 讀寫存儲庫,固然一些更多的細節由於沒有源碼不得而知。Gitee 目前使用 Rugged,但一部分 libgit2 實現不佳的則直接採用 git 命令實現。
實現 Git Over HTTP,Gitlab 最初採用了 Grack, 運行在 unicorn
中的 Grack 併發有限且容易影響 Web 訪問(即 Git 請求較多時,Web 拒絕服務),而基於 Golang 開發的 Gogs,Gitea 使用 Golang 原生 HTTP 庫編寫 Git HTTP Server 功能,這要比 Grack 好要好不少,Golang HTTP 模型可以支撐更多的併發。目前 Gitee 的 Git HTTP Server Brzox 也是使用 Golang 編寫。
實現 Git Over SSH,Gitlab 目前依然使用的是 OpenSSH,而不像 Github/BitBucket/Gitee 直接編寫 SSH 服務器,直接編寫 SSH 服務器能夠禁用 SSH 登陸,自定義錯誤消息,簡化驗證流程,減小數據拷貝。Github 早先是基於 libssh 編寫的 SSH Server, 目前不得而知。BitBucket 技術上偏向 Java, 則有可能使用 Apache Mina SSHD, GitBucket 使用 Apache Mina SSHD + JGit 實現 Git Over SSH 功能。而 Gogs/Gitea 在雖然使用 Golang crypto/ssh 編寫了 SSH 服務,但在實現時仍然使用了中間命令,這就致使數據拷貝次數的增長,觀測 Gogs/Gitea 的各類服務實現,這多是設計不足的妥協吧。
實現 Git Over TCP (git:// 協議)也很是簡單,但 Git 協議並不提供驗證機制,Git 代碼託管平臺提不提供 Git 協議支持也可有可無,但 Git 協議無需加密,協議簡單,做爲平臺內部傳輸服務卻是能夠,目前 Gitee 使用 C++ Asio 編寫 git-diamond 支持內部同步,企業存儲庫備份等功能。
<!--存儲庫分片,分佈式文件系統-->
伸縮性是 Git 代碼託管可否支撐成千上萬用戶/存儲庫的重要指標。像 Gogs/Gitea 這樣的代碼託管系統儘可能認爲自身運行在單一服務器上,所以這類 Git 代碼託管平臺伸縮性很是有限,固然若是使用 NFS/Ceph 這類分佈式文件系統可以在單一服務器上支持更多的存儲庫,但 NFS/Ceph 這種分佈式系統的作爲 Git 代碼託管系統的存儲層,除了分佈式文件系統帶來的性能降低,還會帶來內網帶寬太高等更多的問題。
咱們以使用 NFS 掛載實現伸縮性的平臺和 Gitee 分佈式模型 git 請求 對比,I/O 細節簡化以下:
NFS I/O 細節:
Gitee Basalt I/O 細節:
計算機是質樸的,流程的增長每每須要更多的計算資源,與 Basalt-GitSrv 相比,NFS 的 I/O 拷貝要多一些,排除 Git 協議影響咱們可能會認爲 Basalt 的機制要比 NFS 更節省 I/O。若是考慮到 Git 協議的影響,咱們應該確信如此,git 推送或者拉取都須要耗費大量的 CPU 計算資源,而在 NFS 模型中,計算所有都是發生在前端服務器,當請求數量較多時,前端服務器則容易出現 CPU 競爭的局面,這將很是影響服務器性能,另外,對於 NFS 這樣的文件系統,讀寫 Git 鬆散對象都是不得力的。另外,因爲 NFS 的緩存機制,負載較高時會出現 master.lock
這樣的鎖定狀況,致使用戶使用異常。而對於 Basalt,git 則是在存儲服務器上直接操做存儲庫,打包壓縮,解壓等對 CPU 需求較高的活動也在存儲服務器上,這樣意味着,CPU 計算被攤薄到存儲服務器上了,另外 basalt-gitsrv 中間傳輸的是打包後的數據,這與 NFS 讀寫多個文件相比,網絡數據量其實是降低的。
Gitee 做爲國內最先的 Git 代碼託管平臺之一,最開始使用 NFS 實現伸縮性,隨着用戶規模增加很快出現了上述全部 NFS 容易遇到的問題,後來嘗試切換到 Ceph,git 鬆散對象給其致命一擊,上線便宣告失敗,出現了嚴重的宕機事故,數據被毀,只能從備份恢復。後來遷移到分佈式架構後基本穩定運行至今(這種方案基本上增長機器便可,前端負載高加前端,存儲滿了加存儲)。
Github 目前有大約 1億個項目,咱們假設 Github 上存儲庫大小平均爲 10MB,目前 Github 存儲庫使用三副本機制,大概須要的磁盤容量爲 2861 TB,按照硬盤出廠的規則(1000GB=1TB),則是須要最小 3PB。這麼大的磁盤容量並非一個標準服務器可以提供的,按照目前企業級硬盤容量較大的每一個 16TB, 則須要硬盤大概 188 塊。你能想象到這樣大的規模可以簡單的運行在分佈式文件系統上嗎?目前的技術基本上不太現實。
實現 Git 代碼託管平臺的可伸縮性重要的是實現資源的分片,最開始 Gitee 分佈式時使用的是基於用戶(namespace)的資源分片,也就是存儲庫所在的機器與 namespace 所屬的機器像匹配,這其實是一種先入爲主的設計,在使用 NFS 掛載的時代,Gitee 的存儲庫就是按照 namespace 的前兩個字母分片存儲到不一樣服務器上,掛載到前端服務器上。所以,基於 namespace 的分片帶來了一些問題,好比用戶轉移存儲庫可能須要跨機器,fork 存儲庫也可能須要跨機器,這就沒法實現高效的輕量級 fork 功能。從去年開始遷移到基於存儲庫的分片,基於存儲庫分片基本上能夠解決這些問題,但因爲歷史緣由,輕量級 fork 等功能道阻且長。
資源的分片和請求的路由相伴而生,將存儲庫存儲到不一樣服務器上後,則須要在這些服務器上實現對應的服務支持前端的請求,而前端也須要實現特定的路由機制,關於 Gitee 的路由機制架構,能夠參考相關演講或者博客。Gitee 存儲服務器上使用了 git-srv 做爲 Git 傳輸協議後端服務,而 Github 則使用了 DGit/Spokes,Gitlab 使用了 Gitaly。不一樣平臺的技術各有側重,好比 Gitlab Gitaly 側重兼容舊的 OpenSSH,而 Gitee 的 Basalt-GitSrv 針對實際狀況優化,與 Gitaly 相比要少一次 I/O 拷貝。 Gitee 目前不足之處是存沒有徹底剝離 Web(基於早期 Gitlab 發展而來),而 Gitaly 也有 Ruby 代碼實現存儲庫讀寫(這塊代碼用 Golang 封裝 I/O 多了一次拷貝)。與 Gitee 相似,Gitea 還有另外一種方案,即將 Gitea 部署到多個服務器上共用 DB 支持分片,好比 gitea.com 即是這樣的平臺,但 gitea.com 彷佛並不支持 SSH,所以並不能算有效的分片。
前端服務器的擴展性實際上要比存儲服務器好,前端服務器的遷移通常不須要像存儲服務器那樣轉移存儲庫,服務也通常更簡單。
存儲庫分片以後仍是沒法避免特定存儲庫請求過多的問題,Github 的解決方案是使用三副本讀寫分離的 Spokes 機制,這一方案最多可以提供 3倍於單一服務器的併發讀取能力,但不支持併發寫入存儲庫。三副本機制須要解決分佈式系統常見的一致性問題,引入併發寫入可能會帶來更多的數據衝突,破壞一致性,所以 Github 徹底禁止併發寫入存儲庫副本(即同時有不一樣的寫存儲庫請求)。Gitlab 沒有實現這樣的技術,BitBucket 則沒有披露相關資訊,Gitee 受限與硬件限制和開發資源限制,也沒有實施。
github-dfs:
除了存儲庫的分片,代碼託管平臺還須要考慮數據庫 SQL/NoSQL 可否支撐大規模併發,數據庫的分佈式集羣是一個比較成熟的方案,而 Redis 最新的版本也支持集羣,所以數據庫的伸縮性通常不會存在太大問題,增長機器搭建集羣便可。選擇關係性數據庫時還須要考慮許可證,數據庫自身的功能等,好比 Gitlab 目前已經放棄對 MySQL 的支持,而是選擇了 PostgreSQL,不過 Gitlab 的選擇對於其餘代碼託管平臺來講,也只能算做僅做參考。MariaDB 是 MySQL 的分支版本,隨着 MySQL 被 Oracle 收購,開源社區漸漸喪失了對 MySQL 的興趣,雖然 MySQL 8.0 發佈已經好久,但採用 MySQL 8.0 的發行版本寥寥無幾,不少還停留在 MySQL 5.X,有些發行版還使用 mariadb-connector-c 替代 libmysqlclient
做爲數據庫鏈接器,使用 MySQL 的平臺很容易遷移到 MariaDB 而不用修改客戶端數據庫鏈接代碼 ,MariaDB 支持線程池,而 MySQL 僅在企業版中支持線程池。一些 MariaDB 與 MySQL 的對比這裏不贅述了。Gogs/Gitea 還支持使用 SQLite,但其使用 SQLite 時,基本上是放棄了伸縮性,不過目前有一個使用 Raft+libuv 實現的分佈式 SQLite canonical/dqlite,能夠嘗試一下。Redis 通常能夠做爲 Web 緩存或者任務隊列的中間件,目前 Redis 雖然支持集羣,但就單機 Redis 而言,因爲它是單線程的服務,在將內存數據持久化到磁盤是仍是可能出現超時,而且單線程服務性能終究有限,在 Github 上,KeyDB 是官方 Redis 的另外一個選擇,KeyDB 是 Redis 的分支,徹底兼容 Redis 協議,KeyDB 支持多線程,有更好的內存效率和高吞吐量。
<!--大存儲庫,大文件,保護分支,只讀目錄,安全,兩步驗證/WebAuthn (https://github.com/duo-labs/webauthn)...-->
除了支持用戶經過 Git 協議或者經過網頁方式讀寫遠程存儲庫,代碼託管平臺通常還須要提供一些與開發相關的功能加強用戶體驗,這些功能在不一樣平臺之間的對比時顯得很是重要。
SQLite3 使用 2007 年誕生的版本控制系統 Fossil 託管其源碼,與前輩 Git 相比,它集成了 Bug 追蹤,Wiki,論壇和技術報告。而對於 Git 來講,這些則須要 Git 代碼託管平臺本身實現,固然如今不管是 Github/Gitee/Gitlab/BitBucket 仍是 Gogs/Gitea 都提供了 Issues
這樣的機制方便開發者第一時間報告軟件缺陷或者提出功能建議。Issues
這樣的功能實現主要在於讓用戶參與其中,也就是用的人多了,纔有人氣。而 Github 的 Issues
相比其餘平臺是最活躍的。另外 Github 還提供依賴警報功能(詳情能夠閱讀 Introducing security alerts on GitHub),另外 Github 還收購了 Semmle 代碼分析用於連續漏洞檢測 (參考:Securing software, together),這也是其餘 Git 代碼託管平臺能夠借鑑的功能。
在微軟收購 Github以後,Github 有了更充足的財力在給用戶提供持續集成功能,今年以來 Github 推出了 GitHub Package Registry 和 Github Actions (相關文章:GitHub Actions now supports CI/CD, free for public repositories,Introducing GitHub Package Registry),在推出 Github Actions 以前,開發者在 Github 上大可能是經過第三方軟件實現 CI/CD 功能,好比個人 M2Team/Privexec 就使用 Appveroy。Windows Terminal 則使用 Azure Pipeline。平臺的生態繁榮得益於第三方的支持,而對於其餘平臺,這些 CI/CD 支持就沒有這麼大的力度了,這也促使其餘代碼託管平臺的 API 趨向 Github 化,WebHook 也逐步趨同,Github 造成了事實上的標準。好比 Gitee 的 APIv5 就保持了對 Github 的兼容。
Gitee 很早就實現了相似 SVN 的保護分支功能,而 Github 目前也一樣支持保護分支。實現保護分支的途徑很不少條,一般經過服務端 Git 鉤子實現,我曾寫過 《服務端 Git 鉤子的妙用》 介紹瞭如何經過鉤子實現保護分支功能。
只讀目錄功能一樣能夠經過鉤子實現,若是不經過鉤子,而是在 git 命令中實現,則要面臨修改 git 源碼,須要投入大量人力維護的狀況。《服務端 Git 鉤子的妙用》和 《實現 Git 目錄權限控制》對實現目錄權限控制有詳細介紹。
將使用其餘版本控制系統的存儲庫轉爲 Git 很是簡單,git 自身提供了 git svn
命令,能夠將遠程 svn 存儲庫一個個版本遞歸的轉變爲 Git 存儲庫,詳細的操做能夠參考 《Pro Git 2nd Edition》9.2 Git and Other Systems - Migrating to Git,這種方案的缺點比較是比較耗時,Gitee 開發者曾經幫助國內某汽車製造企業將 Subversion 存儲庫遷移到 Git,一開始使用 git svn
,發現耗費時間太長,因而我找到了一個開源工具: git-svn-fast-import,將其編譯好並修復特定 BUG 交給相關同事,後來該企業的遷移工做順利完成。這個工具直接解析存儲庫將其轉換爲 git 存儲庫,省去了網絡傳輸的消耗。
除了支持從其餘版本控制系統導入外,一些代碼 Git 代碼託管平臺也支持其餘協議接入,Github/Gitee 都支持 Subversion 接入,也就是同一個存儲庫同時支持 git 客戶端和 svn 客戶端接入(像 BitBucket 支持 Mercurial 的實現其實是單獨搭建 Mercurial 存儲庫,不屬於此類狀況)。實現 Subversion 的接入幾個難點,一是 Subversion 各類傳輸協議細節徹底不一樣,HTTP 基於 WebDAV,而 SVN 協議又是一種自定義的 ABNF 格式協議,若是在考慮支持 Subversion 接入時還須要考慮選擇哪一種協議,兩類協議都支持一般是不現實的,費時費力。二是 Subversion 自身也在不斷髮展,但實際上在願意在 Git 代碼託管平臺使用 svn 的畢竟仍是少數,實現 Subversion 接入一般是費力不討好,投入與產出不成正比。
Github 實現的是 svn HTTP 協議,將 git 存儲庫的 commit 映射到 svn 的 revs。Github 的實現並不完美,因爲須要經過 commit 計算 svn 版本信息,第一次經過 svn 協議訪問存儲庫時會比較慢,若是當存儲庫較大時,檢出還很容易失敗,而且一次檢出操做可能須要發送的很是多的請求,大概是全部目錄全部文件數目之和。
Gitee 使用了 git-as-svn 實現對 svn 的支持,支持的協議有 svn://
和 svn+ssh://
,svn+ssh://
其實是 svn://
協議經過 SSH 隧道傳輸,在 Gitee 中,當 Basalt 接收到客戶端請求在遠程服務器上運行 svnserve -t
命令,則會將請求轉發到 git-as-svn。在 Gitea 開發者的貢獻下,git-as-svn 增長了 svnserve
命令包裝,即當 Gitea 接收到 svn+ssh://
協議請求時,則是啓動包裝的命令,進行一些列受權後而後在 shell 中與使用命令 exec 3<>/dev/tcp/localhost/3690
與 git-as-svn 通訊,Gitee 的設計簡化了驗證流程,可以支持分佈式架構,Gitea 目前還不能作到。git-as-svn 的基於 Java 開發,早期,開發者彷佛對 git 的理念研究不夠透徹,git-as-svn 的內部實現細節變更很是大,早前的實現機制不太理想,性能不佳。在 Gitee 中,咱們爲了不存儲庫較大時開啓 svn 支持帶來的性能降低,額外增長了對經過 svn 協議訪問存儲庫的限制,目前是經過 svn 協議訪問存儲庫時,存儲庫的大小限制爲 400MB。
在早期,兼容其餘版本控制系統多是吸引用戶的一大法寶,但隨着 Git 的愈來愈流行,支持其餘版本控制系統接入逐漸成了雞肋,前人有言:「食之無肉,棄之惋惜」,正是如此。像 Github/Gitee 這樣的平臺雖然支持 svn,但 svn 訪問的仍是極少數,而支持 svn 則須要花費一些人力物力,而且在系統架構設計時增長了複雜度。若是如今開發一個 Git 代碼託管平臺則沒有必要支持 svn。Gitee 雖然支持 svn,但 svn 每日的請求數不足 1%,在這 1% 中,又有 50% 以上的請求是特定的用戶使用定時命令發送的。
公共 Git 代碼託管平臺不少時候其實是給用戶提供免費服務,爲了過多避免大文件大存儲庫佔用平臺資源,對其做出限制必不可少,一般是大文件限制 100MB, 存儲庫限制 1GB. 存儲庫的檢測簡單的遍歷存儲庫 objects 目錄便可,而大文件的檢測則複雜一些。Gitee 最初使用 Grit 檢測 commit 是否引入了 blob 原始大小大於限制的文件,但這種機制須要解析 Git 對象,檢測容易坍塌(一是檢測超時,二是檢測逃逸,三是存儲庫體積膨脹),後開使用原生鉤子,改變了檢測機制,則避免了這些問題。詳細狀況能夠閱讀《服務端 Git 鉤子的妙用》。
禁止大文件推送這只是堵,那麼大文件應該如何存放呢?Github 推出了 LFS 方案,目前 LFS 功能已經被大多數平臺支持,Github 將 LFS 存儲到 AWS 上,而 Gitee/Gitlab/Gogs/Gitea 大多使用自建的 LFS 服務器,存儲在特定服務器上。
若是一個存儲庫自身就已經很是大了,如何去解決用戶的訪問難題呢?好比 Windows 源碼超過 300GB
,若是用戶克隆存儲庫,按照每秒 1MB/s 的速度,須要 85 小時,這在任何代碼託管平臺都是不太現實的,好在微軟 2017 年發佈了 GVFS(如今叫 VFSforGit),在使用 VFSforGit 獲取遠程存儲庫時,能夠只得到目錄結構,並在本地建立佔位文件,但用戶操做這些佔位文件時,VFSforGit 客戶端纔會去請求服務器下載對應的對象,這大大改善了巨型存儲庫的操做體驗。VFSforGit 本地涉及到的主要技術是 ProjFS,在 Windows 上,VFSforGit 會建立 IO_REPARSE_TAG_PROJFS
類型的 ReparsePoint(NTFS 重解析點),讀寫到這些重解析點時,ProjFS 驅動會轉發到 VFSForGit 客戶端下載相應的對象。微軟不少開發者在 macOS 上開發,因此官方增長了對 macOS 的支持,而 Github 的 VFSForGit fork 則增長了對 Linux 的支持,不過離實用還有一些時日,Github ProjFS 實現庫是 libprojfs。
Git 代碼託管平臺支持 VFSforGit 客戶端比較容易,目前除了 Visual Studio Online,還有 BitBucket 也增長了對 VFSforGit 的支持。我曾用 libgit2 開發了一個 git-vfs-serve
命令,用戶訪問 brzox 時,brzox 請求 git-srv,git-srv 執行 git-vfs-serve 即可以支持 VFSforGit 客戶端的訪問,不過並未上線。
Github 最近宣佈了支持 WebAuthn: GitHub supports Web Authentication (WebAuthn) for security keys,這種機制可使用生物識別從而避免輸入用戶密碼,隨着信息技術的不斷髮展,一方面,安全機制不斷完善,另外一方面,用戶面臨的風險也會多樣化,複雜化。代碼託管平臺管理了開發者的核心資產,所以在安全上毫不能掉以輕心。固然須要作的不只僅是及時跟進新的安全機制,還須要對整個系統及時進行安全升級,淘汰舊的協議(好比 SSL3/TLS1.1),舊的加密,哈希算法(DSA,MD5/SHA1),及時採用新的協議(TLS1.3),新的加密,哈希算法(ED25519,SHA3)等等。
<!--附件下載,發佈文件,Archive 下載-->
一個優秀的 Git 代碼託管平臺,應該在軟件的開發整個週期都給用戶提供幫助,好比下載源碼,軟件發佈。源碼下載主要指 Archive 功能,軟件的發佈則須要平臺提供 Release/附件下載功能。
咱們知道 git-archive 命令能夠將存儲庫特定的 commit/branch 打包成一個 zip/tar 文件,而在 Git Over SSH(Git Over TCP) 實現中,只要咱們容許 git-upload-archive
命令在遠程服務器上運行,就打包遠程服務器上的存儲庫的特定分支。但因爲 git-upload-archive 與 git-upload-pack/git-receive-pack 存在一些不一樣,是的 HTTP 協議沒法實現 archive 協商。提供 archive 下載則須要另闢蹊徑。
咱們在遠程服務器上運行 git-archive 將其輸出做爲響應體的內容返回給 HTTP Client 即可實現 archive 下載功能,因爲 archive 下載其實是將 git tree/blob 遍歷而後寫入到歸檔文件後壓縮(tar.gz/tar.bz2 ...)或者是壓縮後寫入文件(zip),兩者都很是消耗 CPU 資源,所以咱們在實現 archive 下載功能的同時應該設計 archive 的緩存功能(固然緩存應該支持過時)。gitlab-workhorse 實現的 archive 下載功能即是先嚐試命中緩存,若是沒有緩存則調用 git 命令而後生成寫入到緩存文件。Gitee 最近實現的 blaze-archive 也採用了相似的機制,但 blaze-archive 是一個獨立的命令,這個命令其實是被 git-srv 調用,brzox 與 git-srv 通訊,brzox 將 archive 返回給 HTTP Client,而緩存的刪除則是 blaze 負責的。
附件,Release 能夠選擇雲方案,若是要將附件和 LFS 統一管理,實際上國內的阿里雲,騰訊雲之類的並不合適,這些平臺對並不支持相似 AWS x-amz-content-sha256
這樣的頭部,而是 Content-MD5
所以這些雲平臺要支持 LFS 則要花費多一些功夫。選擇國外的 AWS, Azure 則須要考慮經濟,網絡等問題。固然不管如何使用雲平臺都須要考慮經濟問題。
平臺自建附件,Release 功能可使用分佈式文件系統,如 FastDFS, 但 FastFDS 並非一個好的選擇,歷史比較久,存儲機制安全機制如今來講都不是很優秀。有個更好的選擇是 Minio, minio 使用 Golang 開發,支持 AWS API。許可協議是 Apache 2.0
,商用沒有阻礙,所以是用來搭建附件,Release 以及 LFS 存儲服務器的不二選擇。
Git 雖然是當前最受歡迎的代碼託管系統,但 Git 也面臨了一些難題,一類是如何支持大文件大存儲庫,這些問題有 Git LFS, VFSforGit 這樣的第三方解決方案,也有微軟,Google 開發者參與的官方 Partial Clone,部分克隆須要 Wire 協議支持,離可用還爲時尚早。
2017年2月,Google 開發者宣佈攻破 SHA1,這曾經給一些 git 用戶帶來了擔心,由於 git 使用 SHA1 計算對象 ID,但 git 使用的其實是一種特殊的 SHA1,將對象類型對象長度以及對象內容合併在一塊兒計算 SHA1,因爲有長度校驗,這使得 SHA1 的衝突可能被下降了,但不管如何,SHA1 也再也不是安全的,Git 在源碼中增長了 sha1collisiondetection 來避免 SHA1 衝突,而且增長了計劃遷移到 SHA-256,而且將一些涉及到 Hash 的代碼從單一的 SHA1 轉變成 object_id
。 關於 Hash 轉換,能夠查看文檔 Git hash function transition。
Git 從 SHA1 遷移到 SHA-256 困難重重,從首次增長文檔距今已經有兩年時間,而 SHA-256 的實現還不見全貌。與 Hash 遷移相比,壓縮算法的演進不重要更難實施,時至今日,zlib 壓縮已經再也不優秀,但 Git 可能還要負重前行。
軟件開發一直是一個飛速變化的領域,而代碼託管也要不斷面臨新的挑戰,道路漫漫,吾輩不休。
版本控制軟件種類繁多,維基百科收錄的最先的版本控制系統是 1972 年貝爾實驗室開發的 Source Code Control System。1986 年 Concurrent Versions System(CVS) 誕生,CVS 曾很是流行,但今時用之寥寥無幾,不過 OpenBSD 仍在使用 CVS。2000 年 CollabNet 建立了 Subversion 項目,2009年,Subversion 被 Apache 基金會接受成爲頂級項目並被命名爲 Apache Subversion。2005 年 Linus Torvalds 建立了 Git,2007 Github 誕生後,Git 隨着 Github 的發展愈發流行,14 年間,Git 成爲了最流行的版本控制系統,不管是 Windows 仍是 Linux 或是 Android,MySQL 等等大型軟件都使用 git 進行版本控制。縱觀版本控制系統流行史,前有 CVS 後有 SVN,今日 Git 更風流。俱往矣,數風流人物,還看今朝,版本控制系統莫不如斯。
與 CVS/Subversion 這種集中式版本控制系統不一樣的是,Git 的存儲庫數據會被存儲在本地,提交也是發生在本地,遠程能夠看做是本地存儲庫的一個鏡像。而 CVS/Subversion 的提交都是在線的。這就是分佈式版本控制系統的核心特徵。(理解這一問題的關聯在於區分工做樹 worktree
和存儲庫 repository
。)
Git 的源碼託管在 git.kernel.org 上,Github 上也有隻讀鏡像 github.com/git/git。Git 主頁 https://git-scm.com 的網頁源碼則託管在 Github 上。一般給 git 提交 PR 須要註冊 public-inbox.org 郵件列表,而後發送補丁。者一般比較麻煩,好在有微軟開發者Johannes Schindelin 使用 TypeScript 開發 gitgitgadget ,當你在 Github 上像 gitgitgadget/git 提交 PR 時,gitgitgadget 會將你的 PR 發送到 public-inbox,一旦補丁被 git 維護者接受,gitgitgadget 則會關閉那個 PR。gitgitgadget 簡化了給 git 貢獻代碼的難度,省去了註冊 Inbox 的麻煩,這年頭開發者大多都有 Github 賬號。我就使用 gitgitgadget 給 git 提交了一個補丁用於支持 HTTP/2。
Johannes Schindelin 此人也是 git-for-windows 的維護者。 Git 的維護者則是 Google 的開發者 Junio C Hamano。大多數 Git 開發者來自於 Google/Microsoft(包括 Github)。libgit2 的開發者主要來自 Microsoft(包括 Github)。而 JGit 的開發者則主要來自 Google。已故 JGit 的創始人 Shawn Pearce 還開發了著名的 Gerrit Code Review。這些開發者的無私奉獻才能使咱們用上這麼優秀的版本控制系統,感謝他們的付出。
Git 與遠程存儲庫之間的傳輸協議有 HTTP, GIT(git://
),SSH. 在 《Pro Git - 2nd Edition》4.1 Git on the Server - The Protocols 中有介紹。其中 HTTP 協議包括啞協議和智能協議,因爲啞協議是隻讀協議,目前大多數代碼託管平臺均再也不提供支持。HTTP 智能協議和 GIT 協議,SSH 協議相似,都是特定幾組 客戶端/服務端 git 命令之間的輸入輸出數據傳輸和交換。Git 傳輸協議較爲簡單,以智能傳輸協議 v1 爲例,基本的 fetch/push
流程以下:
Git 拉取流程:
Git 推送流程:
雖然在 2018 年 5 月,git 推出了 Wire Protocol
(即 Git v2 協議),增長了 Git 協議的複雜性,但在服務器上支持 git 協議(包括 v2 協議)仍然只須要在服務器上運行 git-upload-pack/git-receive-pack。這使得開發者很容易實現對 git 協議的支持。正由於 Git 協議表徵的簡單,因此針對不一樣的用戶和存儲庫數量規模,Git 也都比 Subversion,Mercurial 有更多的選擇。
Git 使用文件快照記錄文件變動,當對象存儲到鬆散文件目錄時,每一次大小不變的文件修改至關於存儲庫中增長特定文件的大小,Git 使用 zlib deflate 壓縮對象,對象頭包括對象類型,原始大小。基於快照的方式使得 Git 在提交代碼,檢出文件時都比較高效,但存儲庫的佔用缺比較高。但運行 git gc
時,Git 會將鬆散的對象打包到 pack 文件中,這個時候會使用特定的機制存儲一部分文件的 OFS_DELTA
,這樣就能節省一部分空間。
zlib(deflate) 壓縮算法一般來講除了沒有版權限制,不管是壓縮比仍是速度,CPU 使用率都不是一個最佳的選擇,引用來自的 https://github.com/facebook/zstd 基準測試,zlib 看起來必後起之秀 brotli
/zstd
差多了:
Compressor name | Ratio | Compression | Decompress. |
---|---|---|---|
zstd 1.4.0 -1 | 2.884 | 530 MB/s | 1360 MB/s |
zlib 1.2.11 -1 | 2.743 | 110 MB/s | 440 MB/s |
brotli 1.0.7 -0 | 2.701 | 430 MB/s | 470 MB/s |
quicklz 1.5.0 -1 | 2.238 | 600 MB/s | 800 MB/s |
lzo1x 2.09 -1 | 2.106 | 680 MB/s | 950 MB/s |
lz4 1.8.3 | 2.101 | 800 MB/s | 4220 MB/s |
snappy 1.1.4 | 2.073 | 580 MB/s | 2020 MB/s |
lzf 3.6 -1 | 2.077 | 440 MB/s | 930 MB/s |
當開發者要將 git 集成到其餘軟件或者系統中時,能夠經過命令行調用 git 命令捕獲輸出,也可使用 libgit2/JGit 等庫。
libgit2 最初是由 Shawn Pearce 建立了初始 commit。目前主要維護者來自微軟。libgit2 提供一些基礎的 API,功能基本上是完整的,除了一部分實現性能沒有 git 那麼好,其餘方面使人滿意,而且有多種語言綁定,包括 C++/D/Golang/Ruby/.NET/Node.js/Perl/Perl6/Ruby/Rust 等等。Gitee 原生鉤子就使用了 libgit2,Gitee-gitlab 項目使用了 rugged。
JGit 也是有 Shawn Pearce 建立的,目前屬於 Eclipse 基金會,運行在 JVM 上,國內騰訊的工峯的 TGit 也是使用的 JGit。
在 Git Rev News 第48期,編輯推薦了 gitbase 經過 SQL 的方式查詢 git 存儲庫,這個工具基於 src-d/go-git,go-git 是純 Golang 實現的,若是基於 Golang 的項目須要簡單的讀寫存儲庫,可使用 go-git。與 libgit2 的 Golang 綁定 git2go 相比,不須要使用 CGO。
固然還有一些其餘的 git 實現,大可能是實驗性的,不建議用於生產環境,好比基於 Rust 的 git-rs。
Git 最初由 Linus Torvalds 開發用來取代 BitKeeper 做爲 Linux 內核源碼的版本控制工具,因此 Git 一直和 Linux 內核源碼託管在同一個服務器上。官方地址是:https://git.kernel.org/。在 git.kernel.org 上,Git 代碼託管功能是由 git 內置的工具實現的。用戶使用 HTTPS 協議訪問 https://git.kernel.org/ 時,Nginx 會以 CGI 的方式將瀏覽器的請求轉發到 GitWeb。GitWeb 是一個使用 Perl 編寫的 CGI 程序,爲用戶提供簡單的 git 在線交互圖形界面。GitWeb 的源碼地址能夠在 Github Git 鏡像 中查看。GitWeb 界面比較不夠精美,相比於 Github 這樣的代碼託管平臺,功能寥寥無幾。當用戶須要使用 HTTP/HTTPS 協議拉取推送源碼時,Nginx 會以 CGI的方式將請求轉發給 git-http-backend 處理。git-http-backend 是 Git Over HTTP 的服務端實現。當用戶 GIT 協議 (git://
) 在 git.kernel.org 上拉取源碼是,請求會被 git-daemon 處理。git-daemon 默默的監聽 9418 端口,靜靜的等待 git 客戶端的訪問。
使用 Git 內置的 GitWeb/git-http-backend/git-daemon,咱們可以搭建一個簡易的 Git 代碼託管服務器,但這裏沒有 SSH 協議支持。而實現 SSH 協議支持也很是簡單,只須要在服務器上運行 sshd
(OpenSSH),並容許命令 git-upload-pack/git-receive-pack/git-upload-archive
命令的運行,對於 SSH 協議的驗證,咱們則可使用 authorized_keys
機制,將須要容許的用戶的 SSH 公鑰添加到 authorized_keys
文件。
這種方案一般使用 Gitolite 加強訪問控制,Gitolite 主要使用 Perl 編寫,這和 GitWeb 一致,ssh 的驗證是將 gitolite-shell 添加到 ~/.ssh/authorized_keys
中被 sshd 調用實現的。git.kernenl.org 正是使用 Gitolite 實現 Git Over SSH 訪問控制。
https://git.kernel.org/ 網站託管了 Linux 內核源碼,驅動,文檔等大概有 1000 多個存儲庫,較大的存儲庫好比 Linux 內核源碼磁盤佔用大概是 2GB,所以在理想狀況下,一塊 2TB 磁盤的服務器即可支撐 https://git.kernel.org/ 這個網站的運行(實際狀況則並非如此,因爲 Linux 內核的流行,git.kernel.org 的請求將比較多,對硬件的需求將更高一點)。基於 Git 內置功能搭建的代碼託管服務,麻雀雖小五臟俱全,不過回過頭來講,這樣的代碼託管服務功能有限,可伸縮性和擴展性不佳。
當用戶須要搭建一個幾人到幾十幾百人規模的 Git 代碼託管服務,一般有很是多的選擇,下面是幾個目前仍然比較活躍的小型 Git 代碼託管平臺。
名稱 | 平臺 | 語言 | 技術概述 |
---|---|---|---|
Bonobo Git Server | Windows Only | C# | 基於 .Net Famework 4.6(遷移到 .Net Core 的建議在 2017 年便被提出,但截至目前仍爲遷移到 .Net Core)。使用 LibGit2Sharp 操做存儲庫,但版本較老,不支持 SSH 協議訪問。 |
Gogs | Cross Platform | Golang | 基於 Golang 編寫,Web 讀寫 Git 存儲庫由 git-module 封裝 Git 命令實現,SSH 由 Golang crypto/ssh 提供,支持多種數據庫,是一個極簡的代碼託管平臺,能夠在 Raspberry Pi 上運行 |
Gitea | Cross Platform | Golang | 是 Gogs 的開源分叉,Web 讀寫 Git 存儲庫使用了 src-d/go-git,使用 gliderlabs/ssh 提供 SSH 接入功能,支持多種數據庫,能夠在 Raspberry Pi 上運行。 |
GitBucket | Cross Platform | Scala/Java | 使用 Apache Mina SSHD 實現 SSH 功能。Mina SSHD 還專門針對 JGit 實現了一個 sshd-git 模塊,但 GitBucket 是直接使用 JGit 的 transport 相關類。Eclipse JGit 主要由 Google 開發者參與貢獻。 |
除了上述定位爲代碼託管平臺的服務,還有像 Phabricator 這樣的 Web 軟件也提供 Git 代碼託管功能,但 Phabricator 的重點更可能是缺陷追蹤,代碼審覈。LLVM https://reviews.llvm.org/ 和 libssh https://bugs.libssh.org/ 就是基於 Phabricator。
隨着用戶規模和存儲庫規模的增加,達到必定級別後,上述代碼託管平臺每每變得力不從心,而下面的代碼託管平臺卻深耕於此,可以支撐巨大規模的用戶量和存儲庫數量。
Github 是全球最大的代碼託管平臺,目前 Github 官方數據顯示註冊用戶數量爲 4000萬,項目數量爲 1億。Github 網站主要的技術是 Ruby on Rails 內部進程名爲 github-unicorn
,最近他們將其升級到了 Rails 6.0。Github 使用 Spokes 負責文件系統上存儲庫的複製,同步和備份。Github 以前使用 libssh 開發 Git SSH 服務器,目前的 SSH 服務器的標識爲 babeld-*
,但不肯定 babeld 是否依然基於 libssh。Git 驗證服務爲 github-gitauth
。Github 的大多數服務都是閉源的,所以分析 Github 的技術內幕一般是 Github 官方的一些技術博客, 固然也能夠分析 Github Enterprise
去窺測 Github 內幕。
關於 Github Spokes 的大體原理能夠閱讀 Introducing DGit 和 Building resilience in Spokes。
在開發 Gitaly 以後, Gitlab 擺脫了 NFS 的禁錮,在平臺的伸縮性方面獲得了巨大的提高。要知道 Gitlab 使用 Gitaly 的緣由能夠閱讀 The road to Gitaly v1.0。Gitaly 使用 RPC 將存儲服務器上的 git 命令包轉成前端服務機器上的 git 命令,併爲 gitlab 服務提供存儲庫的讀寫。 Gitlab 的 SSH 功能仍然由 OpenSSH 提供,而一些靜態資源,文件下載,附件等功能則由 Golang 編寫的 gitlab-workhorse 實現,gitlab-workhorse 須要與 Gitaly 通訊。
Bitbucket 是 Atlassian 開發的代碼託管平臺,與 Github/Gitlab 不一樣,Bitbucket 還提供了原生 Mercurial 支持,不過最近,Bitbucket 宣佈要逐步關閉 Mercurial 的支持。Atlassian 還開發了 Jira/Sourcetree 這樣著名的軟件,Bitbucket 源碼沒有開發,推測主要使用 Java 技術棧(這個從一次 Bitbucket VFSForGit 安裝包分析可得)。
Gitee 是目前國內最大的代碼託管平臺之一,早在 2015 年便開始了分佈式改造,並編寫了一系列服務實現分佈式架構,編寫了 Nginx 路由模塊實現動態路由,基於 libssh 開發了 Basalt v1 SSH 服務器,基於 Golang 開發了 Basalt v2 SSH 服務器,還開發了 git-srv 智能服務後端,brzox Git HTTP/Archive 服務。以及 git-diamond git 協議內部傳輸服務等等。Gitee 最初代碼基於 Gitlab,幾年之間已經與 Gitlab 有了很大的差別,如今 Gitee 已經逐步將一些功能從 gitlab 中剝離,實現雲平臺的微服務,好比目前的 git/svn/hook 驗證服務是基於 Golang 編寫的 banjo。Gitee 須要以有限的硬件實現更多的用戶接入,因此在服務的設計上更傾向於提供資源使用率,對一些比較容易形成計算資源緊張的服務進行降級。
<!--SSH/HTTP/GIT, LFS, GitVFS....-->
Git 代碼託管平臺的基本服務應該包括瀏覽器接入支持和 git 客戶端接入支持,前者須要平臺開發網頁提供若干服務供用戶訪問。後者須要支持 git 客戶端推拉代碼。經過網站訪問存儲庫意味着 HTTP 服務須要經過必定的途徑讀寫存儲庫,在 GitWeb 中,這一般使用 git 命令實現,好比使用 git tree
查看 tree
,使用 git archive
打包文件等等。在 Gogs 中,使用的 git-module 一樣使用了命令讀寫存儲庫。而 Gogs 的分叉 Gitea 則使用的是 src-d/go-git 讀寫存儲庫。實際上咱們經常有那種感受,使用命令行可能會比直接調用 API 慢,而且錯誤難以處理,這一般是對的。好比咱們查看 HEAD
對應的引用,使用命令咱們能夠運行 git symbolic-ref HEAD
,運行這個命令咱們須要 fork 出一個進程,fork 成功後立刻在子進程中執行 exec git symbolic-ref,爲了讀取 git symbolic-ref 的輸出,咱們還須要建立幾對 Pipe,並檢測 git symbolic-ref 的退出值。而使用 libgit2 API 咱們只須要調用 git_repository_open
,git_reference_open
,git_reference_symbolic_target
便可拿到對應的引用。而對於服務程序而言,fork-exec 的代價可能不小。固然你也能夠直接使用 open("/path/to/.git/HEAD")
而後解析 HEAD 對應的引用。GitBucket 使用 JGit 讀寫存儲庫,Gitlab 曾經歷了 Grit (Grit 部分命令部分 Git 純 Ruby 實現,Github 曾經使用)。後來的 Rugged,到如今 Gitaly 的純命令 + Ruby Repository(Gitlab 如今的架構我對其保留意見,至少 IO 複製將增長屢次)。Github 目前使用 Rugged 讀寫存儲庫,固然一些更多的細節由於沒有源碼不得而知。Gitee 目前使用 Rugged,但一部分 libgit2 實現不佳的則直接採用 git 命令實現。
實現 Git Over HTTP,Gitlab 最初採用了 Grack, 運行在 unicorn
中的 Grack 併發有限且容易影響 Web 訪問(即 Git 請求較多時,Web 拒絕服務),而基於 Golang 開發的 Gogs,Gitea 使用 Golang 原生 HTTP 庫編寫 Git HTTP Server 功能,這要比 Grack 好要好不少,Golang HTTP 模型可以支撐更多的併發。目前 Gitee 的 Git HTTP Server Brzox 也是使用 Golang 編寫。
實現 Git Over SSH,Gitlab 目前依然使用的是 OpenSSH,而不像 Github/BitBucket/Gitee 直接編寫 SSH 服務器,直接編寫 SSH 服務器能夠禁用 SSH 登陸,自定義錯誤消息,簡化驗證流程,減小數據拷貝。Github 早先是基於 libssh 編寫的 SSH Server, 目前不得而知。BitBucket 技術上偏向 Java, 則有可能使用 Apache Mina SSHD, GitBucket 使用 Apache Mina SSHD + JGit 實現 Git Over SSH 功能。而 Gogs/Gitea 在雖然使用 Golang crypto/ssh 編寫了 SSH 服務,但在實現時仍然使用了中間命令,這就致使數據拷貝次數的增長,觀測 Gogs/Gitea 的各類服務實現,這多是設計不足的妥協吧。
實現 Git Over TCP (git:// 協議)也很是簡單,但 Git 協議並不提供驗證機制,Git 代碼託管平臺提不提供 Git 協議支持也可有可無,但 Git 協議無需加密,協議簡單,做爲平臺內部傳輸服務卻是能夠,目前 Gitee 使用 C++ Asio 編寫 git-diamond 支持內部同步,企業存儲庫備份等功能。
<!--存儲庫分片,分佈式文件系統-->
伸縮性是 Git 代碼託管可否支撐成千上萬用戶/存儲庫的重要指標。像 Gogs/Gitea 這樣的代碼託管系統儘可能認爲自身運行在單一服務器上,所以這類 Git 代碼託管平臺伸縮性很是有限,固然若是使用 NFS/Ceph 這類分佈式文件系統可以在單一服務器上支持更多的存儲庫,但 NFS/Ceph 這種分佈式系統的作爲 Git 代碼託管系統的存儲層,除了分佈式文件系統帶來的性能降低,還會帶來內網帶寬太高等更多的問題。
咱們以使用 NFS 掛載實現伸縮性的平臺和 Gitee 分佈式模型 git 請求 對比,I/O 細節簡化以下:
NFS I/O 細節:
Gitee Basalt I/O 細節:
計算機是質樸的,流程的增長每每須要更多的計算資源,與 Basalt-GitSrv 相比,NFS 的 I/O 拷貝要多一些,排除 Git 協議影響咱們可能會認爲 Basalt 的機制要比 NFS 更節省 I/O。若是考慮到 Git 協議的影響,咱們應該確信如此,git 推送或者拉取都須要耗費大量的 CPU 計算資源,而在 NFS 模型中,計算所有都是發生在前端服務器,當請求數量較多時,前端服務器則容易出現 CPU 競爭的局面,這將很是影響服務器性能,另外,對於 NFS 這樣的文件系統,讀寫 Git 鬆散對象都是不得力的。另外,因爲 NFS 的緩存機制,負載較高時會出現 master.lock
這樣的鎖定狀況,致使用戶使用異常。而對於 Basalt,git 則是在存儲服務器上直接操做存儲庫,打包壓縮,解壓等對 CPU 需求較高的活動也在存儲服務器上,這樣意味着,CPU 計算被攤薄到存儲服務器上了,另外 basalt-gitsrv 中間傳輸的是打包後的數據,這與 NFS 讀寫多個文件相比,網絡數據量其實是降低的。
Gitee 做爲國內最先的 Git 代碼託管平臺之一,最開始使用 NFS 實現伸縮性,隨着用戶規模增加很快出現了上述全部 NFS 容易遇到的問題,後來嘗試切換到 Ceph,git 鬆散對象給其致命一擊,上線便宣告失敗,出現了嚴重的宕機事故,數據被毀,只能從備份恢復。後來遷移到分佈式架構後基本穩定運行至今(這種方案基本上增長機器便可,前端負載高加前端,存儲滿了加存儲)。
Github 目前有大約 1億個項目,咱們假設 Github 上存儲庫大小平均爲 10MB,目前 Github 存儲庫使用三副本機制,大概須要的磁盤容量爲 2861 TB,按照硬盤出廠的規則(1000GB=1TB),則是須要最小 3PB。這麼大的磁盤容量並非一個標準服務器可以提供的,按照目前企業級硬盤容量較大的每一個 16TB, 則須要硬盤大概 188 塊。你能想象到這樣大的規模可以簡單的運行在分佈式文件系統上嗎?目前的技術基本上不太現實。
實現 Git 代碼託管平臺的可伸縮性重要的是實現資源的分片,最開始 Gitee 分佈式時使用的是基於用戶(namespace)的資源分片,也就是存儲庫所在的機器與 namespace 所屬的機器像匹配,這其實是一種先入爲主的設計,在使用 NFS 掛載的時代,Gitee 的存儲庫就是按照 namespace 的前兩個字母分片存儲到不一樣服務器上,掛載到前端服務器上。所以,基於 namespace 的分片帶來了一些問題,好比用戶轉移存儲庫可能須要跨機器,fork 存儲庫也可能須要跨機器,這就沒法實現高效的輕量級 fork 功能。從去年開始遷移到基於存儲庫的分片,基於存儲庫分片基本上能夠解決這些問題,但因爲歷史緣由,輕量級 fork 等功能道阻且長。
資源的分片和請求的路由相伴而生,將存儲庫存儲到不一樣服務器上後,則須要在這些服務器上實現對應的服務支持前端的請求,而前端也須要實現特定的路由機制,關於 Gitee 的路由機制架構,能夠參考相關演講或者博客。Gitee 存儲服務器上使用了 git-srv 做爲 Git 傳輸協議後端服務,而 Github 則使用了 DGit/Spokes,Gitlab 使用了 Gitaly。不一樣平臺的技術各有側重,好比 Gitlab Gitaly 側重兼容舊的 OpenSSH,而 Gitee 的 Basalt-GitSrv 針對實際狀況優化,與 Gitaly 相比要少一次 I/O 拷貝。 Gitee 目前不足之處是存沒有徹底剝離 Web(基於早期 Gitlab 發展而來),而 Gitaly 也有 Ruby 代碼實現存儲庫讀寫(這塊代碼用 Golang 封裝 I/O 多了一次拷貝)。與 Gitee 相似,Gitea 還有另外一種方案,即將 Gitea 部署到多個服務器上共用 DB 支持分片,好比 gitea.com 即是這樣的平臺,但 gitea.com 彷佛並不支持 SSH,所以並不能算有效的分片。
前端服務器的擴展性實際上要比存儲服務器好,前端服務器的遷移通常不須要像存儲服務器那樣轉移存儲庫,服務也通常更簡單。
存儲庫分片以後仍是沒法避免特定存儲庫請求過多的問題,Github 的解決方案是使用三副本讀寫分離的 Spokes 機制,這一方案最多可以提供 3倍於單一服務器的併發讀取能力,但不支持併發寫入存儲庫。三副本機制須要解決分佈式系統常見的一致性問題,引入併發寫入可能會帶來更多的數據衝突,破壞一致性,所以 Github 徹底禁止併發寫入存儲庫副本(即同時有不一樣的寫存儲庫請求)。Gitlab 沒有實現這樣的技術,BitBucket 則沒有披露相關資訊,Gitee 受限與硬件限制和開發資源限制,也沒有實施。
github-dfs:
除了存儲庫的分片,代碼託管平臺還須要考慮數據庫 SQL/NoSQL 可否支撐大規模併發,數據庫的分佈式集羣是一個比較成熟的方案,而 Redis 最新的版本也支持集羣,所以數據庫的伸縮性通常不會存在太大問題,增長機器搭建集羣便可。選擇關係性數據庫時還須要考慮許可證,數據庫自身的功能等,好比 Gitlab 目前已經放棄對 MySQL 的支持,而是選擇了 PostgreSQL,不過 Gitlab 的選擇對於其餘代碼託管平臺來講,也只能算做僅做參考。MariaDB 是 MySQL 的分支版本,隨着 MySQL 被 Oracle 收購,開源社區漸漸喪失了對 MySQL 的興趣,雖然 MySQL 8.0 發佈已經好久,但採用 MySQL 8.0 的發行版本寥寥無幾,不少還停留在 MySQL 5.X,有些發行版還使用 mariadb-connector-c 替代 libmysqlclient
做爲數據庫鏈接器,使用 MySQL 的平臺很容易遷移到 MariaDB 而不用修改客戶端數據庫鏈接代碼 ,MariaDB 支持線程池,而 MySQL 僅在企業版中支持線程池。一些 MariaDB 與 MySQL 的對比這裏不贅述了。Gogs/Gitea 還支持使用 SQLite,但其使用 SQLite 時,基本上是放棄了伸縮性,不過目前有一個使用 Raft+libuv 實現的分佈式 SQLite canonical/dqlite,能夠嘗試一下。Redis 通常能夠做爲 Web 緩存或者任務隊列的中間件,目前 Redis 雖然支持集羣,但就單機 Redis 而言,因爲它是單線程的服務,在將內存數據持久化到磁盤是仍是可能出現超時,而且單線程服務性能終究有限,在 Github 上,KeyDB 是官方 Redis 的另外一個選擇,KeyDB 是 Redis 的分支,徹底兼容 Redis 協議,KeyDB 支持多線程,有更好的內存效率和高吞吐量。
<!--大存儲庫,大文件,保護分支,只讀目錄,安全,兩步驗證/WebAuthn (https://github.com/duo-labs/webauthn)...-->
除了支持用戶經過 Git 協議或者經過網頁方式讀寫遠程存儲庫,代碼託管平臺通常還須要提供一些與開發相關的功能加強用戶體驗,這些功能在不一樣平臺之間的對比時顯得很是重要。
SQLite3 使用 2007 年誕生的版本控制系統 Fossil 託管其源碼,與前輩 Git 相比,它集成了 Bug 追蹤,Wiki,論壇和技術報告。而對於 Git 來講,這些則須要 Git 代碼託管平臺本身實現,固然如今不管是 Github/Gitee/Gitlab/BitBucket 仍是 Gogs/Gitea 都提供了 Issues
這樣的機制方便開發者第一時間報告軟件缺陷或者提出功能建議。Issues
這樣的功能實現主要在於讓用戶參與其中,也就是用的人多了,纔有人氣。而 Github 的 Issues
相比其餘平臺是最活躍的。另外 Github 還提供依賴警報功能(詳情能夠閱讀 Introducing security alerts on GitHub),另外 Github 還收購了 Semmle 代碼分析用於連續漏洞檢測 (參考:Securing software, together),這也是其餘 Git 代碼託管平臺能夠借鑑的功能。
在微軟收購 Github以後,Github 有了更充足的財力在給用戶提供持續集成功能,今年以來 Github 推出了 GitHub Package Registry 和 Github Actions (相關文章:GitHub Actions now supports CI/CD, free for public repositories,Introducing GitHub Package Registry),在推出 Github Actions 以前,開發者在 Github 上大可能是經過第三方軟件實現 CI/CD 功能,好比個人 M2Team/Privexec 就使用 Appveroy。Windows Terminal 則使用 Azure Pipeline。平臺的生態繁榮得益於第三方的支持,而對於其餘平臺,這些 CI/CD 支持就沒有這麼大的力度了,這也促使其餘代碼託管平臺的 API 趨向 Github 化,WebHook 也逐步趨同,Github 造成了事實上的標準。好比 Gitee 的 APIv5 就保持了對 Github 的兼容。
Gitee 很早就實現了相似 SVN 的保護分支功能,而 Github 目前也一樣支持保護分支。實現保護分支的途徑很不少條,一般經過服務端 Git 鉤子實現,我曾寫過 《服務端 Git 鉤子的妙用》 介紹瞭如何經過鉤子實現保護分支功能。
只讀目錄功能一樣能夠經過鉤子實現,若是不經過鉤子,而是在 git 命令中實現,則要面臨修改 git 源碼,須要投入大量人力維護的狀況。《服務端 Git 鉤子的妙用》和 《實現 Git 目錄權限控制》對實現目錄權限控制有詳細介紹。
將使用其餘版本控制系統的存儲庫轉爲 Git 很是簡單,git 自身提供了 git svn
命令,能夠將遠程 svn 存儲庫一個個版本遞歸的轉變爲 Git 存儲庫,詳細的操做能夠參考 《Pro Git 2nd Edition》9.2 Git and Other Systems - Migrating to Git,這種方案的缺點比較是比較耗時,Gitee 開發者曾經幫助國內某汽車製造企業將 Subversion 存儲庫遷移到 Git,一開始使用 git svn
,發現耗費時間太長,因而我找到了一個開源工具: git-svn-fast-import,將其編譯好並修復特定 BUG 交給相關同事,後來該企業的遷移工做順利完成。這個工具直接解析存儲庫將其轉換爲 git 存儲庫,省去了網絡傳輸的消耗。
除了支持從其餘版本控制系統導入外,一些代碼 Git 代碼託管平臺也支持其餘協議接入,Github/Gitee 都支持 Subversion 接入,也就是同一個存儲庫同時支持 git 客戶端和 svn 客戶端接入(像 BitBucket 支持 Mercurial 的實現其實是單獨搭建 Mercurial 存儲庫,不屬於此類狀況)。實現 Subversion 的接入幾個難點,一是 Subversion 各類傳輸協議細節徹底不一樣,HTTP 基於 WebDAV,而 SVN 協議又是一種自定義的 ABNF 格式協議,若是在考慮支持 Subversion 接入時還須要考慮選擇哪一種協議,兩類協議都支持一般是不現實的,費時費力。二是 Subversion 自身也在不斷髮展,但實際上在願意在 Git 代碼託管平臺使用 svn 的畢竟仍是少數,實現 Subversion 接入一般是費力不討好,投入與產出不成正比。
Github 實現的是 svn HTTP 協議,將 git 存儲庫的 commit 映射到 svn 的 revs。Github 的實現並不完美,因爲須要經過 commit 計算 svn 版本信息,第一次經過 svn 協議訪問存儲庫時會比較慢,若是當存儲庫較大時,檢出還很容易失敗,而且一次檢出操做可能須要發送的很是多的請求,大概是全部目錄全部文件數目之和。
Gitee 使用了 git-as-svn 實現對 svn 的支持,支持的協議有 svn://
和 svn+ssh://
,svn+ssh://
其實是 svn://
協議經過 SSH 隧道傳輸,在 Gitee 中,當 Basalt 接收到客戶端請求在遠程服務器上運行 svnserve -t
命令,則會將請求轉發到 git-as-svn。在 Gitea 開發者的貢獻下,git-as-svn 增長了 svnserve
命令包裝,即當 Gitea 接收到 svn+ssh://
協議請求時,則是啓動包裝的命令,進行一些列受權後而後在 shell 中與使用命令 exec 3<>/dev/tcp/localhost/3690
與 git-as-svn 通訊,Gitee 的設計簡化了驗證流程,可以支持分佈式架構,Gitea 目前還不能作到。git-as-svn 的基於 Java 開發,早期,開發者彷佛對 git 的理念研究不夠透徹,git-as-svn 的內部實現細節變更很是大,早前的實現機制不太理想,性能不佳。在 Gitee 中,咱們爲了不存儲庫較大時開啓 svn 支持帶來的性能降低,額外增長了對經過 svn 協議訪問存儲庫的限制,目前是經過 svn 協議訪問存儲庫時,存儲庫的大小限制爲 400MB。
在早期,兼容其餘版本控制系統多是吸引用戶的一大法寶,但隨着 Git 的愈來愈流行,支持其餘版本控制系統接入逐漸成了雞肋,前人有言:「食之無肉,棄之惋惜」,正是如此。像 Github/Gitee 這樣的平臺雖然支持 svn,但 svn 訪問的仍是極少數,而支持 svn 則須要花費一些人力物力,而且在系統架構設計時增長了複雜度。若是如今開發一個 Git 代碼託管平臺則沒有必要支持 svn。Gitee 雖然支持 svn,但 svn 每日的請求數不足 1%,在這 1% 中,又有 50% 以上的請求是特定的用戶使用定時命令發送的。
公共 Git 代碼託管平臺不少時候其實是給用戶提供免費服務,爲了過多避免大文件大存儲庫佔用平臺資源,對其做出限制必不可少,一般是大文件限制 100MB, 存儲庫限制 1GB. 存儲庫的檢測簡單的遍歷存儲庫 objects 目錄便可,而大文件的檢測則複雜一些。Gitee 最初使用 Grit 檢測 commit 是否引入了 blob 原始大小大於限制的文件,但這種機制須要解析 Git 對象,檢測容易坍塌(一是檢測超時,二是檢測逃逸,三是存儲庫體積膨脹),後開使用原生鉤子,改變了檢測機制,則避免了這些問題。詳細狀況能夠閱讀《服務端 Git 鉤子的妙用》。
禁止大文件推送這只是堵,那麼大文件應該如何存放呢?Github 推出了 LFS 方案,目前 LFS 功能已經被大多數平臺支持,Github 將 LFS 存儲到 AWS 上,而 Gitee/Gitlab/Gogs/Gitea 大多使用自建的 LFS 服務器,存儲在特定服務器上。
若是一個存儲庫自身就已經很是大了,如何去解決用戶的訪問難題呢?好比 Windows 源碼超過 300GB
,若是用戶克隆存儲庫,按照每秒 1MB/s 的速度,須要 85 小時,這在任何代碼託管平臺都是不太現實的,好在微軟 2017 年發佈了 GVFS(如今叫 VFSforGit),在使用 VFSforGit 獲取遠程存儲庫時,能夠只得到目錄結構,並在本地建立佔位文件,但用戶操做這些佔位文件時,VFSforGit 客戶端纔會去請求服務器下載對應的對象,這大大改善了巨型存儲庫的操做體驗。VFSforGit 本地涉及到的主要技術是 ProjFS,在 Windows 上,VFSforGit 會建立 IO_REPARSE_TAG_PROJFS
類型的 ReparsePoint(NTFS 重解析點),讀寫到這些重解析點時,ProjFS 驅動會轉發到 VFSForGit 客戶端下載相應的對象。微軟不少開發者在 macOS 上開發,因此官方增長了對 macOS 的支持,而 Github 的 VFSForGit fork 則增長了對 Linux 的支持,不過離實用還有一些時日,Github ProjFS 實現庫是 libprojfs。
Git 代碼託管平臺支持 VFSforGit 客戶端比較容易,目前除了 Visual Studio Online,還有 BitBucket 也增長了對 VFSforGit 的支持。我曾用 libgit2 開發了一個 git-vfs-serve
命令,用戶訪問 brzox 時,brzox 請求 git-srv,git-srv 執行 git-vfs-serve 即可以支持 VFSforGit 客戶端的訪問,不過並未上線。
Github 最近宣佈了支持 WebAuthn: GitHub supports Web Authentication (WebAuthn) for security keys,這種機制可使用生物識別從而避免輸入用戶密碼,隨着信息技術的不斷髮展,一方面,安全機制不斷完善,另外一方面,用戶面臨的風險也會多樣化,複雜化。代碼託管平臺管理了開發者的核心資產,所以在安全上毫不能掉以輕心。固然須要作的不只僅是及時跟進新的安全機制,還須要對整個系統及時進行安全升級,淘汰舊的協議(好比 SSL3/TLS1.1),舊的加密,哈希算法(DSA,MD5/SHA1),及時採用新的協議(TLS1.3),新的加密,哈希算法(ED25519,SHA3)等等。
<!--附件下載,發佈文件,Archive 下載-->
一個優秀的 Git 代碼託管平臺,應該在軟件的開發整個週期都給用戶提供幫助,好比下載源碼,軟件發佈。源碼下載主要指 Archive 功能,軟件的發佈則須要平臺提供 Release/附件下載功能。
咱們知道 git-archive 命令能夠將存儲庫特定的 commit/branch 打包成一個 zip/tar 文件,而在 Git Over SSH(Git Over TCP) 實現中,只要咱們容許 git-upload-archive
命令在遠程服務器上運行,就打包遠程服務器上的存儲庫的特定分支。但因爲 git-upload-archive 與 git-upload-pack/git-receive-pack 存在一些不一樣,是的 HTTP 協議沒法實現 archive 協商。提供 archive 下載則須要另闢蹊徑。
咱們在遠程服務器上運行 git-archive 將其輸出做爲響應體的內容返回給 HTTP Client 即可實現 archive 下載功能,因爲 archive 下載其實是將 git tree/blob 遍歷而後寫入到歸檔文件後壓縮(tar.gz/tar.bz2 ...)或者是壓縮後寫入文件(zip),兩者都很是消耗 CPU 資源,所以咱們在實現 archive 下載功能的同時應該設計 archive 的緩存功能(固然緩存應該支持過時)。gitlab-workhorse 實現的 archive 下載功能即是先嚐試命中緩存,若是沒有緩存則調用 git 命令而後生成寫入到緩存文件。Gitee 最近實現的 blaze-archive 也採用了相似的機制,但 blaze-archive 是一個獨立的命令,這個命令其實是被 git-srv 調用,brzox 與 git-srv 通訊,brzox 將 archive 返回給 HTTP Client,而緩存的刪除則是 blaze 負責的。
附件,Release 能夠選擇雲方案,若是要將附件和 LFS 統一管理,實際上國內的阿里雲,騰訊雲之類的並不合適,這些平臺對並不支持相似 AWS x-amz-content-sha256
這樣的頭部,而是 Content-MD5
所以這些雲平臺要支持 LFS 則要花費多一些功夫。選擇國外的 AWS, Azure 則須要考慮經濟,網絡等問題。固然不管如何使用雲平臺都須要考慮經濟問題。
平臺自建附件,Release 功能可使用分佈式文件系統,如 FastDFS, 但 FastFDS 並非一個好的選擇,歷史比較久,存儲機制安全機制如今來講都不是很優秀。有個更好的選擇是 Minio, minio 使用 Golang 開發,支持 AWS API。許可協議是 Apache 2.0
,商用沒有阻礙,所以是用來搭建附件,Release 以及 LFS 存儲服務器的不二選擇。
Git 雖然是當前最受歡迎的代碼託管系統,但 Git 也面臨了一些難題,一類是如何支持大文件大存儲庫,這些問題有 Git LFS, VFSforGit 這樣的第三方解決方案,也有微軟,Google 開發者參與的官方 Partial Clone,部分克隆須要 Wire 協議支持,離可用還爲時尚早。
2017年2月,Google 開發者宣佈攻破 SHA1,這曾經給一些 git 用戶帶來了擔心,由於 git 使用 SHA1 計算對象 ID,但 git 使用的其實是一種特殊的 SHA1,將對象類型對象長度以及對象內容合併在一塊兒計算 SHA1,因爲有長度校驗,這使得 SHA1 的衝突可能被下降了,但不管如何,SHA1 也再也不是安全的,Git 在源碼中增長了 sha1collisiondetection 來避免 SHA1 衝突,而且增長了計劃遷移到 SHA-256,而且將一些涉及到 Hash 的代碼從單一的 SHA1 轉變成 object_id
。 關於 Hash 轉換,能夠查看文檔 Git hash function transition。
Git 從 SHA1 遷移到 SHA-256 困難重重,從首次增長文檔距今已經有兩年時間,而 SHA-256 的實現還不見全貌。與 Hash 遷移相比,壓縮算法的演進不重要更難實施,時至今日,zlib 壓縮已經再也不優秀,但 Git 可能還要負重前行。
軟件開發一直是一個飛速變化的領域,而代碼託管也要不斷面臨新的挑戰,道路漫漫,吾輩不休。