ipfs数据存储原理

从规范、配置到源码(go-ipfs)详细介绍ipfs数据存储。

ipfs repo规范介绍

repo是IPFS节点的存储仓库,它是IPFS节点实际存储数据的子系统。所有IPFS对象都存储在一个仓库中(类似于git)。有多种repo实现,具体取决于所使用的存储介质。最常见的是,IPFS节点使用fs-repo(存储在os文件系统中),还有mem-repo、s3-repo。

Repo存储IPLD对象的集合,这些对象表示:

  • config-节点配置和设置
  • datastore -本地存储的内容以及索引数据
  • keystore-加密密钥,包括节点的身份
  • hooks-可以在预定义的时间运行的脚本(尚未实现)

请注意,repo存储的IPLD对象分为:

  • state (系统,控制平面)用于节点内部状态数据
  • content (用户区域,数据平面),代表用户的缓存数据和pinned数据。

此外,repo状态必须确定以下内容,但它们不一定是IPLD对象:
version-repo版本,是安全迁移所必需的
locks-处理信号量以进行正确的并发访问
datastore_spec-挂载点及其属性的数组
在这里插入图片描述

ipfs配置初始化

ipfs启动前配置需要初始化

ipfs  init --profile test
generating ED25519 keypair...done
peer identity: 12D3KooWE7mL8UP7he1vfQR51YS2fmCCLLiA5BPgkXxx1XYQSbBD
initializing IPFS node at /Users/yourname/.ipfs
to get started, enter:

       ipfs cat /ipfs/QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc/readme

每个profile代表不同的配置,可用的profile如下:

可以通过ipfs config profile --help查看可用的profile,和存储相关的有default-datastore、flatfs、badgerds

  • server

    禁用本地主机发现,在具有公共IPv4地址的计算机上运行IPFS时建议使用此功能。

  • randomports

    为群集使用随机端口号。

  • default-datastore

    将节点配置为使用默认数据存储(flatfs)。

  • local-discovery

    将默认值设置为受服务器配置文件影响的字段,从而可以在本地网络中进行发现。

  • test

    减少IPFS守护程序的外部干扰,这在测试环境中使用该守护程序时非常有用。

  • default-networking

    恢复默认的网络设置。测试配置文件的反向配置文件。

  • flatfs
    配置节点以使用flatfs数据存储。
    这是经过最严格测试和可靠的数据存储,但是它比badgerds存储要慢得多。
    支持在小型(<= 10GiB)数据存储上运行垃圾回收。

  • badgerds
    配置节点以使用Bader数据存储。
    这是最快的数据存储。如果性能(尤其是添加大量GB的文件)对性能至关重要时,当您的数据存储区小于几GB时,该数据存储区将无法正确回收空间。可能会使用几GB的内存。

  • lowpower
    减少系统上的守护程序开销。可能会影响节点功能-内容发现和数据获取的性能可能会降低。

初始化后的仓库目录:

