IPFS共享更新文件的效率优化:仅传输变更块的方案问询
更新于2019年10月30日:
- 关于IPFS中提升更新文件共享效率、减少块重复的类git-diff功能,社区已有相关需求讨论
- 关于IPFS块级文件复制功能的细节,也可在社区讨论中找到更多信息
先给你还原一下核心场景:
- 用户A用
ipfs add file.txt上传了一个1GB的文件 - 用户B把文件拉到本地后,用户A只修改了文件里一个字符,重新上传后,用户B却要重新下载整个1GB文件,而不是仅更新那一小部分
你肯定会问:IPFS能不能像git pull那样只拉取变更部分?毕竟Git用了增量压缩,IPFS有没有类似的机制?
验证实验
为了确认这个问题,我做了个小测试:
步骤1:初始上传与下载
用户A操作:
$ fallocate -l 1G gentoo_root.img $ ipfs add gentoo_root.img 920.75 MB / 1024.00 MB [========================================>----] 89.92% added QmdiETTY5fiwTkJeERbWAbPKtzcyjzMEJTJJosrqo2qKNm gentoo_root.img
用户B操作:
$ ipfs get QmdiETTY5fiwTkJeERbWAbPKtzcyjzMEJTJJosrqo2qKNm Saving file(s) to QmdiETTY5fiwTkJeERbWAbPKtzcyjzMEJTJJosrqo2qKNm 1.00 GB / 1.00 GB [==================================] 100.00% 49s
步骤2:修改后重新上传与下载
用户A修改并重新上传:
$ echo 'hello' >> gentoo_root.img $ ipfs add gentoo_root.img # 这里节点显示推送完整1GB文件,耗时1小时,而非仅更新变更块 32.75 MB / 1.00 GB [=>---------------------------------------] 3.20% 1h3m34s added Qmew8yVjNzs2r54Ti6R64W9psxYFd16X3yNY28gZS4YeM3 gentoo_root.img
用户B获取新文件:
# 还是要下载完整1GB文件 ipfs get Qmew8yVjNzs2r54Ti6R64W9psxYFd16X3yNY28gZS4YeM3 [sudo] password for alper: Saving file(s) to Qmew8yVjNzs2r54Ti6R64W9psxYFd16X3yNY28gZS4YeM3 1.00 GB / 1.00 GB [=========================] 100.00% 45s
核心问题
这种场景下,怎么避免重新共享完整文件,只传输变更的数据块?最优方案是什么?
另外还有个小问题:同一节点执行ipfs cat <hash>时,每次都会重新下载相同的哈希:
$ ipfs cat Qmew8yVjNzs2r54Ti6R64W9psxYFd16X3yNY28gZS4YeM3 212.46 MB / 1.00 GB [===========>---------------------------------------------] 20.75% 1m48s $ ipfs cat Qmew8yVjNzs2r54Ti6R64W9psxYFd16X3yNY28gZS4YeM3 212.46 MB / 1.00 GB [===========>---------------------------------------------] 20.75% 1m48s
仓库增量分析
我还对比了原文件和更新后文件对仓库大小的影响:
- 先创建100MB的
file.txt,初始仓库状态:
NumObjects: 5303 RepoSize: 181351841 StorageMax: 10000000000 RepoPath: /home/alper/.ipfs Version: fs-repo@6
- 添加并固定文件后:
$ ipfs add file.txt added QmZ33LSByGsKQS8YRW4yKjXLUam2cPP2V2g4PVPVwymY16 file.txt $ ipfs pin add QmZ33LSByGsKQS8YRW4yKjXLUam2cPP2V2g4PVPVwymY16
- 仓库状态变为:
$ ipfs repo stat NumObjects: 5307 RepoSize: 181389824 StorageMax: 10000000000 RepoPath: /home/alper/.ipfs Version: fs-repo@6
对象数增加4,仓库大小增加了37983字节。
- 执行
echo 'a' >> file.txt后再次添加并固定,发现仓库大小的增量和首次添加时完全一致。
解决方案与最优实践
1. 理解IPFS的块级复用机制
首先要明确:IPFS默认把文件分割成固定大小的块(默认256KB),每个块用内容哈希标识。只要块内容不变,哈希就不变——也就是说,重新上传修改后的文件时,IPFS会自动复用本地或网络中已存在的块,只上传变化的块。
你实验中看到的进度条显示整个文件大小,其实是个“视觉错觉”:进度条统计的是文件总大小,但实际传输的只有变化的块。如果你查看ipfs repo stat的前后变化,会发现仓库大小只增加了几KB(新增内容的块+新的根节点块),而不是1GB。如果你的节点真的上传了整个1GB,可能是缓存或配置问题,可以检查IPFS的日志确认实际传输量。
2. 使用ipfs patch实现增量更新
IPFS提供了patch子命令,可以直接修改已有的IPFS文件对象,不需要重新上传整个文件:
- 在文件末尾添加内容:
ipfs patch append <原文件哈希> <新增内容文件> - 替换文件中的某段内容:
ipfs patch replace <原文件哈希> <起始偏移> <长度> <替换内容文件>
这样生成的新文件对象会引用原文件的大部分块,只包含变化的部分。用户B获取新文件时,只会下载新增或修改的块,不需要重新下载整个文件。
3. 结合Git与IPFS
既然你熟悉Git的增量更新,可以把Git仓库托管到IPFS中:
- 每次提交后,用
ipfs add上传Git仓库的最新版本 - 用户B可以先拉取IPFS中的仓库,再用
git pull获取增量更新,结合两者的优势 - 也可以用专门的工具让Git直接把IPFS作为远程仓库,实现无缝的增量传输
4. 调整块大小适配修改场景
默认块大小是256KB,如果你的修改通常很小(比如只改几个字符),可以尝试减小块大小(比如64KB),这样修改只会影响更少的块。但要注意:块太小会导致块数量激增,增加节点的存储和索引负担,需要根据实际场景权衡。
5. 解决ipfs cat重复下载的问题
ipfs cat默认不会自动固定文件到本地仓库,所以每次运行都会从网络重新获取。解决方法很简单:先执行ipfs pin add <文件哈希>,把文件固定到本地,后续再用ipfs cat就会直接读取本地存储,不会重新下载了。
内容的提问来源于stack exchange,提问作者alper




