分享 RailsConf 2016 - 我们如何为三万人的公司横向伸缩 GitLab

psvr · 2016年05月27日 · 最后由 CHINAJAVABoy 回复于 2021年03月11日 · 24941 次阅读
本帖已被管理员设置为精华贴

以下是本人在刚刚举办的 RailsConf 2016 上的演讲,介绍支付宝如何改造 Gitlab,作为整个阿里巴巴集团的代码管理后台。译自视频 http://v.youku.com/v_show/id_XMTU4MzU3MjEzMg==.html

引子

当初我把这个题目提交给 RailsConf 主办方的时候,提交到了“分布式时代”这个主题中。然而最后我惊讶地发现,我的演讲好像是那个主题中唯一的一个,竟然没有别人讲 Rails 应用如何伸缩的问题,或是讲 Rails 的分布式设计问题。我觉得这恐怕是因为作为 Rails 开发者,我们在开发的时候就比较注重遵循最佳实践,从而不会使得项目后期的伸缩成为什么大难题。

但是今天这个 Rails 应用——GitLab——却真是个坏小子,我今天就来讲讲我们是怎么修理他的。谢谢大家来听我的这场演讲!

自我介绍

我的名字叫潘旻琦(孝达),我来自中国,是蚂蚁金服体验技术部的一员。这是我的 Github 主页:https://github.com/pmq20 和 Twitter 主页:https://twitter.com/psvr 。欢迎大家关注我的主页!

GitLab 是什么

其实,偷偷地讲,GitLab 就是一个 GitHub 的开源克隆版。但是这个讲法难登大雅之堂;应该说,GitLab 是一个可以独立安装在你自己的服务器上的 git 盒子,让你拥有一个私密的代码托管协作平台。

如果我们把 GitLab 看作一个黑盒,那么它对外无非提供了两个接口:一是 HTTP,二是 SSH。HTTP 接口不仅可以在浏览器上用,还可以在命令行上用。在命令行上,用户可以通过 HTTP 上传(git push)和下载(git fetch, git clone)代码;在浏览器上,作为一个 Rails 应用,用户可以进行各种页面上的交互操作,如浏览代码、发起 Merge Request、查看提交历史等。SSH 接口则相对简单,这个接口只能用在命令行上进行代码的上传下载。

在后端,以一个极度简化的视角来看,无非就是借助 git 来持久化用户传来的内容;然而成也萧何败也萧何,就是这一点导致了 GitLab 的横向伸缩难题。如果看得更仔细一点,GitLab 的数据持久层主要由三部分组成:一是文件系统,因为 GitLab 背后是通过调用 libgit2、git 命令、grit 来访问和操作存储在服务器文件系统上的 git 仓库;二是 MySQL,这个是用来持久化用户、权限、Merge Request 这些页面交互数据用的;还有 Redis,主要作 Sidekiq 的任务队列和 Rails 的缓存用。其实他们还支持 PostgreSQL,因为 ActiveRecord 抽象掉了底层数据源的具体实现。

接下来我们把 GitLab 打开,看看它的内部有哪些构造。一个简略的分层结构如图。

在最高层,Nginx 和 OpenSSH 分别用来接收客户端发来的 HTTP 和 SSH 请求。首先,当客户端发来的是一个 HTTP 页面请求,那么它会进入下一层的 Unicorn 进程,进而利用第三层的诸多 gem 获取关于仓库的信息或对仓库进行操作,这些 gem 包会调到第四层,通过调用原生 git 命令或通过调用 libgit2 中的函数对存在磁盘上的 git 仓库进行实际的访问和操作。其次,当客户端发来的是 HTTP 协议下的 git 操作,那么这个操作将直接被转向给 GitLab Workhorse,它会进一步借助 Unicorn 验证用户的操作权限,之后便将操作全然代理给第四层的原生 git 命令。因为这种请求不同于页面请求,是 I/O 繁重型,因此 GitLab Workhorse 用 go 写成,而不是 Ruby。最后,当客户端发来的是 SSH 协议下的 git 操作,OpenSSH 会在用户认证登陆成功后启动一个特殊的 shell——亦即 GitLab Shell——来调用第四层的原生 git 命令,而这个 shell 跟 Workhorse 类似,自身也不含用户逻辑,必须再内部调用 Unicorn 才能完成用户认证与鉴权工作。