tree ~/.ipfs
/Users/yourname/.ipfs
├── blocks
│   ├── 6Y
│   │   └── CIQA4T3TD3BP3C2M3GXCGRCRTCCHV7XSGAZPZJOAOHLPOI6IQR3H6YQ.data
│   ├── 75
│   │   └── CIQBEM7N2AM5YRAMJY7WDI6TJ4MGYIWVBA7POWSBPYKENY5IKK2I75Y.data
│   ├── BE
│   │   └── CIQCXBHBZAHEHBHU6P7PEA72E7UZQRJALHH7OH2FCWSWMTU7DMWVBEA.data
│   ├── HB
│   │   └── CIQMDQRK7B5DSZKBYOX4353TGN5J3JXS5VS6YNSAEJBOXBG26R76HBY.data
│   ├── I2
│   │   └── CIQBZNLCBI3U2I5F7O636DRBO552SCMSK2X2WYVCQ6BMYJN4MJTRI2Q.data
│   ├── IL
│   │   └── CIQJFGRQHQ45VCQLM7AJNF2GF5UHUAGGHC6LLAH6VYDEKLQMD4QLILY.data
│   ├── IY
│   │   └── CIQB4655YD5GLBB7WWEUAHCO6QONU5ICBONAA5JEPBIOEIVZ5RXTIYY.data
│   ├── JN
│   │   └── CIQPHMHGQLLZXC32FQQW2YVM4KGFORVFJAQYY55VK3WJGLZ2MS4RJNQ.data
│   ├── KE
│   │   └── CIQD44K6LTXM6PHWK2RHB3G2VCYFPMVBTALE572GSMETJGBJTELFKEI.data
│   ├── MJ
│   │   └── CIQHQFRJK4MU2CVNFR3QG6KZB3FZG6OG7EBI4SUNB5K4S4T5UVECMJA.data
│   ├── N6
│   │   └── CIQGFYPT5OBMRC7ZMUFC2R3ZQPKOGBMHJEDDFEVS5ALYBKIZCXPTN6Y.data
│   ├── OO
│   │   └── CIQBT4N7PS5IZ5IG2ZOUGKFK27IE33WKGJNDW2TY3LSBNQ34R6OVOOQ.data
│   ├── QD
│   │   └── CIQL4QZR6XGWMPEV5Q2FCTDFD7MF3G5OOC5CMEDUHNA5VXYZVDLFQDA.data
│   ├── R3
│   │   └── CIQBED3K6YA5I3QQWLJOCHWXDRK5EXZQILBCKAPEDUJENZ5B5HJ5R3A.data
│   ├── RO
│   │   └── CIQDRD2UT66U4EATJW53PSVWMFFPGNAN42PVWMDLHJD6FA5EVNNZROI.data
│   ├── SHARDING
│   ├── TP
│   │   └── CIQCODPXR5G237BYM7E5JF4A624CLH2TQDLC4QI6HEZK7FUWZQESTPI.data
│   ├── U2
│   │   └── CIQHFTCY7XL57YWLVDQ6UAXUOND3ADYQYJKYXA6G7A5IMD7SMO22U2A.data
│   ├── UC
│   │   └── CIQFKVEG2CPWTPRG5KNRUAWMOABRSTYUFHFK3QF6KN3M67G5E3ILUCY.data
│   ├── V3
│   │   └── CIQAPZYJAKUKALYI4YTB5PUMEN5BZYZHUQZWGFL4Q3HZUV26SYX2V3Q.data
│   ├── VN
│   │   └── CIQPEOA2TS3RMLOBOF55ZOEZE3TNBQG3HCNFOYC3BATAIJBOIE5FVNY.data
│   ├── X3
│   │   └── CIQFTFEEHEDF6KLBT32BFAGLXEZL4UWFNWM4LFTLMXQBCERZ6CMLX3Y.data
│   ├── XV
│   │   └── CIQGAS6MQJCEC37C2IIH5ZFYJCSTT7TCKJP3F7SLGNVSDVZSMACCXVA.data
│   ├── _README
│   └── diskUsage.cache
├── config
├── datastore
│   ├── 000002.ldb
│   ├── 000003.log
│   ├── CURRENT
│   ├── CURRENT.bak
│   ├── LOCK
│   ├── LOG
│   └── MANIFEST-000004
├── datastore_spec
├── keystore
└── version

文件内容如下:

  • 数据存储
    默认使用flatfs,但是应用profile flatfs时会组合两个datastore,block数据存放在flatfs里,其他的key存在leveldb
cat ~/.ipfs/datastore_spec
{
    
    "mounts":[{
    
    "mountpoint":"/blocks","path":"blocks","shardFunc":"/repo/flatfs/shard/v1/next-to-last/2","type":"flatfs"},{
    
    "mountpoint":"/","path":"datastore","type":"levelds"}],"type":"mount"}
  • repo版本
cat ~/.ipfs/version
11
  • 分片策略
cat ~/.ipfs/blocks/SHARDING
/repo/flatfs/shard/v1/next-to-last/2
  • 磁盘使用率
cat ~/.ipfs/blocks/diskUsage.cache
{
    
    "diskUsage":11289,"accuracy":"initial-exact"}

如果需要将所有数据存在leveldb 配置如下:
~/.ipfs/config(部分数据)

  "Datastore": {
    
    
    "StorageMax": "10GB",
    "StorageGCWatermark": 90,
    "GCPeriod": "1h",
    "Spec": {
    
    
      "child": {
    
    
        "path": "levelds",
	"compression":  "snappy",
        "type": "levelds"
      },
      "prefix": "levelds.datastore",
      "type": "measure"
    },
    "HashOnRead": false,
    "BloomFilterSize": 0
  }

~/.ipfs/datastore_spec

{
    
    "path":"levelds","type":"levelds"}

如果将所有配置都改为flatfs,配置如下

~/.ipfs/config(部分数据)

"Datastore": {
    
    
    "StorageMax": "10GB",
    "StorageGCWatermark": 90,
    "GCPeriod": "1h",
    "Spec": {
    
    
          "child": {
    
    
            "path": "flatfs",
            "shardFunc": "/repo/flatfs/shard/v1/next-to-last/2",
            "sync": true,
            "type": "flatfs"
          },
          "prefix": "flatfs.datastore",
          "type": "measure"
        
    },
    "HashOnRead": false,
    "BloomFilterSize": 0
  }

~/.ipfs/datastore_spec

{
    
    "path":"blocks","shardFunc":"/repo/flatfs/shard/v1/next-to-last/2","type":"flatfs"}

运行ipfs daemon后,将会报错

