作者简介
王振威,CODING 创始团队成员之一,多年系统软件开发经验,擅长 Linux,Golang,Java,Ruby,Docker 等技术领域,近两年来一直在 CODING 从事系统架构和运维工作
前言
最近 Google 发布了一篇文章,描述了对 Git 的一个传输协议的更新,引起了国内技术圈的不小规模的轰动(相关文章请自行百度“Git v2 性能提升”)。 很多技术圈的朋友也在转载这个新闻,那至于性能改进有多大,里面的细节是什么呢?事实上这次改动只在极端情况下有性能提升,绝大多数情况下,用户感受不到性能的提升。很多不明所以的转发大概是因为 Google 的品牌效应吧 :)
Git 是什么?
为了讲清楚 why,我们先来简单介绍一下 Git 相关的协议。如果你还不了解 Git,想了解更多内容,可参考其官方网站:http://git-scm.com/ . 也可来 https://coding.net/help/doc/git 这里了解如何在国内使用优质快速的 Git 托管服务。
Git 传输协议
Git 常见的有三种协议,SSH,HTTP(S),Git,使用最广泛的是前两种。
让我们来看一下, HTTP(S) 和 SSH 协议的使用示例
git clone https://git.coding.net/wzw/coding-demo.git
Cloning into 'coding-demo'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
git clone git@git.coding.net:wzw/coding-demo.git
Cloning into 'coding-demo'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (3/3), done.
可以看到,对于全新 clone 来讲两者基本上的过程是一模一样的。
事实上, Git 底层对于各种应用层协议的底层处理是一致的,不管是 HTTP(S) 还是 SSH 还是 Git 协议。
让我们来进一步看一下, Git 在传输过程中都做了什么。
GIT_TRACE=1 GIT_TRACE_PACKET=1 git clone https://git.coding.net/wzw/coding-demo.git
17:48:21.767799 git.c:344 trace: built-in: git 'clone' 'https://git.coding.net/wzw/coding-demo.git'
Cloning into 'coding-demo'...
17:48:21.797959 run-command.c:626 trace: run_command: 'git-remote-https' 'origin' 'https://git.coding.net/wzw/coding-demo.git'
17:48:22.278880 pkt-line.c:80 packet: git< # service=git-upload-pack
17:48:22.279390 pkt-line.c:80 packet: git< 0000
17:48:22.279405 pkt-line.c:80 packet: git< fdacba1d541c75bd48f2cd742ee18f77ea3517a1 HEAD\0multi_ack thin-pack side-band side-band-64k ofs-delta shallow deepen-since deepen-not deepen-relative no-progress include-tag multi_ack_detailed no-done symref=HEAD:refs/heads/master agent=git/2.15.0
17:48:22.279419 pkt-line.c:80 packet: git< fdacba1d541c75bd48f2cd742ee18f77ea3517a1 refs/heads/master
17:48:22.279431 pkt-line.c:80 packet:
好,基础知识补充完毕,有没有发现火爆的区块链在技术层面上跟 Git 的存储是有相似之处的 :)
在 Clone 过程中,服务器端首先会推荐给客户端一些 ref 列表,这也是 Git v2 协议号称的性能改进的地方,后文有解释。
像这样:
17:49:19.772436 pkt-line.c:80 packet: clone< fdacba1d541c75bd48f2cd742ee18f77ea3517a1 refs/heads/master
17:49:19.772527 pkt-line.c:80 packet: clone< 1536ad10fc0a188c50680932ca191c8da46938c4 refs/heads/test-abc
17:49:19.772549 pkt-line.c:80 packet: clone< 1536ad10fc0a188c50680932ca191c8da46938c4 refs/heads/test-bcd
17:49:19.772566 pkt-line.c:80 packet: clone< 30eb4b0d813c662c4d7e87c4d3b4cf561e544f8e refs/tags/v1.0
17:49:19.772863 pkt-line.c:80 packet: clone< 1536ad10fc0a188c50680932ca191c8da46938c4 refs/tags/v1.0^{}
很显然,上文中的 40 位16进制数字就是对应后面的 ref 指向的对象 ID。
而客户端,只需要依据自己感兴趣的 ref 和自己本地已经存在的对象库(对于 pull 和 fetch 来讲,本地有对象库,对于 clone 来讲本地还没有对象库,那么他就是需要所有的感兴趣的对象)。
在客户端计算完毕自己感兴趣的对象列表后,会用 want 指令告诉远端服务器。
17:49:19.776185 pkt-line.c:80 packet: clone> want fdacba1d541c75bd48f2cd742ee18f77ea3517a1 multi_ack_detailed side-band-64k thin-pack ofs-delta deepen-since deepen-not agent=git/2.15.1.(Apple.Git-101)
17:49:19.776215 pkt-line.c:80 packet: clone> want fdacba1d541c75bd48f2cd742ee18f77ea3517a1
17:49:19.776224 pkt-line.c:80 packet: clone> want 1536ad10fc0a188c50680932ca191c8da46938c4
17:49:19.776232 pkt-line.c:80 packet: clone> want 1536ad10fc0a188c50680932ca191c8da46938c4
17:49:19.776239 pkt-line.c:80 packet: clone> want 30eb4b0d813c662c4d7e87c4d3b4cf561e544f8e
如果客户端执行的是 pull 或者 fetch ,他还会告诉远端自己已经有了什么对象(在文章的后面,我们会补充一段专门说明此点)。
远端服务器会根据客户端想要的对象以及客户端已经有的对象并对比自身的对象库和对象依赖关系,将客户端必须的对象整理起来并打包压缩传给客户端。
客户端收到对象包后,解包并校验对象,并更新引用的对应指向。
Google 在 Protocol version 2 做了什么
完整的 version 2 的协议说明在这里: https://www.kernel.org/pub/software/scm/git/docs/technical/protocol-v2.html
这里我们对其做的主要改动做些说明,主要有三点:
服务端引用过滤
新特性的易扩展性升级(例如可声明想要什么 ref)
简化的客户端 HTTP 协议处理
被很多标题党夸大其词的主要是其第一点:服务端引用过滤。
Google 官方的博客中对此段的描述是这样的:
The main motivation for the new protocol was to enable server sid