这个架构在小型开发团队中工作相当顺畅,但是很不幸,阿里巴巴有三万多员工,这个数字来自前年的财报,如图。就在我演讲的前几天去年的财报也公开出来了,股票价格又涨了不少,我们公司业绩相当给力哦。

横向伸缩 GitLab:前端

作为一名 Rails 程序员,系统中最容易的集群化部件就是 Unicorn 了,因为社区内有无数集群化部署 Unicorn 的文章,我们只要多跑几个 Unicorn 实例,再在前置服务器的 Nginx 中配几个 upstream server 就搞定了!

然并卵。Nginx 对于 HTTP 协议的负载均衡自然是得心应手,但别忘了还有 SSH。我们的前端机还必须能分发 SSH 请求,在这种需求下 Nginx 便不再合适了。为了解决这个问题我写了一个 gem 叫 ssh2http,源码在 https://github.com/pmq20/ssh2http 。它基本上消除了 SSH,而把请求的处理逻辑代理给了 HTTP。因为当对比 git 通过 SSH 及 HTTP 访问服务器的区别时,我们发现它们的通讯方式非常相似,所以写个代理问题就解决了。

但实际上我们没用这个方案,我们借助了阿里的中间件 VIPServer。VIPServer 是基于 Linux 内核 LVS 中的 IP Virtual Server(IPVS)机制实现的,它可以在 TCP/IP 的第四层上进行负载均衡,而不同于作用在第七层上的 Nginx。通过传输层复杂均衡,我们的前端机便可通吃 HTTP 与 SSH,因为负载均衡器已然对应用层的协议不可知了。当然这样也带来一些缺点,例如负载均衡器没法通过检查 HTTP 协议的返回值是否等于 200 来为集群机器的健康状态打分,因为这是应用层的逻辑。但基本的 IP 权重设置、IP 黑名单、简单的健康监测功能还是可以进行的。

加入了 IPVS 之后那么问题来了,不光用户的 SSH 客户端开始抱怨同一个域名连接上的机器的 SSH 签名不一致,同一个用户通过网页添加的 SSH 密钥也只能允许他访问集群中的某台机器。为此,我们必须把集群中所有机器的 SSH 主机密钥设为一致,且必须在 rails 中将通过网页添加的 SSH 密钥广播给集群内的所有机器。用 sidekiq 实现这个不太合适,因为一个任务只能被集群中的一台机器抢占;我们是通过 redis 的 pub sub 数据结构来实现这一步的。

至此,前端的伸缩问题就解决了。

横向伸缩 GitLab:后端

在讲我们的方案之前,我想暂停一下跟大家回忆受 Heroku 推崇的“The Twelve-Factor App”法则。之所以 GitLab 是个坏小子就是因为它没有遵守十二法则的第四条:“把后端服务当作附加资源”。对应用程序而言,不管是数据库、消息队列还是缓存,都应该是附加资源,通过一个 url 或是其他存储在配置中的服务定位来获取数据;部署应可以按需加载或卸载资源。然而 GitLab 系统中的三个组件——libgit2、git、grit——都是直接作用在文件系统上的,这就是万恶之源了。

其次,面临这样一个情况,我想提醒大家其实我们有很多选择,方案各有利弊,我接下来要详细讲的方案不一定适合你。如果将来你要伸缩其他的 Rails 应用,也可以按照类似的思路来思考问题。

