Dragonfly 解决什么问题 #
假设要把一个 2 GiB 的镜像层下载到 100 台节点。如果所有节点都直接访问镜像仓库,仓库大约需要发送 200 GiB 数据,跨机房链路也可能被同时打满。
Dragonfly 的做法是:让一部分节点先获取数据,其他节点再从这些节点下载;下载过的节点还可以继续向别人提供数据。这就是 P2P 分发,可以理解为“节点互相帮忙下载”。
它主要改善的是相同内容的大规模重复下载,常见用途包括容器镜像、模型文件和软件包分发。它不能消除第一次获取新内容的成本,也不会自动加快镜像解压、容器初始化和应用启动。
如果文件很小、每次内容都不同,或者只有一台机器下载,P2P 带来的收益可能有限,甚至不及额外的调度和缓存开销。
本文介绍的是 Dragonfly P2P 分发项目,不是同名的 Redis 兼容数据库。
阅读顺序与版本 #
这一组文章分为三篇:
配置示例统一使用下面这组已发布版本,作为可核对的学习基线,不表示它们是最新版本或生产升级建议:
| 项目 | 版本 |
|---|---|
| Dragonfly Helm Chart | 1.7.0 |
| Manager / Scheduler | v2.5.0 |
| Client / dfinit | v1.4.0 |
它们的对应关系可在固定版本 Chart及其 values.yaml 中核对。阅读官方 next 文档时,要注意其中可能包含更新版本的行为。
先认识五个名称 #
| 名称 | 容易理解的说法 | 例子 |
|---|---|---|
| Origin,源站 | 原始数据所在的位置 | Harbor、其他 Registry、HTTP 文件服务器 |
| Task,任务 | Dragonfly 识别的一份待分发内容 | 某个镜像层的 blob 下载任务 |
| Piece,分片 | 任务中可单独传输的一段数据 | 文件中某个偏移和长度的数据 |
| Host,主机 | 运行 dfdaemon 的节点实例 | worker-1 上的客户端 |
| Peer,对等节点 | 某个 Host 对某个 Task 的参与记录 | worker-1 正在参与下载文件 A |
一台主机可以同时参与多个任务,所以 Host 与 Peer 不是一个概念。下载关系中的 Parent 是当前数据提供方,Child 是从它获取数据的一方;同一个节点可以一边下载,一边上传已经完成的分片。
组件各自做什么 #
dfdaemon:真正下载、缓存和上传数据 #
dfdaemon 是常驻客户端进程。在 Kubernetes 中,它通常随 Client DaemonSet 部署在工作节点上。
它主要做四件事:接收本机下载请求、从源站或其他节点获取分片、保存缓存、向其他节点上传分片。因此,节点不只是消费者,也可能成为数据提供者。
业务可以通过 dfget、HTTP 代理或集成接口使用它。dfget 是发起请求的命令行工具,后台真正承担缓存和 P2P 服务的是 dfdaemon。
Scheduler:告诉节点可以从谁那里下载 #
Scheduler 收集任务、主机和下载状态,为请求方选择合适的候选父节点。父节点失败或负载变化时,它还可以重新安排下载来源。
Scheduler 传递的是“节点信息、分片状态和调度结果”,不是文件内容。如果下载瓶颈在磁盘或节点出口,单纯增加 Scheduler 带宽通常解决不了问题。
Seed Peer:比较稳定的数据提供者 #
Seed Peer 不是另一套独立客户端,而是承担种子角色的 dfdaemon。Chart 中通常称为 seedClient,常用 StatefulSet 部署。
当某份内容第一次进入 P2P 网络,Seed Peer 可以先从源站获取数据,供其他节点下载。它适合安排在磁盘和带宽比较充足的位置,也常用于提前准备热门内容。
Seed 不必下载完整文件后才开始服务。某个分片准备好后,其他节点就可以获取它,让内容边下载、边扩散。
Manager:管理组件、配置和集群 #
Manager 负责管理 Scheduler 集群、Seed Peer 集群、组件注册与动态配置,还提供管理界面、API 和预热任务等能力。
它通常不参与每个分片的传输。已有客户端下载可能在 Manager 暂时不可用时继续工作,但新节点发现、配置更新和管理任务仍可能受影响,不能因此忽略 Manager 的可用性。
Manager 不是所有部署都必需。无 Manager 的轻量部署可使用本地动态配置连接 Scheduler,但这时需要自行管理发现和配置,也没有完整的集中管理能力。
dfinit:帮助接入容器运行时 #
dfinit 用于调整 containerd 等运行时配置,让镜像请求进入本机 dfdaemon。它是接入辅助程序,不负责日常分片调度或数据上传。
可以选择自动修改,也可以手工配置。是否重启运行时取决于版本和变更内容,不能在不了解影响的情况下直接启用所有节点自动修改。
把组件连起来看 #
图源:Dragonfly 官方 v2.5.0 架构文档。原图保留英文标注。
读图时先看三处:Manager 管配置,Scheduler 安排来源,Daemon 实际下载和提供数据;Seed Peer 也是以种子角色运行的 Daemon。图中箭头既有请求关系,也有控制关系,不能全部当作文件流向。这是组件关系图,不是协议清单:本文基线客户端的分片传输还涉及 TCP/QUIC,不能把图中的 “All Communication on gRPC Framework” 理解为所有文件字节都通过 gRPC 传输。
管理和调度部分通常叫控制面;真正传输文件字节的部分叫数据面。这一区分有助于定位瓶颈:注册和调度失败看控制面,文件传得慢则要继续检查数据来源、网络和磁盘。
完整部署通常还包含数据库和 Redis。数据库主要保存 Manager 的管理信息;Redis 用于相应缓存、作业和部分任务状态等功能。它们不是所有文件分片的存放位置,文件主要在 Peer/Seed 的本地存储或相应后端中。
一次文件下载经过哪些步骤 #
第一次下载:网络里还没有这份内容 #
假设节点 A 要下载文件 model.bin:
- 应用向本机 dfdaemon 发起请求。
- dfdaemon 根据请求信息识别任务,检查本地是否已经有可用数据。
- 本地没有时,联系 Scheduler 寻找下载来源。
- 若还没有可用 Peer,Scheduler 可以触发 Seed 回源;在相应策略允许时,普通 Peer 也可能回源。
- Seed 下载并准备好分片,节点 A 开始从它获取数据。
- 节点 A 校验、缓存分片,并把数据交给本机调用者;已经完成的分片也可供其他节点下载。
具体分支受任务类型、策略和失败处理影响,不是每次都必须经过同一个 Seed。
后续节点下载:复用已有内容 #
节点 B 请求相同内容时,Scheduler 可以返回 Seed、节点 A 等候选来源。节点 B 从可用来源获取分片,不必让源站再发送一份完整数据。
缓存复用也分层次:
- 本机命中:从本机 Dragonfly 缓存读取。
- 其他 Peer 命中:经过节点间网络传输,减少源站流量。
- 回源:没有合适数据源,或策略要求访问源站。
这三种情况成本不同,性能测试时应分别记录。
父节点故障:换来源,而不是一律重下整个文件 #
某个父节点中断时,客户端可以重试、改用其他候选节点,必要时请求重调度。符合策略时还可回源。
已完成分片可能继续复用,但最终能否成功仍取决于是否存在可用数据源。源站不可用、所有缓存又都缺失时,P2P 也不能凭空恢复内容。
为什么要分片 #
分片允许多个来源并行提供数据,也让下载中的节点尽早参与上传。某个分片失败时,可以只重试受影响部分。
例如,A 已有前两个分片,B 已有第三个分片,C 就可能分别向它们下载。它不必等待其中一个节点拥有完整文件。
代价是要管理更多状态、连接、校验和磁盘读写。分片越小不一定越快,并发越高也不一定越快;过多并发可能先压垮磁盘和节点网络。
调度为什么分两步 #
图源:Dragonfly 官方 v2.5.0 Scheduler 文档。图中的 Stage 1 是 Scheduler 推荐候选父节点,Stage 2 是客户端为具体分片选择来源。
Scheduler 先从整体视角筛选候选父节点,例如考虑位置、负载和任务关系;客户端再结合分片是否可用、连接情况和实时负载,为具体分片选择来源。
可以理解为:Scheduler 推荐几家合适的店,客户端再看哪一家现在有货、排队更短。 这比固定所有分片只从一个节点下载更灵活。
调度关系通常组织成 DAG,也就是没有环的有向图,避免出现 A 等 B、B 又等 A 的循环依赖。候选关系存在,不表示父节点一定拥有所有分片,下载前仍需检查实际可用数据。
Dragonfly 怎样判断“这是同一份内容” #
缓存能否共享,取决于任务标识。URL、相关下载参数、tag、application、分片配置等可能影响任务标识;具体以版本和请求入口为准。
两个常见问题是:
- 同一个文件使用不同临时签名 URL,结果被识别成多个任务,重复下载。
- 不同内容共用一个被过度简化的标识,结果错误复用缓存。
只有确认某些参数不影响内容时,才适合从标识计算中排除它们。URL 一直不变但内容会更新的文件,也需要明确的版本策略。不可变 URL、版本号或内容 digest 通常更容易管理。
镜像 blob 按 digest 识别内容时,可以更准确地复用相同镜像层;但鉴权和隔离仍需单独设计,不能把“内容相同”当作“所有人都有权读取”。
镜像加速并不是把整个镜像当一个文件 #
一次拉镜像通常涉及 manifest、配置和多个 layer blob。Dragonfly 可以按相应请求分发内容,多个镜像共享同一个层时,也可能复用该层缓存。
kubelet → containerd → 本机 dfdaemon → 其他 Peer / 源站
下载完成后
containerd → 校验内容、解压和准备镜像 → 启动容器
是否进入 P2P,取决于代理规则和请求类型。例如示例版本常见默认规则针对 blobs/sha256.*,不能看到经过代理就断言所有 manifest、认证请求也都进行了 P2P。
Dragonfly 主要减少重复传输;Nydus 等方案关注镜像格式和按需读取。两者可以结合,但“分发更快”和“不下载整个镜像就启动”是不同能力。
预热、缓存和持久化不要混为一谈 #
预热是提前下载内容,把成本从业务启动时挪到之前。预热成功后,普通缓存仍可能被 GC 回收;后续请求的任务标识和所属 P2P 集群不同,也可能无法复用。
普通任务缓存是可回收的加速数据,不应当作唯一存储。Dragonfly 还提供持久任务、持久缓存等能力,但它们的后端、副本和 TTL 语义不同,需要按功能单独配置。不能因为名称中有 persistent,就把它当成永不丢数据的对象存储。
下一篇从一个可控的 HTTP 文件开始验证这些过程:部署与使用。
图片来源与许可 #
本篇两张标注“官方”的图片直接取自 Dragonfly 官方文档仓库的 v2.5.0 目录,未修改原图。仓库快照固定为 991f992cbaf3d02daa49bb5b29a3e6160aa0fb72,以免后续更新造成图文版本混用。随文保留 Apache-2.0 许可证及图片来源记录。系列中的其他中文流程图为本文绘制。