第23章 【IPFS一问一答】IPFS底层架构解析之文件层

23 IPFS底层架构解析之文件层

IPFS在Merkle DAG上对版本化的文件系统进行了建模。这个模型与Git类似:

  1. block:一个可变大小的数据块
  2. list:一个块或其它列表的集合
  3. tree:块、列表或其它树的集合
  4. commit:树在版本历史记录中的一个快照

23.1 文件对象:blob

文件对象被存储时一般都要进行数据分割,每块大小为256kb,但如果是小文件(< 256kb)就不需要做分割处理,直接将其作为一个数据单元进行存储,存储的类型就是blob对象。大文件切割后的数据单元也是blob对象。

每个blob对象包含的是一个可寻址的数据单元,如下所示::

{
    "data":"some data here", 
    //blobs 无links
}

23.2 文件对象:list

文件大于256kb的大文件在ipfs存储时,需要进行数据分割。list对象由几个blob对象组成,其中里面的blob可能有重复。lists对象包含有序的blob和list对象。这些有序的blob和list会有自己的link,link对应了他们的数据信息。

如下结构标示了一个lists结构:

{
    "data":["blob","list","blob"],  //lists有一个对象类型的数组作为数据
    "links":[
        {
            "hash":"XLYkgq61DYaq8Nhkcqy7LcnSA7dSHQ78x",
            "size":189458
        },
        {
            "hash":"XLHBNsgoepUDKL8dkd9Hesa5io9sdxi7n",
            "size":19442
        },
        {
            "hash":"XLWVQKJII8v7dggkfdhHSFlkaw9yjs7dj",
            "size":5286
        } // 在links中lists是没有名字的
    ]
}

23.3 文件对象:tree

在IPFS中,Tree对象与Git的tree类似,代表一个目录,或者一个名字到哈希值的映射表,哈希值表示blob,list,其他的tree,或commit,结构如下:

{
    "data":["blob","list","blob"], // trees有一个对象类型的数组作为数据
    "links":[
        {
            "hash":"XLYkgq61DYaq8Nhkcqy7LcnSA7dSHQ78x",
            "name":"less",
            "size":189458
        },
        {
            "hash":"XLHBNsgoepUDKL8dkd9Hesa5io9sdxi7n",
            "name":"script",
            "size":19442
        },
        {
            "hash":"XLWVQKJII8v7dggkfdhHSFlkaw9yjs7dj",
            "name":"template",
            "size":5286
        } // trees是没有名字的
    ]
}

23.4 文件对象:commit

在IPFS中,commit对象代表任何对象在版本历史记录中的一个快照,它与Git的commit也非常类似,但它可以指向任何类型的对象。

{
"data": {
    "type": "tree",
    "date": "2014-09-20 12:44:06Z",
    "message": "This is a commit message."
}, "links": [
{ "hash": "XLa1qMBKiSEEDhojb9FFZ4tEvLf7FEQdhdU",
  "name": "parent", "size": 25309 },
{ "hash": "XLGw74KAy9junbh28x7ccWov9inu1Vo7pnX",
  "name": "object", "size": 5198 },
{ "hash": "XLF2ipQ4jD3UdeX5xp1KBgeHRhemUtaA8Vm",
  "name": "author", "size": 109 }
  ]
}
> ipfs file-cat <ccc111-hash> --json
{
  "data": {
    "type": "tree",
    "date": "2014-09-20 12:44:06Z",
    "message": "This is a commit message."
}, "links": [
    { "hash": "<ccc000-hash>",
      "name": "parent", "size": 25309 },
    { "hash": "<ttt111-hash>",
      "name": "object", "size": 5198 },
    { "hash": "<aaa111-hash>",
      "name": "author", "size": 109 }
  ]
}

> ipfs file-cat <ttt111-hash> --json
{
  "data": ["tree", "tree", "blob"],
  "links": [
    { "hash": "<ttt222-hash>",
      "name": "ttt222-name", "size": 1234 },
    { "hash": "<ttt333-hash>",
      "name": "ttt333-name", "size": 3456 },
    { "hash": "<bbb222-hash>",
      "name": "bbb222-name", "size": 22 }
 ] 
}

> ipfs file-cat <bbb222-hash> --json
{
  "data": "blob222 data",
"links": [] }

23.5 版本控制

IPFS的版本控制是在Git的基础上实现的,Git版本控制在之前的章节有讲。commit对象代表着一个对象在历史版本中的一个特定快照。两个不同的commit之间相互比较对象数据,可以揭露出两个不同版本文件系统的区别。IPFS可以实现Git版本控制工具的所有功能,同时也可以兼容Git。

兼容的原因可能是:

23.6 文件系统路径

在Merkle DAG中,我们可以看到,IPFS对象可以使用字符串路径API来遍历。IPFS文件对象是特意设计的,为的是让挂载IPFS到UNIX文件系统更加简单。

23.7 将文件分割成list和blob

版本控制和分发大文件最主要的挑战:找到一个正确的方法来将它们分隔成独立的块。与其认为IPFS可以为每个不同类型的文件提供正确的分隔方法,不如说IPFS提供了以下的几个可选选择:

使用Rabin Fingerprints指纹算法来定义比较合适的块边界。 使用rsync和rolling-checksum算法,来检测块在版本之间的改变。 允许用户设定文件大小而调整数据块的分割策略。

23.8 路径查找性能

基于路径的访问需要遍历整个对象图,检索每个对象需要在DHT中查找它的Key值,连接到对等点并检索对应的数据块。这是一笔相当大的性能开销,特别是在查找的路径具有多个路径时。IPFS充分考虑了这一点,并设计了如下的方式来缓解:

例如,对于上面的ttt111的扁平树如下:

{
    "data":["tree","blob","tree","list","blob","blob"],
    "links":[
        {"hash":"<ttt222-hash>","size":1234,"name":"ttt222-name"},
        {"hash":"<bbb111-hash>","size":123,"name":"ttt222-name/bbb111-name"},
        {"hash":"<ttt333-hash>","size":3456,"name":"ttt333-name"},
        {"hash":"<lll111-hash>","size":578,"name":"ttt333-name/lll111-name"},
        {"hash":"<bbb222-hash>","size":22,"name":"ttt333-name/lll111-name/bbb222-name"},
        {"hash":"<bbb222-hash>","size":22,"name":"bbb222-name"},
    ]
}