一个方案是用官方企业版 8.5 以来加入的新功能 GitLab Geo。但 GitLab Geo 没有真正的解决问题,因为它是在集群中全量复制每一台上的 git 仓库的,没有分片。这个方案假定每台机器都有足够的磁盘空间来容纳全量的资源,但在阿里这个假设不成立,所以对我们来说用处不大。从分布式系统的角度评价,GitLab Geo 是一个一主多从、无分片全量复制的分布式系统,实现 CAP 定理中的 A 与 P。

另外一个方案是我们杜撰的,貌似很完美。首先,通过 ssh2http 消除 SSH,问题简化为纯粹的 HTTP 请求的问题。其次,注意到一个仓库的特征性名称是"namespace/repo_name",而且几乎每个请求的 URL 中都包含着个部分。那么我们直接通过这个名称作分片路由好了,只需要发明一个哈希算法,把"namespace/repo_name"哈希映射到集合 {0,1,2,...,cluster_size},便可以把所有请求在集群中分片分发,仓库也分片存储就好了。但这个方案也有问题,大家发现了吗?首先,别忘了 Sidekiq 也要分片啊!因为一个 unicorn 分片产生的任务只能由这个特定分片上的 Sidekiq 来处理,这样一搞复杂度很高。其次,应用层的修改也不能避免:例如,系统管理员的页面要列出仓库的列表,每个仓库后面还有大小,这个数据是跨分片的,只能考虑降级这种功能;类似这样的单请求跨分片场景应该还有很多。所以这条路并不好走。

那我们还是乖乖对付文件系统吧,我们有哪些办法呢?首先,我们可以使用网络附加存储设备,亦即硬 NAS(如 EMC 生产的存储设备),或使用软 NAS(如 Google File System);其次我们可以类似于 MySQL 的分片那样,将文件系统分片存储,然后通过远程过程调用的手法将请求发送到远程的文件系统上;最后,我们也可以整体去掉文件系统,将系统这部分进行改造。

在阿里实施去 IOE 战略后,硬 NAS 设备已不再属于集团考虑采购的范围;软 NAS 方案在阿里也不成熟,阿里目前还没有一个可以与谷歌的 GFS 媲美的内部中间件;RPC 的方案是可行的,而且是经过验证的,因为 GitHub 就是这样做的,可惜它不开源。因此,我们决心尝试第四个方案,我们打算去掉文件系统,改用用云端存储。

这里我们要提醒大家,使用 NAS 是值得考虑的。因为它很简单,我们只需替代掉系统中的一个组件,而无需引入接口的变化。但我们没有去走这条路,而且这条路也有它的问题:例如增加了运营成本,尤其是软 NAS,复杂度较高,需要能力很强的运维工程师来支持。其次,这个性能的损耗可能也比较严重,因为每个底层 FS I/O 都换成了较慢的网络 I/O;而且由于变化引入的位置在整个技术栈中处于的层次比较低,这种变慢的 I/O 的数量较大,做了乘法之后累积的延迟就大了。

我们走了第四条路——去掉文件系统,使用云端存储。阿里厂内有成熟的存储方案,即阿里云的对象存储(Object Storage Service,简称 OSS)服务,它类似于亚马逊的 S3,特别适合海量数据存储,且存储容量和处理能力均已实现自由伸缩。

整体架构

GitLab 中存在的 libgit2、git、grit 这三种访问 git 的方式实在是太多了,势必会对后期改造带来麻烦。因此,为了简化问题,我们先想方设法去掉 grit 这个组件。grit 主要用在 GitLab 的 wiki 功能上,被 gollum-lib 和 gollum-grit_adapter 两个 gem 使用。所幸,gollum 使用了适配器设计模式,可以直接撤掉 gollum-grit_adapter 而改用 gollum-rugged_adapter,而 rugged 是调用 libgit2 的。通过这种手法,系统就只剩下了 libgit2 和 git 这两种访问 git 的渠道。

