IPFS使用有向无环图(DAG)作为存储的数据结构,更具体一点是DAG中的Merkle DAG,每个节点都有一个唯一的标识符,该标识符是节点内容的哈希,即CID。在文件上传到IPFS的过程中发生的实际上是Merkle DAG表示的构建过程。
文件存储流程
在IPFS中,信息可以存储进IPFS系统中的块(block)里,这些块可以存储至多256kb的数据,它们还可以链接其他IPFS块。
也就是说,存储小于256kb的文件时,只需将这个文件放进一个块内就可以了。而大于256kb的文件会被分成多个256kb然后放进块中,之后IPFS将创建一个空块,该块将链接到文件的所有其他部分。这个空块就类似于一个大信封,里面会涵盖整个文件的所有部分。
系统会给同一个文件的每一个块计算一次哈希值a,所有块的哈希值a计算完毕之后,会将所有的哈希值a拼凑成一个数组b,再计算一次哈希值,从而得到最终的哈希值c。最后把最终的哈希值c和原文件捆绑起来,组成一个对象,从而形成一个索引结构d。把块和最终的索引结构d上传至IPFS节点,文件便同步到网络了。
此外,还有一种小于1kb的小文件,如果这些小文件也单独放进一个块里的话,也会造成一定的浪费。于是,IPFS把小于1kb的数据内容直接和哈希索引放在一起上传给IPFS节点,不会再额外的占用一个块。
在Merkle DAG中,所有内容都具有CID(即包含哈希值的结构)。如果该文件与其他几个文件位于文件夹中,这些文件也将具有CID。 该文件夹的CID将是来自其下文件(即文件夹的内容)的CID的哈希值。 反过来,这些文件由块组成,并且每个块都有一个CID。
Merkle DAG的好处
首先,拆分为多个块意味着文件的不同部分可以来自不同的来源并可以快速进行身份验证。 (类似BitTorrent可以一次从多个对等方获取文件)
其次,将内容分块后,如果有两个相似的文件,它们可以共享Merkle DAG的一部分,即,不同Merkle DAG的一部分可以引用相同的数据子集。这可以使传输大型数据集的版本更加有效,因为只需要传输新的或已更改的部分,而不必每次都创建全新的文件。
Merkle DAG构建过程详解
文件上传时将文件添加到IPFS的仓库中,上传的流程可以如下图所示,生成默克尔DAG的结构,生成的结构有两种Layout:balanced和trickle的。这里介绍默认的balanced结构,首先生成root作为根节点,然后将文件分割,默认按照256KB大小读取一个chunk,生成叶子节点,依次生成node1,node2,root节点会有Link指向挂在root节点的叶子节点node1和node2。root节点下面能够Link的叶子节点数量是有限的,IPFS中默认设置的是174个(定义的Link的总的大小是8KB,每个Link的大小是34 + 8 + 5【sha256 multihash + size + no name + protobuf framing】,默认的Link的个数为8192/47约等于174)。
分块小于174个时的默克尔DAG如下图所示,超过174个后则会新创建一个new root节点,并Link到old root,新的chunk作为node3(这里用node3简约了,实际上是第175个节点)被new root直接Link。
分块大于174个时的默克尔DAG当继续有新的chunk添加时,则会生成node34作为node3和node4的父节点,node34含有两个Link分别链接到node3和node4。
更多分块时的默克尔DAGIPFS在init的时候会生成.ipfs目录,如下图所示,其中blocks则为文件块存储的目录,datastore为leveldb数据库,其中存储了文件系统的根哈希等,存储相关的配置关联在.ipfs目录下面的config文件。
存储流程经过上面的步骤,文件已经切块并转化成Merkle DAG的结构,接下来详细介绍每个块是如何进行存储的流程。
- 如下图所示,一个Block存储时,首先由dagService(实现了DAGService接口)调用Add进行添加;
- 之后由blockService(实现了BlockService接口)调用AddBlock添加该Block;
- 再调用arccache的Put,arccache是对存储的Block做arc策略的缓存;
- 再之后由VerifBS调用Put进行存储,VerifyBS主要对CID的合法性进行校验,合法则进行Put;
- 接着blockstore(实现了Blockstore接口)调用Put进行存储,Put函数中会对CID进行转化,调用dshelp的CidToDsKey方法将CID转化成存储的Key;
- 再接着调用keytransform.Datastore的Put,Put函数中会将前缀拼上,这时Key加上了前缀/blocks;
- 然后调用measure的Put函数,measure是对mount的封装;
- 之后调用mount的Put函数,mount和IPFS的config配置文件中结构对应,根据key去查找对应的datastore,由于前缀是/blocks则可以找到对应的measure;
- 调用该measure的Put函数;
- 最后调用flatfs的Put函数,由Put函数调用doPut最终调用encode函数将完整的block写入的目录指定为/home/test/.ipfs/blocks/WD,其中WD来自于blocks/CIQFSQATUBIEIFDECKTNGHOKPOEE7WUPM5NNNSJCCDROMM6YHEKTWDY中的倒数第三第二个字符。这样该Block则写入了该目录下面的文件中。
文件存储过程中有多个Datastore进行了组合和封装,每个Datastore功能比较单一,例如arccache只做Block的缓存,VerifBS只做CID的校验,这样做的好处是每个组件功能明确,不好的地方在于组合太多,调用深度太深,加上内部都是用interface,好几个组件都实现了该interface,不便于阅读。
参考:
https://docs.ipfs.io/concepts/how-ipfs-works/#directed-acyclic-graphs-dags
https://www.cnblogs.com/Bin-DuS/p/9754458.html
https://bihu.com/article/1996734810
https://www.hyperchain.cn/news/262.html
网友评论