主頁 >  其他 > 探討 Git 代碼托管平臺的若干問題

探討 Git 代碼托管平臺的若干問題

2020-09-21 10:59:18 其他

關于 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 拉取流程:

Fetch Flow

Git 推送流程:

Push Flow

雖然在 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 nameRatioCompressionDecompress.
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 代碼托管平臺

基于內置工具搭建 Git 代碼托管服務

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 代碼托管服務,通常有非常多的選擇,下面是幾個目前仍然比較活躍的小型 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,

云服務級別的 Git 代碼托管平臺

隨著用戶規模和存盤庫規模的增長,達到一定級別后,上述代碼托管平臺往往變得力不從心,而下面的代碼托管平臺卻深耕于此,能夠支撐巨大規模的用戶量和存盤庫數量,

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 需要以有限的硬體實作更多的用戶接入,所以在服務的設計上更傾向于提供資源使用率,對一些比較容易造成計算資源緊張的服務進行降級,

Git 代碼托管平臺服務實作

<!--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 代碼托管平臺的伸縮性

<!--存盤庫分片,分布式檔案系統-->

伸縮性是 Git 代碼托管能否支撐成千上萬用戶/存盤庫的重要指標,像 Gogs/Gitea 這樣的代碼托管系統盡量認為自身運行在單一服務器上,因此這類 Git 代碼托管平臺伸縮性非常有限,當然如果使用 NFS/Ceph 這類分布式檔案系統能夠在單一服務器上支持更多的存盤庫,但 NFS/Ceph 這種分布式系統的做為 Git 代碼托管系統的存盤層,除了分布式檔案系統帶來的性能下降,還會帶來內網帶寬過高等更多的問題,

我們以使用 NFS 掛載實作伸縮性的平臺和 Gitee 分布式模型 git 請求 對比,I/O 細節簡化如下:

NFS I/O 細節:

NFS

Gitee Basalt I/O 細節:

Basalt

計算機是質樸的,流程的增加往往需要更多的計算資源,與 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:

DGIT

除了存盤庫的分片,代碼托管平臺還需要考慮資料庫 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 支持多執行緒,有更好的記憶體效率和高吞吐量,

Git 代碼托管平臺的增強功能

<!--大存盤庫,大檔案,保護分支,只讀目錄,安全,兩步驗證/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/附件下載功能,

Archive

我們知道 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

附件,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 也面臨了一些難題,一類是如何支持大檔案大存盤庫,這些問題有 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 可能還要負重前行,

道路漫漫

軟體開發一直是一個飛速變化的領域,而代碼托管也要不斷面臨新的挑戰,道路漫漫,吾輩不休,

關于 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 拉取流程:

Fetch Flow

Git 推送流程:

Push Flow

雖然在 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 nameRatioCompressionDecompress.
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 代碼托管平臺

基于內置工具搭建 Git 代碼托管服務

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 代碼托管服務,通常有非常多的選擇,下面是幾個目前仍然比較活躍的小型 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,

云服務級別的 Git 代碼托管平臺

隨著用戶規模和存盤庫規模的增長,達到一定級別后,上述代碼托管平臺往往變得力不從心,而下面的代碼托管平臺卻深耕于此,能夠支撐巨大規模的用戶量和存盤庫數量,

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 需要以有限的硬體實作更多的用戶接入,所以在服務的設計上更傾向于提供資源使用率,對一些比較容易造成計算資源緊張的服務進行降級,

Git 代碼托管平臺服務實作

<!--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 代碼托管平臺的伸縮性

<!--存盤庫分片,分布式檔案系統-->

伸縮性是 Git 代碼托管能否支撐成千上萬用戶/存盤庫的重要指標,像 Gogs/Gitea 這樣的代碼托管系統盡量認為自身運行在單一服務器上,因此這類 Git 代碼托管平臺伸縮性非常有限,當然如果使用 NFS/Ceph 這類分布式檔案系統能夠在單一服務器上支持更多的存盤庫,但 NFS/Ceph 這種分布式系統的做為 Git 代碼托管系統的存盤層,除了分布式檔案系統帶來的性能下降,還會帶來內網帶寬過高等更多的問題,

我們以使用 NFS 掛載實作伸縮性的平臺和 Gitee 分布式模型 git 請求 對比,I/O 細節簡化如下:

NFS I/O 細節:

NFS

Gitee Basalt I/O 細節:

Basalt

計算機是質樸的,流程的增加往往需要更多的計算資源,與 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:

DGIT

除了存盤庫的分片,代碼托管平臺還需要考慮資料庫 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 支持多執行緒,有更好的記憶體效率和高吞吐量,

Git 代碼托管平臺的增強功能

<!--大存盤庫,大檔案,保護分支,只讀目錄,安全,兩步驗證/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/附件下載功能,

Archive

我們知道 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

附件,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 也面臨了一些難題,一類是如何支持大檔案大存盤庫,這些問題有 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 可能還要負重前行,

道路漫漫

軟體開發一直是一個飛速變化的領域,而代碼托管也要不斷面臨新的挑戰,道路漫漫,吾輩不休,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/96488.html

標籤:其他

上一篇:QTableWidget中cell與item有什么區別

下一篇:求大神指引道路

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more