首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

高可用分布式 Git 代码托管系统的构建

本文为 CODING 资深架构师欧俊飞在 CODING 技术小馆 · 广州站 的演讲内容整理。

大家好,我是一个程序员,一直在 Coding.net 打杂,从开发 Coding.net 的网站功能、设计系统的架构、运维的事情我都有做过。

我们今天技术小馆的主题是高可用实践探索,而高可用相关的内容有很多,今天我先分享一下我们是如何构建高可用分布式 Git 代码托管系统的。

如果写了一天的代码,精疲力尽,提交的时候发现竟然提不上去了或者提交非常慢,肯定大为恼火,所以 Coding.net 一直非常注重服务的可用性,大家最近也会发现 Coding.net 最近经常在深夜进行维护更新,这的确是我们在做的一些优化,而这些优化目前还没有做完,之前存在的一些问题可能还会出现,但是我们已经比较明确知道去怎么解决他们,希望大家的使用体验会越来越好。

Git 与版本控制系统的演进

那今天我就介绍一下我们优化的方式和实践经验,按照国际惯例或者 PPT 潜规则,我需要先介绍一下 Git 是什么。Git 是一个分布式的版本控制软件,它的诞生就是为了解决集中式版本控制管理带来的一些问题,它是为像 Linux 的这样具有海量分支以及开发者散布在全球各地的项目设计的。如果是用 SVN 这样的集中式版本控制,全球那么多开发者往一个仓库推拉代码,服务器肯定是扛不住,所以 Git 能在每个人的电脑上独立提交,只在必要时做一下同步,那就无需维护一套强大服务器。

我们先说说版本控制系统的演进,虽然称其为演进,实际上我觉得是属于三个级别的系统,从技术发展上是这个路线,实际上对应的场景不同。

比如说本地的版本管理方式,即从编辑器开始,自己管理文件名的变化。这个好处就是它可以脱离任何的服务器环境自己管自己。当然坏处就是如果你的系统挂了或者是硬盘坏了,那本地的控制器就没有了,也不方便跟其它人协作。

然后是一些大公司或者是需要集中式管理的组织比较喜欢的中心化的版本管理方式。中心化主要好处就是我可以对仓库某一些内容做分级,做权限控制非常简单,因为它可以指定哪个东西给谁用。但是坏处就是如果我的服务服务器挂了,所有人都不能工作。

而最后的分布式就是目前比较流行的,就像 Git 那一类的版本控制系统,好处就是不依赖服务器,可以当本地的版本控制来用,也可以把本地版本控制来连用,实现大家共同开发产品的目的。有一个坏处就是它默认情况下在每个地方保存的仓库都是完整的。比如我提交几年代码之后,仓库变成了几个 G 大小,那别人要拉我的仓库下来就有几个 G。

代码托管面临的问题

我们说 Git,目前 Git 这个主要的比较知名的平台就是这几个,Bitbucket 是有免费的数据库,GitLab 是开源的,Github 就不用说了,国外的服务有个不好的地方就是网站的速度会稍微比较慢,服务也不一定符合中国程序员的实际情况和使用习惯,所以我们做了 Coding.net。

既然我们要做公有的代码托管,必然要面临这三个问题:安全性、可靠性和高性能。这三个问题就是大家平时用在线以及各种云服务的时候都会遇到的问题。

然后 Git 本身有一个比较难解决的一个实现上的技术点,就是它会要求很高的 I/O,因为本身它虽然是一个分布式的产品,但作为公有云的服务,别人就会以集中式的方式使用他。有很多人会把所有的东西存在上面,防止自己的东西被丢掉。如果 I/O 没有做过优化,每次提交,本地和远程都会对比一次,那你仓库越大,读的数据越多,时间也就越长。而且对比本地和服务器上的仓库差异需要占用 CPU,就会导致 I/O 和 CPU 的用量都比较高。

这两张图就是我们 Coding.net 这次优化 Git 架构之前后的两个状态,第一版就是为了快速上线,做了一个单机版,仓库都是单机,然后做一个冷备,然后程序和仓库的内容都放在一起,这样的好处因为它是单机的,所以他没有一些同步的问题或者是一些延迟相关的东西。但是缺点也很明显,因为是单机的,如果我们要更新程序或者是做一个调整,重启这些程序的话就必须是有缝的,服务就不可用了。