2021-03-18T14:20:30.266+0800    ERROR   cmd/ipfs        error from node construction: could not build arguments for function "reflect".makeFuncStub (/usr/local/Cellar/go/1.15.5/libexec/src/reflect/asm_amd64.s:12): failed to build *mfs.Root: received non-nil error from function "github.com/ipfs/go-ipfs/core/node".Files (src/github.com/ipfs/go-ipfs/core/node/core.go:106): failure writing to dagstore: when putting '"/blocks/CIQFTFEEHEDF6KLBT32BFAGLXEZL4UWFNWM4LFTLMXQBCERZ6CMLX3Y"': key not supported by flatfs

可以看到flatfs并不支持存储/blocks/打头的cid,看看源码:
Put前先会验证Key,显然key中包含/不满足条件

func (fs *Datastore) Put(key datastore.Key, value []byte) error {
    
    
	if !keyIsValid(key) {
    
    
		return fmt.Errorf("when putting '%q': %v", key, ErrInvalidKey)
	}

	fs.shutdownLock.RLock()
	defer fs.shutdownLock.RUnlock()
	if fs.shutdown {
    
    
		return ErrClosed
	}

	_, err := fs.doWriteOp(&op{
    
    
		typ: opPut,
		key: key,
		v:   value,
	})
	return err
}
// keyIsValid returns true if the key is valid for flatfs.
// Allows keys that match [0-9A-Z+-_=].
func keyIsValid(key datastore.Key) bool {
    
    
	ks := key.String()
	if len(ks) < 2 || ks[0] != '/' {
    
    
		return false
	}
	for _, b := range ks[1:] {
    
    
		if '0' <= b && b <= '9' {
    
    
			continue
		}
		if 'A' <= b && b <= 'Z' {
    
    
			continue
		}
		switch b {
    
    
		case '+', '-', '_', '=':
			continue
		}
		return false
	}
	return true
}

为啥不支持,这里有历史原因,请参考:
https://github.com/ipfs/go-ds-flatfs/issues/22
https://github.com/ipfs/go-ds-flatfs/issues/64

ipfs配置文件config

配置文件是位于$IPFS_PATH/config。在节点实例化时将读取一次。在正在运行的守护程序上执行的命令在运行时不会读取配置文件。

也可以通过ipfs config show命令查看。

cat ~/.ipfs/config
{
    
    
  "Identity": {
    
    
    "PeerID": "12D3KooWFWL1egSAhojrL6NsFr6jkrig9n6P9aXd3xGwh4Qk2ApW",
    "PrivKey": "CAESQG4rWwYcFS862IRQxLyV5TXwMSUkb2zni+HyFTJWJPpaVIc6cfZRyiwPNGtTCOrpNiTwrM8FQ2cQd/E6q0hsXP8="
  },
  "Datastore": {
    
    
    "StorageMax": "10GB",
    "StorageGCWatermark": 90,
    "GCPeriod": "1h",
    "Spec": {
    
    
      "mounts": [
        {
    
    
          "child": {
    
    
            "path": "blocks",
            "shardFunc": "/repo/flatfs/shard/v1/next-to-last/2",
            "sync": true,
            "type": "flatfs"
          },
          "mountpoint": "/blocks",
          "prefix": "flatfs.datastore",
          "type": "measure"
        },
        {
    
    
          "child": {
    
    
            "compression": "none",
            "path": "datastore",
            "type": "levelds"
          },
          "mountpoint": "/",
          "prefix": "leveldb.datastore",
          "type": "measure"
        }
      ],
      "type": "mount"
    },
    "HashOnRead": false,
    "BloomFilterSize": 0
  },
  "Addresses": {
    
    
    "Swarm": [
      "/ip4/0.0.0.0/tcp/4001",
      "/ip6/::/tcp/4001",
      "/ip4/0.0.0.0/udp/4001/quic",
      "/ip6/::/udp/4001/quic"
    ],
    "Announce": [],
    "NoAnnounce": [],
    "API": "/ip4/127.0.0.1/tcp/5001",
    "Gateway": "/ip4/127.0.0.1/tcp/8080"
  },
  "Mounts": {
    
    
    "IPFS": "/ipfs",
    "IPNS": "/ipns",
    "FuseAllowOther": false
  },
  "Discovery": {
    
    
    "MDNS": {
    
    
      "Enabled": true,
      "Interval": 10
    }
  },
  "Routing": {
    
    
    "Type": "dht"
  },
  "Ipns": {
    
    
    "RepublishPeriod": "",
    "RecordLifetime": "",
    "ResolveCacheSize": 128
  },
  "Bootstrap": [
    "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
    "/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa",
    "/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb",
    "/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt",
    "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
    "/ip4/104.131.131.82/udp/4001/quic/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ"
  ],
  "Gateway": {
    
    
    "HTTPHeaders": {
    
    
      "Access-Control-Allow-Headers": [
        "X-Requested-With",
        "Range",
        "User-Agent"
      ],
      "Access-Control-Allow-Methods": [
        "GET"
      ],
      "Access-Control-Allow-Origin": [
        "*"
      ]
    },
    "RootRedirect": "",
    "Writable": false,
    "PathPrefixes": [],
    "APICommands": [],
    "NoFetch": false,
    "NoDNSLink": false,
    "PublicGateways": null
  },
  "API": {
    
    
    "HTTPHeaders": {
    
    }
  },
  "Swarm": {
    
    
    "AddrFilters": null,
    "DisableBandwidthMetrics": false,
    "DisableNatPortMap": false,
    "EnableRelayHop": false,
    "EnableAutoRelay": false,
    "Transports": {
    
    
      "Network": {
    
    },
      "Security": {
    
    },
      "Multiplexers": {
    
    }
    },
    "ConnMgr": {
    
    
      "Type": "basic",
      "LowWater": 600,
      "HighWater": 900,
      "GracePeriod": "20s"
    }
  },
  "AutoNAT": {
    
    },
  "Pubsub": {
    
    
    "Router": "",
    "DisableSigning": false
  },
  "Peering": {
    
    
    "Peers": null
  },
  "Provider": {
    
    
    "Strategy": ""
  },
  "Reprovider": {
    
    
    "Interval": "12h",
    "Strategy": "all"
  },
  "Experimental": {
    
    
    "FilestoreEnabled": false,
    "UrlstoreEnabled": false,
    "ShardingEnabled": false,
    "GraphsyncEnabled": false,
    "Libp2pStreamMounting": false,
    "P2pHttpProxy": false,
    "StrategicProviding": false
  },
  "Plugins": {
    
    
    "Plugins": null
  },
  "Pinning": {
    
    
    "RemoteServices": {
    
    }
  }
}