libgit2 和 git 命令的应用场景都非常广泛,难以进一步简化。例如 libgit2 被上层 rugged 和 gitlab_git 这两个 gem 包封装后备用在 rails 的 MVC 的各个角落之中。而 git 命令则被 gitlab-shell 和 gitlab-workhorse 广泛使用,好多操作——诸如上传下载代码、生成压缩包、合并 MR 等——都是通过直接调用 git 命令来完成的。

我们观察到 libgit2 在设计上有一个非常有用的特性,亦即 git 对象的后端存储层 backend 被抽象出来,做成了可插拔的。例如官方提供的代码中,默认的 backend 是基于文件系统的,但也有将 FS 的 backend 换掉的例子,从而将 libgit2 的后端存储嫁接在 MySQL 以及 Redis 等其他数据源上。我们打算利用这个特性,为 libgit2 开发一个新的 backend,亦即一个基于阿里 OSS 的 backend,从而将 libgit2 的后端存储嫁接在 OSS 上。

然而,git 的后端存储确是写死在 FS 上了,如若替换则会引入代码的大改。但万幸的是,git 的开发思想非常 UNIX 化:git 包含 140 多个子命令,这些子命令之间互相调用,每个子命令做到了功能单一、简单。我们只需巧妙挑选其中的动刀点,在 libgit2 的新后端上重新实现某些子命令,甚至是某些子命令的某些参数,即可实现 git 的 libgit2 嫁接,工作量并不大。

libgit2 嫁接 OSS

libgit2 可插拔的后端分两部分:一是 refdb,用来存储分支名、tag 名等 git 指针;二是 odb,用来存储 tree、commit、blob 等 git 值对象。refdb 只能有一个,而 odb 可以有多个,且可设置优先级。我们新设计的后端包含了 OSS 存储的 refdb,以及包含了两个 odb:一是散装 OSS 存储,二是大包 OSS 存储。散装 OSS 存储优先级低,大包 OSS 存储优先级高。

散装 OSS 存储和 refdb OSS 存储的原理一样,就是将原本作用在文件系统路径上的读写操作转到 OSS 上,读文件变成一个 HTTP 的 GET 请求,写文件变成一个 HTTP 的 PUT 请求。但如果只有这么简单的设计,系统的速度必然变得很慢,git clone 的性能将退化到 svn 一般,这是完全不可接受的。

因此,我们设计了更复杂的大包 OSS 存储。大包 OSS 存储的写的部分主要用在接收用户上传来的代码的场景。当用户传上来的代码的 pack 太大,不适合直接解压缩进行散装存储时,我们便直接调用 git-index-pack 命令为用户传来的 pack 计算索引,将 .idx(索引文件)和 .pack(用户传来的包)这两个文件直接存储到 OSS 上。

但读的时候,总不能把整个包下载下来。因此我们利用 OSS 的断点续传功能——亦即通过 HTTP 协议的 Range 头——巧妙地编写了大包 OSS 存储的读取逻辑,一个典型读取场景发生时产生的 HTTP 请求如图所示。

每当读一个文件,我们会先取下大包的索引文件 idx,从中计算出 pack 文件中目标对象的偏移量,然后通过 Range 请求取出需要的部分,成功后服务器返回 206 Partial Content。这时,取出的需要的部分可能因压缩,只是一个 Delta 片段,因此需要继续重复上述步骤直到找到 Delta 的 Base Root 并最终解压缩出来,Delta 到 Base Root 的链长一般不超过 10,且由于 git 在产生 pack 时的优良启发式算法,Delta 在 pack 中的偏移量与它的 Base 链距离不会太远,因此我们每次发出 Range 请求的时候会故意把 Range 的区间取大一些,这样能有效节省 Range 请求的数量,目前该窗口值我们取为 16 MB,这纯粹是一个经验值,可随观察结果进一步调整。