我们为了解决发布和程序的问题,就用了 NFS 来做共享存储,然后应用服务会分开不同的机器来跑,然后这些机器都会挂载同一套 NFS,存储不够用的时候会用同一套 NFS 机制来扩容。

但是这个问题是比较明显,相当于把所有的 IO 访问都放到 NFS 了,NFS 是网络协议,始终会有一些延迟,导致它天生会有一些访问会比较慢的情况,响应时间肯定没有单机快。所以后面我们又改了一次。

然后我们尝试把目前 NFS 的架构做了调整,我们研究了一下一些可能的方案,首先是说 Ketch,这个是 Google 的开源作品,虽然它开源了,但是它并不是一个完整的开源项目,有很多东西都是需要自己改,而且也是缺乏维护,我们做过这个东西的测试,虽然说看起来是一个大厂出来的东西,但是实际上它的性能并没有我们预想中好。

另外一个就是比较多第三方程序用的 libgit2,这个是 C 写的库,给非官方提供 Git 的应用来操作 Git 仓库。这个库就是把 Git 的操作抽出来变成 API,给第三方应用直接操作 Git 仓库,但是没有本质上解决这个问题。

然后我们再想如果最简单来说是不是把 NFS 改成 DFS,DFS 也是单点 I/O 过高的情况,而且它还是有网络延迟,在 Git 协议交互过程每次 I/O 操作的延时都多一点,就算只是多延时了零点几毫秒,但完成整个过程的几十或者上百次 I/O 操作后,这个延时就会到秒的级别。

解决方法

所以我们自己实现了一个 DGit 分布系统,这个分布系统就是我们在 Git Push 的时候,我们会把 Push 的内容同时写到三个地方,这三个 Zone 就是单独的机器,用本地的硬盘,然后各自写本地硬盘,保证写入时没有网络 I/O 的延迟,本地硬盘的 I/O 延时比网络低很多。

当有两个 Zone 写入成功的时候,就显示成功的状态,写完以后也有一个异步的冷备。相当于把我们原来单机的方案做了一个扩展,前面加了一个 HA,把数据转发到三个机器上面,第三份如果因为负载或者其它原因没写完它会在后台自己慢慢写,每个 Zone 的写入状态都会在数据库里面做标记,如果出现写入状态不一致的情况,我们有另外的机制来解决各种不同步的问题。

我们这里 DGit 实现比较轻量级,这个一致性是通过一个 Proxy 服务来做,如果我们在写入的时候发现只有一个节点写成功,另外的节点怎么写都写不动,这个 Push 就会失败,Proxy 会按需要定义有多少个节点操作完成才算成功,因为我们要保证每次推上来的数据都是高可用的,如果推上来的过程出问题导致不能 HA,但返回推送成功,这是不对的,会影响后续使用时的可用性。

如果只有有一个节点写成功了,就阻止提交;如果只有某一个节点失败了,程序会标记那个节点不可用,然后用另外一个定时任务把这个不同步的节点恢复过来,最差的情况是人工处理这个失败的节点,但一般不会存在这个情况,因为在这个架构上不会出现人工介入的情况,除非是出现一些目前没有预料到的奇葩情况。

回到刚才说的 I/O 问题,这样设计的限制就是本地硬盘的限制,没有其硬件上的限制。然后因为存储了多份,而且前面有 HA,能做负载均衡来提高并发性能,同时也没有单点故障,提高了可用性和可靠性高,保证数据更加安全,也能让运维效率提升,不用规划停机时间,最大化利用物理资源。

这个是我们自己做一个 DGit 的测试,在单机本地硬盘的情况下,Git Push 一个 2.58w commits, 12w+ objects, 386M 的大仓库是 22s,而用了 DGit 是 30 多秒,性能损失在可以接受的范围内,但是这个性能损失主要是在 Push 这种写操作上,而在 Pull / Fetch 等读相关的操作上面,并没有很大的影响。而且这个测试我们是在单个用户的情况下做测试,如果这个仓库本身就有好几十个用户在用,大部分是读操作的时候,性能会比单机的好很多。

交流提问

提问:你那个 DGit 具体实践能不能再介绍一下。我更多看起来是 MSTMST 的结构。

欧俊飞:有一点像,我们没有改造 Git 程序,是通过 Proxy 这个服务来记录仓库是不是正常,在 Push 时会通过 Git Hook 读取 Proxy 接口来获取其他仓库的状态,看是否同步。我们需要检查各个机器上的程序有没有写完整,还有就是处理并发操作的问题。

