探讨 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。)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 拉取流程:算法

Fetch Flow

Git 推送流程:sql

Push Flow

虽然在 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 代码托管平台

基于内置工具搭建 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 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 代码托管平台

基于内置工具搭建 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 可能还要负重前行。

道路漫漫

软件开发一直是一个飞速变化的领域,而代码托管也要不断面临新的挑战,道路漫漫,吾辈不休。

相关文章
相关标签/搜索