通过一个具体例子来解释,假设我们要取的 git 对象的 SHA-1 值为 9fcf811e00fa469688943a9152c16d4ee90fb9a9,因为它的第一个字节是 0x9f,进入 idx 索引文件的扇出表 IDX[8 + (0x9f - 1) * 4] 到 IDX[8 + 0x9f * 4] 字节取出以该字节开头的对象的编号范围,之后到该范围指向的 SHA-1 清单列表中进行二分查找看 SHA-1 值是否存在,当判断到存在时,跳过 对象总数*(20+4) 的区域(这片区域主要是和校验等),去读取该对象在 pack 文件中的偏移量。Range 取出 pack 文件的该偏移量处的内容之后,读取该处内容的类型,若发现是 OFS_DELTA(0b110),则继续读取其 Base 的偏移量,为其所有 Base 重复上述步骤直到取到其 Base Root 为止,最后遍历的整个链条如表所示。但得益于 pack 文件的良好设计,我们实际只需要很少 HTTP Range 请求便可完成整个链条的读取,因为我们可以看到 offset-pack 是单调递减的,链条整个区间长度仅为 164 KB,这对于一次 Range 请求的 16 MB 窗口而言绰绰有余。

git 嫁接 libgit2

开发完 libgit2 的新后端之后,接下来便是对接 git。上面已经分析过,这一步无法绕过,但工作量不大,因为 git 的命令小而多,只要找到最佳的动刀环节即可。

首先是下载代码,亦即 git fetch / clone。服务器端首先被调用的是 git upload-pack --advertise-refs,来广告服务器上已有的资源,这个部分我们可利用 libgit2 简单重写。然后被调用的是普通的 git upload-pack,这一步处理的是大量的 git 自定的应用层协议,不易修改,我们保持不动。接下来 git upload-pack 又自身调用了 git pack-objects,用来将数据进行打包,这是一个仅涉及数据 I/O 的完美的动刀点,且 libgit2 提供了等价的 pack builder 功能,我们用 libgit2 将其重新实现。

其次是上传代码,亦即 git push。服务器端首先被调用的是 git receive-pack --advertise-refs,重写思路类似。处理通信协议的 git receive-pack 亦不进行修改。接下来要分情况,git 源代码中很重要的一句话就是对“ntohl(hdr.hdr_entries) >= unpack_limit”的判断。当用户传来的数据较少时,git 调用 unpack-objects 将传输包解开,此处用 libgit2 进行改造,加入写入 OSS 散装后端的逻辑;反之,当用户传来的数据较多,git 调用 index-pack 直接为传输包生成索引,此处也用 libgit2 进行改造,加入写入 OSS 大包后端的逻辑。

跑分结果(无缓存裸跑分)

经过这些修改,性能变得如何呢?在把所有缓存关掉的情况下,我们得到了如下跑分结果,时间均为墙钟时间。

测试库:gitlab-ce,它包含 20 万个对象,打包后超过 100MB。

原版 OSS 版
全量 git push 53.299s 54.697s
增量 git push 3.059s 2.845s
git clone 47.096s 1min14.12s
git fetch 0.806s 16.019s
GET /namespace/repo/tree/master 74.5ms 5877.7ms
GET /namespace/repo/tree/master/builds 50.0ms 4547.0ms

缓存

我们看到,把 FS I/O 变成网络 I/O 后,系统不可避免得变慢了,所以必须在各层加入缓存。考虑到实际部署时集群内的每台机器上总有一定量的 FS 存储空间,我们打算将这一部分空间利用起来,做成 LRU 缓存,再加入两个 odb 后端层作为本地 FS 缓存层,使得整体呈现为汉堡包结构,我们称作“四合一无敌组合”。与此同时 refdb 也加入 redis 缓存逻辑,从而使较慢的 HTTP 请求仅发生在 redis 缓存被击穿的情况下,从而提升性能。

新增的两层的数据从何而来呢?对于散装本地存储,数据一是来自 ntohl(hdr.hdr_entries) < unpack_limit 时的 git-unpack-objects,二是来自散装 OSS 的读接口。当发生这两个场景的操作时,从 OSS 上取到的数据不再浪费,被缓存在 FS 上。对于大包本地存储,数据则也来自于两个场景,一是 ntohl(hdr.hdr_entries) >= unpack_limit 时的 git-index-pack,二是 git-pack-objects,原理类似。

