技术

IPFS数据之旅(二):名正才能传顺

我们书接上回。

情景一中,小A想给小B传一个文件,大致过程如下:

  • 本地添加文件;
  • 把文件的hash给小B;
  • 小B根据文件hash在IPFS网络中得到这个文件。

这里有意不提上传和下载,点对点的系统并不是上传文件到服务器,再由别人下载。在IPFS中我们只能把文件上传到自己的IPFS节点或public gateway(公共网关),别人通过哈希值从IPFS网络中下载。

回忆一下前面提到的IPFS Stack,这次主要涉及到IPLD对象是如何管理,在每个节点是如何存储的。

 

添加文件

我们向自己的IPFS添加文件的过程如下:

 

1. 文件拆分并写入IPFS基础数据结构dag(有向无环图),dag不仅用在IPFS中也用在Filecoin中,参考下文:深度解读IPFS/Filecoin 2018 update

2. 把dag数据根据系统配置写入IPFS底层blocks存储;

3. Flush mfs(Mutable Filesystem)的root目录,MFS是一个建立在IPFS上面的虚拟文件系统,提供类似unix文件系统的API;

4. 递归Pin上面flush过的root目录及其所有文件,并把状态写入底层blockstore存储。

flush() 方法是用来刷新缓冲区的,即将缓冲区中的数据立刻写入文件,同时清空缓冲区,不需要是被动的等待输出缓冲区写入。 一般情况下,文件关闭后会自动刷新缓冲区,但有时你需要在关闭前刷新它,这时就可以使用 flush() 方法。

做存储的小伙伴肯定这里会问了,我存1M的数据,肯定要比1M多啊,浪费的空间怎么办?IPFS社区有实验特性Filestore,可以避免浪费多余的硬盘空间。

 

文件拆分并生成dag

bamvor@ipfs-ci-and-test:~/works/ipfs_demo$ export IPFS_PATH=~/.ipfs-private

bamvor@ipfs-ci-and-test:~/works/ipfs_demo$ export LIBP2P_FORCE_PNET=1

bamvor@ipfs-ci-and-test:~/works/ipfs_demo$ ipfs add –cid-version 1 –wrap-with-directory test_data

added zdj7WkayJ4Y7QmFsHg6pQQ528J7bqSZpGfUk8WfivrNFJyUr6 test_data

added zdj7WXi97o1NcWTq9bVw9BhJLMxEVKjUVbXP1U3V2TBHswPvr

 512.00 KiB / 512.00 KiB [======================================================================================================================] 100.00%

graphmd可以把dag用图形化方式显示,例如

https://ipfs.io/docs/examples/example-viewer/example#../graphmd/README.md

 

 

文件拆分策略

文件拆分方式可以通过–chunker指定。有固定大小和rabin两种方式。如果不指定,默认是256k一个chunk。

dag (有向无环图)的类型

ipfs的object来自git。ipfs支持两种dag,一种是balanced dag,适合随机访问;另一个种是trickledag,适合顺序访问。默认情况下ipfs使用balanced dag。ipfs添加文件时可以用–trickle或-t表示生成trickledag。下面是balanced dag插入数据的过程。

 

  

 

 

trickledag是这样的:

 

 

 

 

 

 

添加新的子树:

 

 

 

需要注意的是使用不同dag或不同的拆分方式都会生成不同的哈希值,也就是对ipfs来说,是不同的文件。

 

CID版本

CID包括multibase,multicodec和multihash。

默认是cidv0,社区正在做cidv1的工作。有个小工具可以将cid解析成人类可读的数据。

 

dag写入底层存储:

 

ipfs默认使用两种存储,在~/. ipfs/blocks使用flat fs,在~/. ipfs/datastore使用leveldb。后者在后面提到pin的时候仍然会提到,这里先跳过。在上面的默认配置中,ipfs会使用base32哈希值的倒数第二和倒数第三位索引。

bamvor@ipfs-ci-and-test:~/works/ipfs_demo$ hexdump  ~/.ipfs-private/blocks/RX/AFKREIHCUSVW2OVNVS7VX35P33XIYNO6AF4TXB7R7CNAF5YZSGULUB3RXQ.data | head

0000000 38b9 a7c4 061f b138 6dca b81a 5aeb a304

0000010 99e6 2ce7 600b d190 f5da 0695 7436 efd3

0000020 97bd 357a 9fa6 e8ab 422d a7d6 051c 4ba1

0000030 6be3 ad89 f3d4 f6d4 72a4 0b94 5a1e 8f61

0000040 2953 1ad3 a5f4 3f30 477a a907 bd65 b6fa

0000050 9658 4883 54fd 318b 6e51 85c3 1772 65d5

0000060 7c6a 3202 2e4b bc48 54b6 1aca 927e 6d7c

0000070 c34d 522e faa1 3883 9bda f5bf d6e7 e1d1

0000080 5d90 4548 7cf2 5c4d e501 9a8e a747 0bdd

0000090 fa7d a186 1565 5839 93b7 0ad3 2c18 bd48

bamvor@ipfs-ci-and-test:~/works/ipfs_demo$ hexdump test_data | head

0000000 38b9 a7c4 061f b138 6dca b81a 5aeb a304

0000010 99e6 2ce7 600b d190 f5da 0695 7436 efd3