和存储相关的配置项

  • Datastore.StorageMax
    ipfs存储库的数据存储区大小的软上限。和StorageGCWatermark一同配置,用于计算是否触发gc运行(仅当设置了–enable-gc时)。
    默认: “10GB”
    类型:(string)
  • Datastore.StorageGCWatermark
    StorageMax如果守护程序在启用了自动gc的情况下运行(该选项当前默认为false),将自动触发垃圾收集的值的百分比。
    默认: 90
    类型:integer(0-100%)
  • Datastore.GCPeriod
    一个持续时间,指定运行垃圾回收的频率。仅在启用自动gc时使用。
    默认: 1h
    类型:(duration空字符串表示默认值)
  • Datastore.HashOnRead
    布尔值。如果设置为true,将从磁盘读取的所有块都将进行哈希处理并进行验证。这将导致CPU利用率增加。
    默认: false
    类型: bool
  • Datastore.BloomFilterSize
    一个数字,表示blockstore的bloom过滤器的大小(以字节为单位)。零值表示功能被禁用。
    默认值:(0禁用)
    类型:(非负integer,字节)
  • Datastore.Spec
    规范定义了ipfs数据存储的结构。这是一个可组合的结构,其中每个数据存储区均由一个json对象表示。数据存储区可以包装其他数据存储区以提供额外的功能(eg metrics, logging, or caching)。
    可以手动更改,但是如果进行任何更改都需要不同的磁盘结构,则需要运行ipfs-ds-convert工具将数据迁移到新结构中。
    默认值:
{
    
    
  "mounts": [
	{
    
    
	  "child": {
    
    
		"path": "blocks",
		"shardFunc": "/repo/flatfs/shard/v1/next-to-last/2",
		"sync": true,
		"type": "flatfs"
	  },
	  "mountpoint": "/blocks",
	  "prefix": "flatfs.datastore",
	  "type": "measure"
	},
	{
    
    
	  "child": {
    
    
		"compression": "none",
		"path": "datastore",
		"type": "levelds"
	  },
	  "mountpoint": "/",
	  "prefix": "leveldb.datastore",
	  "type": "measure"
	}
  ],
  "type": "mount"
}

类型: object

可配置的type如下:

  • flatfs
    将每个键值对存储为文件系统上的文件。
    shardFunc带有前缀/repo/flatfs/shard/v1,后面跟分片策略。示例:
    1. /repo/flatfs/shard/v1/next-to-last/2
      根据key末尾两个字符上分片
    2. /repo/flatfs/shard/v1/prefix/2
      根据key的两个字符前缀分片
{
    
    
    "type": "flatfs",
	"path": "<relative path within repo for flatfs root>",
	"shardFunc": "<a descriptor of the sharding scheme>",
	"sync": true|false
}