后续工作

为了把成果回馈给开源社区,我们还有很多工作要做。例如:

  • 开发基于 AWS S3 的 libgit2 后端,让大家可以在 Heroku 上跑 GitLab
  • gitlab 项目:提交补丁减少对 git 命令的直接调用,改为基于 libgit2 的 rugged 库级调用
  • gitlab 项目:提交补丁让用户选择后端存储,如可选将仓库存到 FS AWS S3
  • gollum 项目:提交补丁让基于 libgit2 的 rugged 成为默认,不再使用 grit
  • libgit2 项目:提交补丁提升性能,因为 pack builder 的实现跟 git 相比还很慢

精彩!👏🎊🎉

收藏 点赞 周末看

psrv 很屌

@psvr 快说,无人机是不是被你拿走的。

  1. 个人觉得还是用哈希算法的那个方法好点,文件分片存,数据库用 RDS,Sidekiq 肯定是可以改造的而且容易,项目大小统计等非数据库细节,可以看能否通过一些 ssh 代理来做。
  2. 只改应用层肯定比全套改要好。现在的部署方案非常复杂,要重写一部分 git lib,开发者需要吃透 git,rails 开发者大多是应用层开发者,花时间研究 git 有点远离初衷了。
  3. 最后 S3 作为仓库感觉不靠谱啊,从本地硬盘读取一个文件肯定比在网络上读取一个文件效率要高得多,git 操作牵涉到很多文件最终结果可想而知。

建议借鉴一下 Github,Bitbucket,Coding 等项目的成功经验,应该会有所帮助。最后还是要赞一下 psvr,研究能力超强,恭喜在 railsconf 上首 show 成功。

delicious~

#5 楼 @gonglexin 是啊,这你还记得!哈哈

@yakjuly +1

个人也认为应用层修改比较简单,具体想法如下:

每个组织分配一台机器 然后所有非组织用户分配一台机器

上述所有机器上都独立部署 gitlab(nginx、unicorn、sidekiq【redis 使用 127.0.0.1】),共用一个 RDS

所有机器修改 /etc/ssh/sshd_config AuthorizedKeysCommand 指定脚本,脚本发送 http 请求到上述机器中的某一台机器判断 ssh 请求是否合法,禁用所有机器的 /home/gitlab/.ssh/authorized_keys,这个步骤是为了解决【必须在 rails 中将通过网页添加的 SSH 密钥广播给集群内的所有机器】这个问题

最后结合上面说的 VIPServer 统一 http ssh 访问地址

@psvr 这样呢?

#10 楼 @vkill 应用层修改确实是条不错的路线,减少了对底层部件的修改,实现难度也低。但方案都有利弊,缺点是很多功能必须得降级(因为跨 shard);定制版最后也会跟社区版脱节,以后升级也是个问题。

非常棒,但是确实更适合大公司

#5 楼 @gonglexin 这你都记得!!!

@psvr 定制后和社区脱节这个确实是个问题,综合来看还是对 rugged 修改比较靠谱。

我不知道有多少人真正读懂了...

公司内部并不需要考虑什么扩容 公司员工数基本很稳定 也就是说系统的承受力有一个最大承受期望值 只要硬件把这部分给扛下来就好 相对于扩容而言 公司内部需要的更多的是速度、速度、速度 速度就是效率,相比扩容 更有意义的我觉得应该是北京和杭州两个中心仓之间的同步你们如何实现的,毕竟跨网操作速度还是有损的。

19 楼 已删除

#6 楼 @yakjuly Github,Coding 这类项目的经验在哪里能看到呢?谢谢!

#18 楼 @hechen0 那是小公司

像 BAT 之类级别的公司,光技术人员就有上万人,个别仓库上 G,单机存储容量是完全不够的