0000020 97bd 357a 9fa6 e8ab 422d a7d6 051c 4ba1

0000030 6be3 ad89 f3d4 f6d4 72a4 0b94 5a1e 8f61

0000040 2953 1ad3 a5f4 3f30 477a a907 bd65 b6fa

0000050 9658 4883 54fd 318b 6e51 85c3 1772 65d5

0000060 7c6a 3202 2e4b bc48 54b6 1aca 927e 6d7c

0000070 c34d 522e faa1 3883 9bda f5bf d6e7 e1d1

0000080 5d90 4548 7cf2 5c4d e501 9a8e a747 0bdd

0000090 fa7d a186 1565 5839 93b7 0ad3 2c18 bd48

bamvor@ipfs-ci-and-test:~/works/ipfs_demo$ cat  ~/.ipfs-private/blocks/KE/AFKREIBOBL5SRHO6FXSR2KPV7TCNEQFPIL2CHLI7226YE2446Q5A7KEKEY.data

welcom to ipfsbit.com

前面我们通过IPFS add添加了”test_data“文件

上文是拿“welcom to ipfsbit.com“这一句话举个栗子,通过flat fs方式存储这句话返回的哈希值是”bamvor@ipfs-ci-and-test:~/works/ipfs_demo$ cat  ~/.ipfs-private/blocks/KE/AFKREIBOBL5SRHO6FXSR2KPV7TCNEQFPIL2CHLI7226YE2446Q5A7KEKEY.data“而图中的KE就是倒数第二和倒数第三位的索引。

 

Append(追加数据)

IPFS做为内容寻址的文件系统。Append数据之后,只有被修改的chunk会被生成新的hash,原有chunk仍然可以用原有hash访问。例如我们在上面的测试数据test_data后面追加”welcom to ipfsbit.com”字符串。由于原本的test_data是512k,ipfs默认是256k一个chunk,正好是两个chunk。新增的数据会在一个新建的chunk里面。我们可以用ipfs object get看到前面两个chunk数据没有变化。只是最后一个chunk包含我们新增的字符串。

bamvor@ipfs-ci-and-test:~/works/ipfs_demo$ echo “welcom to ipfsbit.com” >> test_data

bamvor@ipfs-ci-and-test:~/works/ipfs_demo$ ipfs add –cid-version 1 –wrap-with-directory test_data

added zdj7We9xdpUKDjQxCXYJ2DMg2YJD87NmuybjoTDxptbitB2cd test_data

added zdj7WfuHAfPSdyS2XL7avfzYudTaYT83ckG1ku1gdv8H96sg7

 512.02 KiB / 512.02 KiB [======================================================================================================================] 100.00%

bamvor@ipfs-ci-and-test:~/works/ipfs_demo$ ipfs object get zdj7WkayJ4Y7QmFsHg6pQQ528J7bqSZpGfUk8WfivrNFJyUr6

{“Links”:[{“Name”:””,”Hash”:”zb2rhmtzy5H4y7UQxZ3pzf3FE6BAE5PAeLT3yjTrKX5RCcBvj”,”Size”:262144},{“Name”:””,”Hash”:”zb2rhaMcpAWL6rvYay5HX7wsuj6gKnZTPk2ToF5xnf7vtnf6G”,”Size”:262144}],”Data”:”\u0008\u0002\u0018\ufffd\ufffd  \ufffd\ufffd\u0010 \ufffd\ufffd\u0010″}

bamvor@ipfs-ci-and-test:~/works/ipfs_demo$ ipfs object get zdj7We9xdpUKDjQxCXYJ2DMg2YJD87NmuybjoTDxptbitB2cd

{“Links”:[{“Name”:””,”Hash”:”zb2rhmtzy5H4y7UQxZ3pzf3FE6BAE5PAeLT3yjTrKX5RCcBvj”,”Size”:262144},{“Name”:””,”Hash”:”zb2rhaMcpAWL6rvYay5HX7wsuj6gKnZTPk2ToF5xnf7vtnf6G”,”Size”:262144},{“Name”:””,”Hash”:”zb2rhZk1fBPjs9ho7beGQ2itVcETXJ7RYkJmsWpeyXGhvLu8y”,”Size”:22}],”Data”:”\u0008\u0002\u0018\ufffd\ufffd  \ufffd\ufffd\u0010 \ufffd\ufffd\u0010 \u0016″}

bamvor@ipfs-ci-and-test:~/works/ipfs_demo$ ipfs get zb2rhZk1fBPjs9ho7beGQ2itVcETXJ7RYkJmsWpeyXGhvLu8y

Saving file(s) to zb2rhZk1fBPjs9ho7beGQ2itVcETXJ7RYkJmsWpeyXGhvLu8y

 22 B / 22 B [===============================================================================================================================] 100.00% 0s

bamvor@ipfs-ci-and-test:~/works/ipfs_demo$ cat zb2rhZk1fBPjs9ho7beGQ2itVcETXJ7RYkJmsWpeyXGhvLu8y

welcom to ipfsbit.com

备注

这里面没有提到cache,默认是ARCCache。如果配置了BloomFilter,也会开启BloomFilter。

上面流程只考虑了单独添加一个文件的情况,没有考虑符号链接,目录的情况。