Docker push 采用的是 "分层(Layered)" + "流式传输(Streaming)" + "断点续传/去重" 的机制。
以下是详细的技术原理解析:
1. 核心机制:分层处理 (Layer-by-Layer)
Docker 镜像是由多个只读的 Layer(层) 组成的。当你执行 docker push 时:
- 计算哈希值:Docker 客户端会先计算本地每一层的 SHA256 哈希值。
- 检查存在性(Mounting/Blob Check) :
- Docker 会向远程仓库发送请求:"我有这一层,哈希值是
sha256:abc...,你那里有吗?" - 如果仓库说"有",则跳过上传(这就是为什么第二次推送很快)。
- 如果仓库说"没有",才准备上传这一层。
- Docker 会向远程仓库发送请求:"我有这一层,哈希值是
- 逐层上传 :
- Docker 一次只处理一个 Layer。
- 它不会把所有层拼成一个大文件,而是分别处理每个 tar.gz 压缩包。
2. 内存管理:流式传输 (Streaming)
对于需要上传的那一层,Docker 绝不会将整个几 GB 的文件读入内存。
- 读取方式 :Docker 使用 IO Stream(流) 的方式读取本地磁盘上的 Layer 文件。
- 缓冲区(Buffer):它会在内存中开辟一个很小的缓冲区(通常是几 KB 到几 MB,取决于具体实现和网络块大小),从磁盘读一点数据,通过网络发一点数据,然后释放这块内存,再读下一块。
- 结果 :无论你的镜像是 100MB 还是 10GB,
docker push进程占用的内存通常非常稳定且较低(通常在几百 MB 以内,主要用于元数据管理和加密开销,而不是存储镜像数据本身)。
3. 数据传输过程图解
bash
[本地磁盘] [Docker Client 内存] [网络网卡] [远程 Registry]
Layer 1 (tar.gz) --(小块读取)--> [Buffer A] --(发送)--> TCP Stream --(接收)--> 存储 Layer 1
Layer 2 (tar.gz) --(小块读取)--> [Buffer B] --(发送)--> TCP Stream --(接收)--> 存储 Layer 2
...
Layer N (tar.gz) --(小块读取)--> [Buffer N] --(发送)--> TCP Stream --(接收)--> 存储 Layer N
4. 为什么有时候感觉内存占用高?
虽然镜像数据本身不占内存,但在以下情况内存占用可能会短暂升高:
- 压缩/解压开销:如果 Layer 在本地是未压缩状态,推送时需要实时压缩(gzip/zstd),这会消耗 CPU 和少量内存用于压缩算法上下文。
- 加密/签名:如果使用 Content Trust 或加密镜像,加密操作需要内存。
- 并发推送:Docker 默认可能会并发推送多个层(取决于配置),每个层都有一个独立的缓冲区和连接,这会线性增加内存使用,但依然远小于镜像总大小。
- Go Runtime GC:Docker 是用 Go 语言写的,Go 的垃圾回收机制在某些高负载下可能会暂时占用较多内存,但会自动释放。