附上老外对这个问题的讨论,供参考。

23 楼 已删除
24 楼 已删除

关于 SSH 密钥,我在以前项目中是通过将用户上传的公钥保存在数据库,然后 sshd 通过配置 AuthorizedKeysCommand 和 AuthorizedKeysCommandRunAs 执行相关的脚本从数据库获取公钥,这个方案比多服务器同步更简单

@Sanster, Coding 一个用户就是一个独立的用例和环境,你在他们网站上看介绍就可以了解一二,每个账户都可以设置内存 CPU。 Github 我目前也不知道,可以自己 google 搜下相关文章。

16M 的 fetch block size 太恶了。这种玩法用在公网上吃个流量费就要破产了。就省流量来说,用 FUSE 做只读 mount 然后做 block 级别的缓存也比现在的方法省流量。

pack 格式肯定有改造的空间。

最后是 ssh2http 应该不是必须的?当然发现 git-upload-pack 和 git-receive-pack 是关键命令是很重要. 如果保持 ssh2http 的服务的话,明显可以做成 stream upload, 哪怕你看到某命令前面要去掉 4 bytes 也是一样,目前的做法明显是 buffered upload. 没有这个服务的话这两个命令是需要 fork 进程是吗?感觉引入两个假的 streaming upload 进程去做转 http 操作要比这个 ssh server 要好。

为什么不用阿里云的块储存或者文件储存?

#28 楼 @rei 这已经是用了啊。

#29 楼 @fcicq

我们走了第四条路——去掉文件系统,使用云端存储。阿里厂内有成熟的存储方案,即阿里云的对象存储(Object Storage Service,简称 OSS)服务,它类似于亚马逊的 S3,特别适合海量数据存储,且存储容量和处理能力均已实现自由伸缩。

我只看到 OSS。

@rei OSS 就是啊。本来就是 S3 clone 所以就是存文件的

#32 楼 @rei 要说 EBS 等同的块存储的话,那个他们内部叫盘古的东西其实口碑非常差但还是在卖。非要拿出来说就是揭他们的疤。

34 楼 已删除

#33 楼 @fcicq

https://www.aliyun.com/product/disk/ https://www.aliyun.com/product/nas

如果是某些方面不符合要求,那么阿里云应该把内部源码库的支持作为首要任务做好。

#35 楼 @rei NAS 这个服务比较奇怪。感觉是某种 NFS over block storage. 而且请注意 NAS 现在是处于公测状态的服务,没有正式商用所以不应当算作正式服务。disk 的事情已经说过了。

#36 楼 @fcicq 所以我就是想知道不用的理由。

#37 楼 @rei 这你应当去打听一下飞天盘古的历史。不过就算问不到,看一下他们论坛上对云硬盘性能的抱怨也是很有趣的。有些事情偶并不愿意知道但还是听到了一些。不过简单的说就是看到 Amazon 有或者 Google 写了论文的东西自己也想做一个,但设计上实现上或者甚至硬件上的其中之一或者多个所限根本就没有达到标准。块存储的山寨品可能还不是最失败的,bigtable 系的山寨成活率可能是最低的。

#37 楼 @rei 内部网络没部署,然后有一些管理要求,代码库这类东西不能放到公司局域网之外

瓶颈在存储层,项目达到一定数量后,往往是硬盘不够。GitLab Geo 解决的不是分仓问题,就连 gitlab 的产品经理也说他们没有分布式存储。楼主的方案是很好的!还有一条 rpc 的路也确实是可行的,其实就是将 git 层(旧版的 gitlab 用的是 grit,新版的用 gitlab_git)作 rpc 处理,存储层可以灵活扩展

看完觉得做这么多事要解决的主要问题吧,就是是有人把一堆 jar 包等巨型文件提交了上去

#41 楼 @luikore 上传二进制文件的还是少数,这个问题也可以通过制度来解决