提问:你这里数据库跟系统有什么关系?你这里可以类比多个节点同步,系统的那一块,因为系统分布式,一直是数据库的同步,一直是分布式的难点。这个能不能搬过去用到那一块?

欧俊飞:会有一点区别,因为数据库,特别是关系型的数据库,会有事务相关的内容。但是 Git 操作没有数据库事务,Git 就是你提交完以后我们确定各个机器都是同一个版本。

提问:你们也当作事物来处理?一个节点没有成功都会回滚。

欧俊飞:这个回滚不需要自己做,Git 本身就是有这个机制。你在 Git 上有一个仓库,这个仓库很大,有一个 G,然后 Push 到 100M 时网络断了,这个时候 Git 虽然不会把这次 Push 的文件删掉,但是这些文件是临时文件不会归入仓库。

提问:我想问一下你那个 DGit 有单点故障?那个数据是很重要的,整个东西包括前面转发单点故障做好了,后面的那些是集群的方式来做吗?

欧俊飞:严格来说不叫集群。因为我们做同步以及跟前面入口那一块的程序逻辑不太一样。因为我们入口就像个 HAProxy,对于读写可以定义流量怎么转发,后面我们是通过一个 Hook 跟我们做 DGit 同步的服务把各个机器的信息是否都同步到做一个对比,然后这个对比完毕后,我们会在数据库里面写一个记录标记哪个节点写成功哪个节点写失败,然后下次进来的时候就会引导流量到有效的节点上。

提问:那个东西你们自己实现了一个代理?

欧俊飞:对。因为我们有三个协议,每个协议后端是通用的,所以有一个转换的逻辑。客户端向 Git 服务发请求,代理会判断当前的节点可用,然后把操作转发到可用的节点上。

提问:为什么你们数据中心不用后面那个呢?

欧俊飞:如果我们的服务器出问题了,我们宁可让用户的 Push 操作失败,也要确保数据的完整。这样就算我们服务真的出问题了,也不会有一个假象说 Push 成功了。

提问:您刚才说有两台成功的话是成功?最后一台会不会保证它是一致呢?

欧俊飞:会的。我们有一个健康状态的检查。

提问:这个过程当中如果有毒的操作会不会读到失败呢?

欧俊飞:不会,我们数据库有一个记录,记录对应的相关的节点。

提问:这个在哪里判断是?是在 HAproxy 判断吗?

欧俊飞:不是 HAproxy,我们的 Proxy 会转发用户到某一个节点,然后让这个节点来执行操作。有一个数据库来记录哪个仓库在哪个节点上。每个项目的仓库都会记录分别在哪些节点上。

提问:你说的数据库状态是针对某一个仓库的状态还是针对主机的状态。比如说我们有一百个人,不等于有一百个仓库。我的这个在 AB 两个节点,另外两个用户在 BC 两个节点,这个怎么控制的呢?第二个问题你怎么保证最终一致?你怎么控制因为 Git 的仓库量比较大,在同步过程当中你怎么保证他不把网卡跑马呢?

欧俊飞:这个有一部分是运维的问题,我们的运维对机器有一定的伸缩能力,根据负载来判断仓库存储和操作分发的节点。无论是这个仓库是谁的仓库,只要是在一台负载比较高的机器,这一台机器的优先级就会降低,就会引导用户到另外一台负载低机器上去。而 DGit 的作用就是提供了同时解决可用性和负载均衡问题的能力,根据机器实际的负载来均衡,而不是分开几台机器。

提问:你说目前正在升级中,请问有什么计划呢?

欧俊飞:DGit 这个事情目前我们有部分仓库已经迁到 DGit 上了,以前的仓库还是 NFS 的架构,有可能出问题。但是 DGit 的系统今年内会全部迁完。因为这个系统的改造比较大,为了避免出现大规模的问题,是逐步迁移的。虽然说原来 NFS 架构可能有一些卡顿或者是小毛病,但是总体来说是稳定的,没有丢数据的情况。

提问:你有没有了解过 Github 或者 Gitlab 他们的解决思路是怎么样呢?

欧俊飞:我们看过 GitHub 的解决方案,总体都是差不多,都是通过数据本地盘来做。分布式的问题是很依赖网络,但是对 Git 协议来说并不适合,因为每次 Push 的数据量都是全量,仓库的提交越多,I/O 就越多,所以最终方案还是在单机处理。

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180228G1JZMS00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券