注意:由于flatfs仅部分实现datastore接口,因此只能将其用作block store(挂载在/ blocks上)。您只能使用mount datastore为/block挂载flatfs。

  • levelds
    使用leveldb数据库存储键值对。
{
    
    
	"type": "levelds",
	"path": "<location of db inside repo>",
	"compression": "none" | "snappy",
}
  • badgerds
    使用badger作为键值存储。
    syncWrites:在继续操作之前,请刷新对磁盘的所有写入操作。将其设置为false是安全的,因为go-ipfs会在执行诸如固定之类的关键操作之前和之后自动刷新对磁盘的写入。但是,您可以将其设置为true以获得更高的安全性(以添加文件时降低2-3倍的速度为代价)。
    truncate:如果找到部分写入的扇区,则截断数据库(默认为true)。没有充分的理由将其设置为false,除非您要手动恢复部分写入(和未固定)的块(如果go-ipfs在添加文件时中途崩溃)。
{
    
    
	"type": "badgerds",
	"path": "<location of badger inside repo>",
	"syncWrites": true|false,
	"truncate": true|false,
}
  • mount
    允许指定的datastore处理给定路径为前缀的key。挂载点将作为子项数据存储定义中的键添加。
{
    
    
	"type": "mount",
	"mounts": [
		{
    
    
			// Insert other datastore definition here, but add the following key:
			"mountpoint": "/path/to/handle"
		},
		{
    
    
			// Insert other datastore definition here, but add the following key:
			"mountpoint": "/path/to/handle"
		},
	]
}
  • measure
    该datastore是一个包装,可将指标跟踪添加到任何datastore。
{
    
    
	"type": "measure",
	"prefix": "sometag.datastore",
	"child": {
    
     datastore being wrapped }
}

存储相关ipfs子项目

项目 功能
ipfs/go-datastore key/value数据存储接口
ipfs/go-ds-badger 使用 badger 作为后端的datastore 实现,badger是go实现的fast key/value数据库
ipfs/go-ds-leveldb 使用 leveldb 作为后端的datastore 实现
ipfs/go-ds-flatfs 直接使用文件系统的datastore 实现
ipfs/go-ds-measure 一个包装datastore,可以将指标跟踪添加到任何datastore
ipfs/go-ipfs-blockstore 在datastore上实现了一个精简包装器,为获取和放置块对象提供了一个干净的接口
ipfs/go-filestore a by-reference file-backed blockstore
ipfs/go-ipfs-pinner pinner系统负责跟踪用户要保留在本地的对象

类图

在这里插入图片描述
datasstore是repo的核心,datastore主要分两部分:datastoreConfig和不同类型的Datastore实现,datastoreConfig抽象出了create方法可以创建对应的datastore实例,所有的datastore实现都实现了Batching接口(即批量操作接口)

  • DatastoreConfig
    DatastoreConfig是数据存储配置接口,它有一个Create方法可以创建对应的DataStore实例,实现有:
    MountDatastoreConfig:可组合多个其他类型的DatastoreConfig
    MeasureDatastoreConfig:包装其他Datasource,统计了Get、Put等操作次数
    MemDatastoreConfig:使用Map实现的存储
    LogDatastoreConfig:包装其他Datastore,在Get、Put等操作增加了控制台打印
    plugin.leveldb.datastoreConfig:leveldb存储配置(通过插件机制加载)
    plugin.badger.datastoreConfig:badger存储配置(通过插件机制加载)
    plugin.flatfs.datastoreConfig:文件系统存储配置(通过插件机制加载)

  • 实现了Batching接口的存储
    ds.NullDatastore:go-datastore仓库中,空实现
    ds.MapDatastore:go-datastore仓库中
    ds.LogDatastore:go-datastore仓库中
    measure.measure:go-ds-measure仓库中
    flatfs.Datastore:go-ds-measure仓库中
    badger. Datastore:go-ds-badger仓库中
    leveldb.DataStore:go-ds-leveldb仓库中
    keytransform.Datastore:go-datastore仓库中,包装其他datastore,提供key转换功能,可以在get、put等操作动态修改key的值

  • Datastore interface
    GCDatastore:删除数据来释放磁盘空间接口
    ScrubbedDatastore:提供一种机制来检查数据完整性和错误纠正的数据存储接口
    CheckedDatastore:检查磁盘上数据完整性的数据存储实现的接口
    PersistentDatastore:报告磁盘使用情况的接口
    TTLDatastore:支持过期条目的数据存储接口
    TxnDatastore:扩展DataStore,支持事务

在这里插入图片描述
blockstore最底层还是依赖datastore,IpfsNode持有Blockstore、Repo、Filestore、Pinner实例,dagService封装了BlockService,BlockService封装了Blockstore,Blockstore的实现有gcBlockstore、blockstore、FileManager,

  • Pinner
    dspinner.pinner:实现了一些结构和方法,跟踪用户要保留在本地的哪些对象。此实现将pin数据存储在datastore中。
    ipldpinner.pin:实现了一些结构和方法,跟踪用户要保留在本地的哪些对象。此实现将pin信息存储在内存(Set)中。

datastore key前缀

// BlockPrefix namespaces blockstore datastores
"/blocks"

// FilestorePrefix identifies the key prefix for FileManager blocks.
"/filestore"

//mfs
"/local/filesroot"

//namesys
"/ipfs"
"/ipns"

// providers
"/providers"

//pin
"/pins"
"/pins/pin"
"/pins/index"
"/pins/state/dirty"