我们目前使用的方案是按 group 做 sharding, 秘钥像 @quakewang 说的那样保存数据库,SSH 协议通过 https://github.com/mscdex/ssh2 做一层代理,目前看来大多数需求是可以满足的,遇到的问题有:

  1. 性能下降,但是肯定比网络存储要的方式要好
  2. 访问多个不同机器代码的场景 (如全局代码搜索) 会有问题

看到有对 Coding 的经验感兴趣的,我也来分享一下(图片来自 coding blog):

这里主要对一些图上可能不太清楚的部分展开一些介绍:

  • 通过 NFS 共享仓库的存储到不同的 git worker 上(分担 CPU 和 IO),每个 worker 上运行 rpc server(GitD)和 RepoManager
  • Git Server 是 http/ssh/git 协议的 router,负责认证和转发请求给 rpc server(GitD),也是多实例的
  • Git 服务 完全不依赖 web 服务,即使网站挂了也只会影响到网页上的代码浏览,而不会影响到 git 客户端的操作
  • RepoManager 主要是给 web 端对 git 仓库的操作提供 rpc 服务的,比如网页上的代码浏览和 MR 功能都是它的使用者
  • 同样的,因为使用了网络存储,必然会导致性能有所下降,我们也做了大量的缓存以提升 web 端服务的响应速度

Coding 后端主要的开发语言是 java 和 go,当然也有少量的 ruby 组件,基本上所有组件都是以微服务的方式运行,相互之间使用 rpc 来通信。Git Server 和 GitD 组件完全使用 go 语言编写,相信大家通过 ssh 客户端的调试信息也可以看到,ssh 协议的路由是通过 golang 的 crypto/ssh 库实现的(公钥存储也是在数据库里的),RepoManager 和 web 后端采用 java 语言编写。

更详细的介绍可以看 coding blog 里这篇关于代码托管架构的文章:CODING 代码托管架构升级之路

话说有考虑过 git lfs 吗?https://github.com/github/git-lfs

终于在大文件上也不输 perforce 了

今年 4 月 aws summit 上和 github 的卖企业版的员工聊天时提到阿里自己优化 gitlab 的事情,他们很直白的说也就阿里有大量人力可以这么干了。。

在支付宝还有搞 ruby/rails 的,赞!而且谈了社区几乎不触碰的分布式领域 哈哈

#49 楼 @magic 免费的平台比较好的只有 gitlab,而 gitlab 又使用了 ruby,所以他们也是不得已而为之!其实,代码托管用 ruby 来做,有点不太适合,实现分布式困难,性能不行!gitlab 到目前为止我觉得都没有给出一个适合几万人的大公司使用的产品方案。

#50 楼 @johnwhm 不懂可别乱说,GitLab 没法分布式,跟 Ruby 有什么联系?

#51 楼 @huacnlee 我在用 druby 搞 ruby 的分布式,对于一些不可序列化的对象,处理起来很麻烦,而且还要高 ruby 的多进程,有太多的坑要去挖。

jasl RubyConf China 2016 讲师和主题介绍 (15/15)(更新完毕) 提及了此话题。 07月22日 07:34

赞~

#48 楼 @anklos 的确,一般公司估计要么陌陌 git clone [email protected]:gitlab-org/gitlab-ce.git ,或者点下git 企业版

#54 楼 @kamiiyu github 的企业版,小公司用用还好,大公司人多了,简直是一大笔开销。

#52 楼 @johnwhm 我们用druby改造过码云(基于 Gitlab 5.5),相对来说还是慢了不少,就放弃了。

liujinxia libgit2 是如何绑定到其他语言的 提及了此话题。 10月02日 22:10
vkill 回复

请教个问题,搭建了两套 gitlab 应用做集群,mysql 数据库和 gitlab 仓库目录拎出来了共用,redis 有必要也拎出来吗?每套上面一个 redis 会影响 gitlab 操作吗?

我是螺丝钉

需要 登录 后方可回复, 如果你还没有账号请 注册新账号