IPFS和区块链有着非常紧密的联系,随着区块链的不断发展,对数据的存储需求也越来越高。本文从IPFS的底层设计出发,结合源代码,分析了IPFS的一些技术细节。
一,概述
IPFS和区块链有着非常紧密的联系,随着区块链的不断发展,对数据的存储需求也越来越高,由于性能和成本的限制,现有的区块链设计方案大部分都选择了把更大的数据存储在链外,通过对数据进行加密,哈希运算等手段来防止数据被篡改,在区块链上只引用所存数据的哈希值,从而满足业务对数据的存储需求。本文从IPFS的底层设计出发,结合源代码,分析了IPFS的一些技术细节。由于IPFS还在不断更新中,文中引用的部分可能和最新代码有所出入。
阅读本文需要读者
了解网络编程
了解分布式存储
了解基本的区块链知识
二,什么是IPFS?
维基百科上是这样解释的:是一个旨在创建持久且分布式存储和共享文件的网络传输协议。
上面的解释稍显晦涩,我的理解是:
1. 首先它是一个FS(文件系统)
2. 其次它支持点对点传输
既然是文件系统,那它和普通的文件系统有什么区别呢?有以下几点区别:
存储方式: 它是分布式存储的,为了方便传输,文件被切分成多个块,每个块通过散列运算得到唯一的ID,方便在网络中进行识别和去重。考虑到传输效率,同一个block可能有多个副本,分别存储在不同的网络节点上。
内容寻址方式: 每个块都有唯一的ID,我们只需要根据节点的ID就可以获取到它所对应的块。
那么问题来了, 既然文件被切分成了多个块,如何组织这些块数据,组成逻辑上的文件呢? 在IFPS中采用的merkledag,下面是merkledag的一个示意图:
简单来说,就是2种数据结构merkle和DAG(有向无环图)的结合,通过这种逻辑结构,可以满足:
内容寻址:使用哈希ID来唯一识别一个数据块的内容
防篡改:可以方便的检查哈希值来确认数据是否被篡改
去重:由于内容相同的数据块哈希是相同的,可以很容去掉重复的数据,节省存储空间
确定了数据模型后,接下来要做的事:如何把数据分发到不同的网络节点上,达到分布式存储和共享的目的?我们先思考一下,通过网络,比如HTTP,访问某个文件的步骤,首先我们要知道存储这个文件的服务器地址,然后我们需要知道这个文件对应的ID,比如文件名。前者我们可以抽象成网络节点寻址,后者我们抽象成文件对象寻址;在IPFS中, KAD算法的文章很多,这里不赘述,只简单说明一下核心思想:
KAD最精妙之处就是使用XOR来计算ID之间的距离,并且统一了节点ID和对象ID的寻址方式。采用 XOR(按比特异或操作)算法计算key之间的“距离”。
这种做法使得它具备了类似于“几何距离”的某些特性(下面用⊕表示XOR)
(A⊕B)==(B⊕A)XOR符合“交换律”,具备对称性。
(A⊕A)== 0反身性,自身距离为零
(A⊕B)> 0【不同】的两个关键之间的距离必大于零
(A⊕B)+(B⊕C)> =(A⊕C)三角不等式
通过KAD算法,IPFS把不同ID的数据块分发到与之距离较近的网络节点中,达到分布式存储的目的。
通过IPFS获取文件时,只需要根据merkledag,按图索,根据每个块的ID,通过KAD算法从相应网络节点中下载块数据,最后验证是否数据完整,完成拼接即可。
下面我们再从技术实现的角度做更深入的介绍。
三,IPFS的系统架构
我们先看一下IPFS的系统架构图,分为5层:
一层为命名,基于PKI的一个命名空间;
第二层为merkledag,IPFS内部的逻辑数据结构;
第三层为exchange,节点之间block data的交换协议;
第四层为routing,主要实现节点寻址和对象寻址;
第五层为network,封装了P2P通讯的连接和传输部分。
站在数据的角度来看,又可以分为2个大的模块:
IPLD(InterPlanetary Linked Data)主要用来定义数据,给数据建模;
libp2p解决的是数据如何传输的问题。
下面分别介绍IFPS中的2个主要部分IPLD和libP2P。
IPLD
通过哈希值来实现内容寻址的方式在分布式计算领域得到了广泛的应用,比如区块链,再比如git repo。虽然使用hash连接数据的方式有相似之处,但是底层数据结构并不能通用,IPFS是个极具野心的项目,为了让这些不同领域之间的数据可互操作,它定义了统一的数据模型IPLD,通过它,可以方便地访问来自不同领域的数据。
前面已经介绍数据的逻辑结构是用merkledag表示的,那么它是如何实现的呢?围绕merkledag作为核心,它定义了以下几个概念:
merkle link代表dag中的边
merkel-dag有向无环图
merkle-path访问dag节点的类似unix path的路径
IPLD数据模型基于json的数据模型
IPLD序列化格式序列化格式
canonical格式:为了保证同样的逻辑对象总是序列化为一个同样的输出,而制定的确定性规则
围绕这些定义它实现了下面几个组成部分
CID内容ID
数据模型数据模型
序列化格式序列化格式
工具和库工具和库
IPLD选择器类似CSS选择器,方便选取dag中的节点
IPLD转换对dag进行转换计算
我们知道,数据是多样性的,为了给不同的数据建模,我们需要一种通用的数据格式,通过它可以最大程度地兼容不同的数据,IPFS中定义了一个抽象的集合,multiformat,包含multihash ,multiaddr,多基准,multicodec,多流几个部分。
multihash
自识别hash,由3个部分组成,分别是:hash函数编码,hash值的长度和hash内容,下面是个简单的例子:
这种设计的最大好处是非常方便升级,一旦有一天我们使用的哈希函数不再安全了,或者发现了更好的哈希函数,我们可以很方便的升级系统。
multiaddr
自描述地址格式,可以描述各种不同的地址
Multibase公司
multibase代表的是一种编码格式,方便把CID编码成不同的格式,比如这里定义了2进制,8进制,10进制,16进制,也有我们熟悉的base58btc和base64编码。
multicodec
mulcodec代表的是自描述的编解码,其实是个表,用1到2个字节定了数据内容的格式,比如用字母z表示base58btc编码,0x50表示protobuf等等。
多数据流
multistream首先是个stream,它利用multicodec,实现了自描述的功能,下面是基于一个javascript的例子;先是新一个缓冲对象,里面是json对象,然后给它加一个前缀protobuf,这样这个这个多流就是构造好了,可以通过网络传输。在解析时可以先取编解码前缀,然后移除前缀,得到具体的数据内容。
结合上面的部分,我们重点介绍一下CID。
CID是IPFS分布式文件系统中标准的文件寻址格式,它集合了内容寻址,加密散列算法和自我描述的格式,是IPLD内部核心的识别符。目前有2个版本,CIDv0和CIDv1。
CIDv0是一个向后兼容的版本,其中:
multibase一直为base58btc
multicodec一直为protobuf-mdag
version一直为CIDv0
multihash表示为cidv0 :: =
为了更灵活的表述ID数据,支持更多的格式,IPLD定义了CIDv1,CIDv1由4个部分组成:
Multibase公司
版
multicodec
multihash
IPLD是IPFS的数据描述格式,解决了如何定义数据的问题,下面这张图是结合源代码整理的一份逻辑图,我们可以看到上面是一些高级的接口,比如文件,mfs,fuse等。下面是数据结构的持久化部分,节点之间交换的内容是以块为基础的,最下面就是物理存储了。比如块存储在块目录,其他节点之间的信息存储在leveldb,还有密钥库,配置等。
数据如何传输呢?
接下来我们介绍libP2P,看看数据是如何传输的.libP2P是个模块化的网络协议栈。
做过socket编程的小伙伴应该都知道,使用raw socket编程传输数据的过程,无非就是以下几个步骤:
获取目标服务器地址
和目标服务器建立连接
握手协议
传输数据
关闭连接
libP2P也是这样,不过区别在于它把各个部分都模块化了,定义了通用的接口,可以很方便的进行扩展。
架构图
由以下几个部分组成,分别是:
对等路由
Swarm(传输和连接)
分布式记录存储
发现
下面我们对它们做分别介绍,我们先看关键的路由部分。
对等路由
libP2P定义了路由接口,目前有2个实现,分别是KAD路由和MDNS路由,扩展很容易,只要按照接口实现相应的方法即可。
ipfs中的节点路由表是通过维护多个K-BUCKET来实现的,每次新增节点,会计算节点ID和自身节点ID之间的公共前缀,根据这个公共前缀把节点加到对应的KBUCKET中,KBUCKET最大值为20,当超出时,再进行拆分。
更新路由表的流程如下:
除了KAD路由之外,IPFS也实现了MDNS路由,主要用来在局域网内发现节点,这个功能相对比较独立,由于用到了多播地址,在一些公有云部署环境中可能无法工作。
群(传输和连接)
swarm定义了以下接口:
transport网络传输层的接口
连接处理网络连接的接口
stream multiplex同一连接复用多个stream的接口
下面我们重点看下是如何动态协商stream protocol的,整个流程如下:
默认先通过multistream-select完成握手
发起方尝试使用某个协议,接收方如果不接受,再尝试其他协议,直到找到双方都支持的协议或者协商失败。
另外为了提高协商效率,也提供了一个ls消息,用来查询目标节点支持的全部协议。
分布式记录存储
记录表示一个记录,可以用来存储一个键值对,比如ipns name发布就是发布一个objectId绑定指定节点id的记录到ipfs网络中,这样通过ipns寻址时就会查找对应的记录,再解析到objectId,实现寻址的功能。
发现
目前系统支持3种发现方式,分别是:
bootstrap通过配置的启动节点发现其他的节点
随机漫步通过查询随机生成的peerID,从而发现新的节点
mdns通过多播发现局域网内的节点
最后总结一下源代码中的逻辑模块:
从下到上分为5个层次:
最底层为传输层,主要封装各种协议,比如TCP,SCTP,BLE,TOR等网络协议
传输层上面封装了连接层,实现连接管理和通知等功能
连接层上面是stream layer,实现了stream的多路复用
流层上面是路由层
最上层是发现,短信以及记录存储等
四,总结
本文从定义数据和传输数据的角度分别介绍了IPFS的2个主要模块IPLD和libP2P:
IPLD主要用来定义数据,给数据建模
libP2P解决数据传输问题
这两部分相辅相成,虽然都源自于IPFS项目,但是也可以独立使用在其他项目中。
IPFS的远景目标就是替换现在浏览器使用的HTTP协议,目前项目还在迭代开发中,一些功能也在不断完善。为了解决数据的持久化问题,引入了filecoin激励机制,通过令牌激励,让更多的节点加入到网络中来,从而提供更稳定的服务。
来源:美图技术