CoreAPI依赖一览

  • add
    repo.Config()/Blockstore.PinLock()/dagService.Sync()/dagService.Add/pinning.PinWithMode/pinning.Flush
  • block put
    BlockService.AddBlock
  • block get
    BlockService.GetBlock
  • block rm
    GCBlockstore.Has/GCBlockstore.DeleteBlock
  • dag add
    ipld.DAGService.Add/pin.Pinner.PinWithMode
  • dag AddMany
    DAGService.AddMany/pin.Pinner.PinWithMode
  • dht Provide
    GCBlockstore.Has/GCBlockstore.Get
  • key Generate
    repo.Keystore().Get/repo.Keystore().Put
  • key List
    repo.Keystore().Get
  • key Rename
    repo.Keystore().Get/Has/Delete/Put
  • key Remove
    repo.Keystore().Get/Delete
  • object Put
    DAGService.Add/pin.Pinner.PinWithMode
  • object Data/Links/Stat/Diff
    DAGService.Get

repo/datastore/blockstore初始化

daemon启动时,首先判断repo是否需要初始化repo,没有初始化则进行初始化(生成仓库初始配置),然后打开repo,将repo传入BuildCfg,最后调用NewNode创建一个新的节点。

func daemonFunc(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) (_err error) {
    
    
    。。。。
	initialize, _ := req.Options[initOptionKwd].(bool)
	if initialize && !fsrepo.IsInitialized(cctx.ConfigRoot) {
    
    
		 。。。

		if err = doInit(os.Stdout, cctx.ConfigRoot, false, profiles, conf); err != nil {
    
    
			return err
		}
	}

	
	repo, err := fsrepo.Open(cctx.ConfigRoot)

	。。。。
	
	ncfg := &core.BuildCfg{
    
    
		Repo:                        repo,
		Permanent:                   true,
		Online:                      !offline,
		DisableEncryptedConnections: unencrypted,
		ExtraOpts: map[string]bool{
    
    
			"pubsub": pubsub,
			"ipnsps": ipnsps,
		},
	}
	。。。。。

	node, err := core.NewNode(req.Context, ncfg)
	if err != nil {
    
    
		log.Error("error from node construction: ", err)
		return err
	}
	node.IsDaemon = true

repo初始化:首先检查仓库根目录权限,判断是否初始化,然后根据profiles生成config,最后调用Init创建repo相关文件

func doInit(out io.Writer, repoRoot string, empty bool, confProfiles string, conf *config.Config) error {
    
    
	if _, err := fmt.Fprintf(out, "initializing IPFS node at %s\n", repoRoot); err != nil {
    
    
		return err
	}

	if err := checkWritable(repoRoot); err != nil {
    
    
		return err
	}

	if fsrepo.IsInitialized(repoRoot) {
    
    
		return errRepoExists
	}

	if err := applyProfiles(conf, confProfiles); err != nil {
    
    
		return err
	}

	if err := fsrepo.Init(repoRoot, conf); err != nil {
    
    
		return err
	}

	if !empty {
    
    
		if err := addDefaultAssets(out, repoRoot); err != nil {
    
    
			return err
		}
	}

	return initializeIpnsKeyspace(repoRoot)
}

initConfig会写入config文件,initSpec会写入datastore_spec文件,WriteVersion写入version文件。

func Init(repoPath string, conf *config.Config) error {
    
    

	// packageLock must be held to ensure that the repo is not initialized more
	// than once.
	packageLock.Lock()
	defer packageLock.Unlock()

	if isInitializedUnsynced(repoPath) {
    
    
		return nil
	}

	if err := initConfig(repoPath, conf); err != nil {
    
    
		return err
	}

	if err := initSpec(repoPath, conf.Datastore.Spec); err != nil {
    
    
		return err
	}

	if err := mfsr.RepoPath(repoPath).WriteVersion(RepoVersion); err != nil {
    
    
		return err
	}

	return nil
}

fsrepo.Open用来打开一个已经初始化的Repo,onlyOne跟踪打开的FSRepo实例。fsrepo.open:先创建FSRepo实例,对仓库目录做检查(和doInit类似),openConfig会从磁盘将config文件载入到内存,openDatastore会根据config配置情况获取DatastoreConfig实例,再调用DatastoreConfig.Create方法生成Datastore,目前的Datastore默认使用flatfs,然后在外面再包装一个measure,使其可以统计Datastore的指标信息,调用openKeystore()打开FSKeystore(如果keystore目录不存在则创建)。最后判断是否打开了FilestoreEnabled和UrlstoreEnabled配置,如果打开则创建FileManager实例。

func Open(repoPath string) (repo.Repo, error) {
    
    
	fn := func() (repo.Repo, error) {
    
    
		return open(repoPath)
	}
	return onlyOne.Open(repoPath, fn)
}

func open(repoPath string) (repo.Repo, error) {
    
    
	packageLock.Lock()
	defer packageLock.Unlock()

	r, err := newFSRepo(repoPath)
	if err != nil {
    
    
		return nil, err
	}

	// Check if its initialized
	if err := checkInitialized(r.path); err != nil {
    
    
		return nil, err
	}

	r.lockfile, err = lockfile.Lock(r.path, LockFile)
	if err != nil {
    
    
		return nil, err
	}
	keepLocked := false
	defer func() {
    
    
		// unlock on error, leave it locked on success
		if !keepLocked {
    
    
			r.lockfile.Close()
		}
	}()

	// Check version, and error out if not matching
	ver, err := mfsr.RepoPath(r.path).Version()
	if err != nil {
    
    
		if os.IsNotExist(err) {
    
    
			return nil, ErrNoVersion
		}
		return nil, err
	}

	if RepoVersion > ver {
    
    
		return nil, ErrNeedMigration
	} else if ver > RepoVersion {
    
    
		// program version too low for existing repo
		return nil, fmt.Errorf(programTooLowMessage, RepoVersion, ver)
	}

	// check repo path, then check all constituent parts.
	if err := dir.Writable(r.path); err != nil {
    
    
		return nil, err
	}

	if err := r.openConfig(); err != nil {
    
    
		return nil, err
	}

	if err := r.openDatastore(); err != nil {
    
    
		return nil, err
	}

	if err := r.openKeystore(); err != nil {
    
    
		return nil, err
	}

	if r.config.Experimental.FilestoreEnabled || r.config.Experimental.UrlstoreEnabled {
    
    
		r.filemgr = filestore.NewFileManager(r.ds, filepath.Dir(r.path))
		r.filemgr.AllowFiles = r.config.Experimental.FilestoreEnabled
		r.filemgr.AllowUrls = r.config.Experimental.UrlstoreEnabled
	}

	keepLocked = true
	return r, nil
}


func (r *FSRepo) openDatastore() error {
    
    
	。。。

	dsc, err := AnyDatastoreConfig(r.config.Datastore.Spec)
	if err != nil {
    
    
		return err
	}
	。。。
	d, err := dsc.Create(r.path)
	if err != nil {
    
    
		return err
	}
	r.ds = d

	// Wrap it with metrics gathering
	prefix := "ipfs.fsrepo.datastore"
	r.ds = measure.New(prefix, r.ds)

	return nil
}

加载的时候只加载了MountDatastoreConfig等四个Config,那其他的像leveldb的config怎么初始化的?ipfs启动的时候会调用loadPlugins方法载入插件,接着调用插件的Inject方法,再调用injectDatastorePlugin方法,最终调用AddDatastoreConfigHandler填充ConfigFromMap。

var datastores map[string]ConfigFromMap

func init() {
    
    
	datastores = map[string]ConfigFromMap{
    
    
		"mount":   MountDatastoreConfig,
		"mem":     MemDatastoreConfig,
		"log":     LogDatastoreConfig,
		"measure": MeasureDatastoreConfig,
	}
}
func AddDatastoreConfigHandler(name string, dsc ConfigFromMap) error {
    
    
	_, ok := datastores[name]
	if ok {
    
    
		return fmt.Errorf("already have a datastore named %q", name)
	}

	datastores[name] = dsc
	return nil
}
// AnyDatastoreConfig returns a DatastoreConfig from a spec based on
// the "type" parameter
func AnyDatastoreConfig(params map[string]interface{
    
    }) (DatastoreConfig, error) {
    
    
	which, ok := params["type"].(string)
	if !ok {
    
    
		return nil, fmt.Errorf("'type' field missing or not a string")
	}
	fun, ok := datastores[which]
	if !ok {
    
    
		return nil, fmt.Errorf("unknown datastore type: %s", which)
	}
	return fun(params)
}

再看下IpfsNode的初始化,实际调用node.IPFS

// NewNode constructs and returns an IpfsNode using the given cfg.
func NewNode(ctx context.Context, cfg *BuildCfg) (*IpfsNode, error) {
    
    
	。。。

	n := &IpfsNode{
    
    
		ctx: ctx,
	}

	app := fx.New(
		node.IPFS(ctx, cfg),

		fx.NopLogger,
		fx.Extract(n),
	)
	。。。
	return n, n.Bootstrap(bootstrap.DefaultBootstrapConfig)
}

IPFS这个方法引用了Storage

func IPFS(ctx context.Context, bcfg *BuildCfg) fx.Option {
    
    
	。。。
	uio.UseHAMTSharding = cfg.Experimental.ShardingEnabled

	return fx.Options(
		bcfgOpts,

		fx.Provide(baseProcess),

		Storage(bcfg, cfg),
		Identity(cfg),
		IPNS,
		Networked(bcfg, cfg),

		Core,
	)
}

继续看Storage,这会根据配置不同调用GcBlockstoreCtor、FilestoreBlockstoreCtor、BaseBlockstoreCtor初始化Blockstore

// Storage groups units which setup datastore based persistence and blockstore layers
func Storage(bcfg *BuildCfg, cfg *config.Config) fx.Option {
    
    
	cacheOpts := blockstore.DefaultCacheOpts()
	cacheOpts.HasBloomFilterSize = cfg.Datastore.BloomFilterSize
	if !bcfg.Permanent {
    
    
		cacheOpts.HasBloomFilterSize = 0
	}

	finalBstore := fx.Provide(GcBlockstoreCtor)
	if cfg.Experimental.FilestoreEnabled || cfg.Experimental.UrlstoreEnabled {
    
    
		finalBstore = fx.Provide(FilestoreBlockstoreCtor)
	}

	return fx.Options(
		fx.Provide(RepoConfig),
		fx.Provide(Datastore),
		fx.Provide(BaseBlockstoreCtor(cacheOpts, bcfg.NilRepo, cfg.Datastore.HashOnRead)),
		finalBstore,
	)
}
// BaseBlocks is the lower level blockstore without GC or Filestore layers
type BaseBlocks blockstore.Blockstore

// BaseBlockstoreCtor creates cached blockstore backed by the provided datastore
func BaseBlockstoreCtor(cacheOpts blockstore.CacheOpts, nilRepo bool, hashOnRead bool) func(mctx helpers.MetricsCtx, repo repo.Repo, lc fx.Lifecycle) (bs BaseBlocks, err error) {
    
    
	return func(mctx helpers.MetricsCtx, repo repo.Repo, lc fx.Lifecycle) (bs BaseBlocks, err error) {
    
    
		// hash security
		bs = blockstore.NewBlockstore(repo.Datastore())
		bs = &verifbs.VerifBS{
    
    Blockstore: bs}

		if !nilRepo {
    
    
			bs, err = blockstore.CachedBlockstore(helpers.LifecycleCtx(mctx, lc), bs, cacheOpts)
			if err != nil {
    
    
				return nil, err
			}
		}

		bs = blockstore.NewIdStore(bs)
		bs = cidv0v1.NewBlockstore(bs)

		if hashOnRead {
    
     // TODO: review: this is how it was done originally, is there a reason we can't just pass this directly?
			bs.HashOnRead(true)
		}

		return
	}
}

// GcBlockstoreCtor wraps the base blockstore with GC and Filestore layers
func GcBlockstoreCtor(bb BaseBlocks) (gclocker blockstore.GCLocker, gcbs blockstore.GCBlockstore, bs blockstore.Blockstore) {
    
    
	gclocker = blockstore.NewGCLocker()
	gcbs = blockstore.NewGCBlockstore(bb, gclocker)

	bs = gcbs
	return
}

// GcBlockstoreCtor wraps GcBlockstore and adds Filestore support
func FilestoreBlockstoreCtor(repo repo.Repo, bb BaseBlocks) (gclocker blockstore.GCLocker, gcbs blockstore.GCBlockstore, bs blockstore.Blockstore, fstore *filestore.Filestore) {
    
    
	gclocker = blockstore.NewGCLocker()

	// hash security
	fstore = filestore.NewFilestore(bb, repo.FileManager())
	gcbs = blockstore.NewGCBlockstore(fstore, gclocker)
	gcbs = &verifbs.VerifBSGC{
    
    GCBlockstore: gcbs}

	bs = gcbs
	return
}

go-ipfs 与rust-ipfs存储实现比较

1.go-ipfs repo提供接口不相同。go-ipfs主要提供config管理接口和Datastore、keystore、FileManager引用,没有直接提供数据数据操作接口;rust-ipfs repo没有提供config管理及keystore、FileManager,提供block/pin等操作方法。

2.go-ipfs的Pinner接口设计更优雅,rust-ipfs DataStore继承PinStore不妥,导致Pinner和DataStore糅杂在一起。方法设计也有问题,如insert_direct_pin/insert_recursive_pin不同model的pin提供不同的方法,go-ipfs直接提供一个pin方法,通过参数区分不同的Pin。

4.go-ipfs datastore实现更为丰富:NullDatastore/MapDatastore/LogDatastore/measureDatastore/flatfs.Datastore/badger.Datastore/leveldb.Datastore;rust-ipfs提供了三种类型的DataStore:MemDataStore、KvDataStore、FsDataStore,KvDataStore和FsDataStore的key/value管理没实现,只实现了Pin。

5.go-ipfs对datastore和blockstore的封装更有层次感,易扩展。 datastore->Batching->blockstore(arccache/bloomcache)->gcBlockstore->VerifBSGC/VerifBS->measurestore->blockService->dagService;go-ipfs BlockStore底层还是datastore,rust-ipfs底层的 FsBlockStore完全自己实现,完全没发挥datastore的价值。

6.go-ipfs FileManager/Filestore,Filestore组合了blockstore和FileManager,他们都实现了Blockstore;rust-ipfs没有文件存储的实现。

猜你喜欢

转载自blog.csdn.net/kk3909/article/details/114894219