分布式存储的基本概念和原理
分布式存储是指将数据存储在多个独立的存储设备中,通过网络连接的方式协同工作,共同提供数据存储和访问服务的系统
数据分片:将数据切分成多个小块,每个小块可以分别存储在不同的存储节点上。数据分片可以提高存储效率和数据可靠性。
数据冗余:分布式存储系统通常会将数据进行多份备份存储,以提高数据的可靠性和可用性。当某个存储节点发生故障时,可以通过备份节点进行数据恢复。
数据一致性::在分布式存储系统中,由于数据分散在不同的节点上,可能会导致数据一致性的问题。因此,需要采用一些数据一致性算法来确保分布式存储系统中的数据一致性。
数据访问:分布式存储系统中,数据的访问通常需要经过多个存储节点。因此,需要设计一些数据访问协议和路由算法来确保数据的高效访问
分布式存储的架构:分布式存储系统通常采用多个存储节点构成的集群进行存储和访问。不同的分布式存储架构包括中心化架构、对等网络架构和混合架构等。
分布式存储的原理是将数据分散存储在多个节点上,通过数据分片和数据冗余等技术提高数据的可靠性和可用性。同时,需要采用一些数据一致性算法和数据访问协议来确保数据的一致性和高效访问。分布式存储还需要考虑存储节点的扩展性、负载均衡和故障恢复等问题。
IPFS的概念和定义
IPFS (InterPlanetary File System)是一个基于内容寻址的、分布式的、新型超媒体传输协议
IPFS项目通过整合已有的技术(BitTorrent、DHT、Git和SFS),创建一种点对点超媒体协议
IPFS的起源
全球化分布式存储网络并不是最近几年的新鲜品,其中最有名的3个就是BitTorrent、Kazaa、和 Napster,至今这些系统在全世界依旧拥有上亿活跃用内尤其是 BitTorrent客户端,现在 BitTorrent 网络每天依然有超过1000万个节点在上传数据。
之所以普及进展十分缓慢,一个原因可能是目前广泛使用的 HTTP 协议已经足够好用。
HTTP的问题
- 极易受到攻击,防范攻击成本高。
- 数据存储成本高
- 数据的中心化带来泄露风险
- 大规模数据存储、传输和维护难
IPFS的优势
- 下载速度快
- 优化全球存储。
- 更加安全。
- 数据的可持续保存
IPFS协议栈
身份层:管理节点身份生成和验证。
所有的节点都通过唯一的NodeId进行标识,与Bitcoin的地址类似,Nodeld 是一个公钥的哈希。IPFS 使用S/Kademlia 中的算法增加创建新身份的成本。
每一个节点在IPFS代码中都由 Node 结构体来表示,其中只包含Nodeld及一组公私钥对。 在节点首次建立连接时,节点之间首先交换公钥,并且进行身份信息验证,比如:检查 hash(other.PublicKey)的值是否等于 other.NodeId 的值。如果校验结果不通过,则用户信息不匹配,节点立即终止连接。
IPFS使用的哈希算法比较灵活,允许用户根据使用自定义。默认以Multihash格式存储,定义如下:
网络层:管理与其他节点的连接,使用多种底层网络协议。
IPFS 几乎可以使用任意网络进行节点之间的通信,没有完全依赖于IP 协议。IPFS 通过 multiaddr 的格式来表示目标地址和其使用的协议,以此来兼容和扩展未来可能出现的其他网络协议。
IPFS的网络通信模式是遵循覆盖网络(Overlay Network) 的理念设计的
覆盖网络的模型如图 所示,是一种网络架构上叠加的虚拟化技术模式,它建立在已有网络上的虚拟网,由逻辑节点和逻辑链路构成。图中多个容器在跨主机通信的时候,使用 Overlay Network 网络模式。首先虚拟出类似服务网关的IP 地址,例如 10.0.9.3,然后把数据包转发到 Host(主机)物理服务器地址最终通过路由和交换到达另一个 Host 服务器的IP 地址。
路由层:以分布式哈希表(DHT) 维护路由信息以定位特定的对等节点和对象。响应本地和远端节点发出的查询请求。
IPFS路由层数据结构使用基于 S/Kademlia和 Coral技术的分布式松散哈希表(DSHT)
IPFS 的 DHT 结构会根据所存储数据的大小进行区分:小的值(等于或小于 1KB)直接存储在 DHT上;更大的值,DHT 只存储值索引,这个索引就是一个节点的 NodeId,该节点可以提供对该类型值的具体服务。
IPFS的路由实现了3 种基本功能: 内容路由、节点路由及数据存储
交换层:一种支持有效块分配的新型块交换协议(BitSwap),模拟可信市场,弱化数据复制,防作弊。
IPFS 中的 BitSwap 协议是协议实验室的一项创新设计,其主要功能是利用信用机制在节点之间进行数据交换。
BitSwap 协议中存在一个数据交换市场,这个市场包括各个节点想要获取的所有块数据,这些块数据可能来自文件系统中完全不相关的文件,同时这个市场是由 IPFS 网络中所有节点组成的。
这样的数据市场需要创造加密数字货币来实现可信价值交换,这也为协议实验室后来启动 Filecoin 这样区块链项目埋下伏笔
type BitSwap struct {
ledgers map[NodeId]Ledger // 节点账单
active map[NodeId] Peer // 当前已经连接的节点
need_list [Multihash] // 此节点需要的块数据校验列表
have_list [Multihash] // 此节点已收到的块数据校验列表
}
BitSwap协议
在 IPFS 中,数据的分发和交换使用 BitSwap 协议。BitSwap 协议主要负责两件事情:向其他节点请求需要的数据块列表 ( want_list),以及为其他节点提供已有的数据块列表
当我们需要向其他节点请求数据块或者为其他节点提供数据块时,都会发送 BitSwap Message 消息,其中主要包含了两部分内容: 想要的数据块列表(want_list)及对应数据块。消息使用 Protobuf 进行编码。
BitSwap 系统中,有两个非常重要的模块一一需求管理器 (Want-Manager)和决策引擎
前者会在节点请求数据块时在本地返回相应的结果或者发出合适的请求;而后者决定如何为其他节点分配资源,当节点接收到包含 want_list 的消息时,消息会被转发至决策引擎,引擎会根据该节点的 BitSwap 账单决定如何处理请求。
一次 BitSwap 数据交换的全过程及节点的生命周期中,节点一般要经历 4 个状态。
- 状态开放 (Open): 对等节点间开放待发送 BitSwap 账单状态,直到建立连接。
- 数据发送 (Sending):节点间发送 want_list 和数据块。
- 连接关闭(Close): 节点发送完数据后断开连接。
- 节点忽略 (Ignored): 节点因为超时、自定义、信用分过低等因素被忽略。
1. Peer.open(Nodeld, Ledger)
当节点建立连接时,发送方节点初始化 BitSwap 信用账单,保存一份对等方的账单或者创建一个新的被清零的信用账单。
取决于节点信用账单一致性之后,发送方节点将发送一个携带账单的 open 信息通知接收方节点,接收方节点接收到一个 open 信息之后,选择是否接受此连接。
如果接收方根据本地的信用账单数据,发现发送方是一个不可信的节点例如传输超时、信用分较低、债务率较高等,则接收方会通过 ignore_cooldown忽略这个请求,并且断开连接,目的是防范作弊行为。
如果连接成功,接收方将用本地信用账单来初始化一个 Peer 对象,并更新last_seen 时间戳。 它会将接收到的账单与自己的账单进行比较。如果两个信用账单完全一样,那么这个连接就被开放;如果账单不完全一致,那么此节点会创建一个新的被清零的信用账单,并发送同步此信用账单,以此保证发送方节点和接收方节点的账单一致。
2. Peer.send_want_list(WantList)
当连接已经处于开放状态时,发送方节点将会把 want_list 广播给所有连接的接收方节点。与此同时,接收方节点在收到一个 want_list 后,会检查自身是否有接收方想要的数据块。如果有,会使用 BitSwap 策略(将在 3.4.3 节介绍)来发送传输这些数据块。
3.Peer.send_block(Block)
发送块的方法逻辑很简单,默认发送方节点只传输数据块,接收到所有数据后,接收方节点计算 Multihash 以验证它是否与预期的匹配,然后返回确认。
在完成块的传输后,接收方节点将数据块信息从 need list 移到 have_list,并且接收方和发送方都同步更新他们的账单列表。如果传输验证失败,则发送方可能发生故障或存在故意攻击接收方的行为,接收方可以拒绝进一步的交易。
4. Peer.close(Bool)
对等连接应该在两种情况下关闭: silent_wait 已超时,但未收到来自对方的任何消息(默认BitSwap 使用30秒),节点发出 Peer.close (false)。 节点正在退出,BitSwap 正在关闭,在这种情况下,节点发出Peer.close(true)。
每一个P2P软件都有自己专属的数据分享策略,IPFS 也不例外,其 BitSwap 的策略体系由信用、策略、账单三部分组成,接下来依次介绍这三部分内容。
IPFS 根据节点之间的数据收发建立了一个信用体系: 有借有还,再借不难。
给其他节点发送数据可以增加信用值;
从其他节点接收数据将降低信用值。
策略的目标是:
节点数据交换的整体性能和效率力求最高;
阻止空载节点“吃白食”现象,即不能够只下载数据不上传数据;
可以有效地防止一些攻击行为;
对信任节点建立宽松机制。
计算信用分和负债率 r = bytes_sent / bytes_recv + 1 这个是负债率的计算公式,比如说 A 和 B 两个节点,现在 A 在往 B 发送数据,如果 A 往 B 发得越多,那对 A 来讲,B 的负债率就会很高。
数据发送率 P(send|r) = 1 - ( 1/ ( 1 + exp(6 - 3r))) 如果r大于 2 时,发送率 P(send|r)会变得很小,从而 A 就不会继续给 B 发送数据。
BitSwap 节点会记录下来和其他节点通信的账单(数据收发记录)。当两个节点之间建立连接的时候,BitSwap 会相互交换账单信息,如果账单不匹配,则直接清除并重新记账。恶意节点会“有意失去”这些账单,从而期望清除自己的债务。其他交互节点会把这些都记下来,如果总是发生,伙伴节点可以自由地将其视为不当行为,拒绝交易。
对象层: 具有基于Merkle DAG所构建的对象层,具有内容寻址、防冗余特性。 IPFS 使用Merkle DAG 技术构建了一个有向无环图数据结构,用来存储对象数据。
- 内容可寻址:所有内容由多重哈希校验并唯一标识。
- 防止篡改:所有内容都通过哈希验证,如果数据被篡改或损坏,在 IPFS网络中将会被检测到。
- 重复数据删除:保存完全相同内容的所有对象都是相同的,并且只存储次。这对于索引对象特别有用。
Merkle DAG 的对象结构定义如下所示:
type IPFSLink struct {
Name string // 此link的别名
Hash Multihash // 目标的加密Hash
Size int // 目标总大小
}
type IPFSObject struct {
links \[]IPFSLink // links数组
data \[]byte // 不透明内容数据
}
IPFS 客户端需要一个本地存储器,一个外部系统可以为 IPFS 管理的对象存储及检索本地原始数据。
希望对某个对象数据进行长期存储的节点可以执行锁定操作,以此保证此特定对象被保存在了该节点的本地存储器上,同时也可以递归地锁定所有相关的派生对象,这对长期存储完整的对象文件特别有用。
任何人都可以发布对象, 只需要将对象的 key 加人 DHT 中,并且对象通过 P2P 传输的方式加人进去,然后把访问路径传给其他的用户
IPFS 具备可以处理数据对象加密的操作
文件层:类似Git的版本化文件系统,支持 blob、commit、list、tree等结构体。 IPFS 还定义了一组对象,用于在 Merkle DAG 之上对版本化文件系统进行建模
- 块(block):一个可变大小的数据块。
- 列表(list):一个块或其他列表的集合
- 树(tree):块、列表或其他树的集合。
- 提交 (commit): 树版本历史记录中的快照.
Blob: Blob 对象包含一个可寻址的数据单元,表示一个文件。当文件比较小,不足以大到需要分片时,就以 Blob 对象的形式存储于 IPFS 网络之中,如下所示:
{
"data": "some data here",//Blobs无links
}
List: List 对象由多个连接在一起的 Blob 组成,通常存储的是一个大文件。List 的功能更适用于数据块互相连接的文件系统,List可以包含其他 List,所以可能形成包括链接列表和平衡树在内的拓扑结构:
{
data": ["blob","list","blob"],
"links": [
{"hash": "XLYkgq61DYaQ8Nhkcqyu7rLcnSa7dSHQ16x""size": 189458},
{"hash":"XLHBNmRQ5sJJrdMPuu48pzeyTtRo39tNDR5""size": 19441},
{"hash":"XLWVQDqxo9Km9zLyquoC9gAP8CLlgWnHZ7z""size": 5286}
]
}
Tree: 它代表一个目录,或者一个名字到哈希值的映射表。哈希值表示 Blob、List、其他的 Tree 或 Commit
{
"data":\["blob""list""blob"],
"links":\[
{"hash":"XLYkgq61DYaQ8Nhkcqyu7rLcnSa7dsHQ16x", "name":"less", "size": 189458 },
{"hash":"XLHBNmRQ5sJJrdMPuu48pzeyTtRo39tNDR5","name":"script","size": 19441},
{"hash":"XLWVQDqxo9Km9zLyquoC9gAP8CL1gWnHZ7z","name": "template","size": 5286}
]
}
Commit: Commit 对象代表一个对象在历史版本中的一个特定快照。两个不同的Commit 之间互相比较对象数据 (和子对象数据),可以揭露出两个不同版本文件系统的区别。IPFS 可以实现 Git 版本控制工具的所有功能,同时也可以兼容并改进 Git。
路径查找性能:
树缓存 ( tree cache):由于所有的对象都是哈希寻址的,它们可以被无限地缓存。另外,Tree 一般比较小,所以比起 Blob,IPFS 会优先缓存Tree。
扁平树 ( fattened tree): 对于任何给定的 Tree,一个特殊的扁平树可以构建一个链表,所有对象都可以从这个 Tree 中访问得到。在扁平树中name 就是一个从原始 Tree 分离的路径,用斜线分隔。
命名层:具有自验特性的可变名称系统。
IPNS星际文件命名系统模块
自验证命名:
- 通过 Nodeld = hash(node.PubKey),生成 IPFS 节点信息
- 给每个用户分配一个可变的命名空间,由之前生成的节点 ID 信息作为地址名称,在此路径下: /ipns/。
- 一个用户可以在此路径下发布一个用自己私钥签名的对象,比如:/ipns/XLF2ipQ4jD3UdeX5xp1KBgeHRhemUtaA8Vm/。
- 当其他用户获取对象时,他们可以检测签名是否与公钥和节点信息相匹配,从而验证用户发布对象的真实性,达到了可变状态的获取。
这块的动态可变内容是通过设置路由函数来控制的
routing.setValue (NodeId, )
在命令空间中,所发布的数据对象路径名称可以作为子名称:
/ipns/XLF2ipQ4jD3udeX5xp1KBgeHRhemUtaA8Vm/ /ipns/XLF2ipQ4jD3Udex5xp1KBgeHRhemUtaA8Vm/docs/ipns/XLF2ipQ4jD3UdeX5xp1KBgeHRhemUtaA8Vm/docs/ipfs
人类友好命名
1.对等节点链接
遵循自验证文件系统 ( SFS) 的设计理念,用户可以将其他用户节点的对象直接链接到自己的命名空间下。
Alice 链接到Bob上
ipfs link //friends/bob /
Eve 链接到Alice上
ipfs link /<eve-pk-hash/friends/alice /
Eve 也可以访问Bob
/<eve-pk-hash/friends/alice/friends/bob
访问Verisign 认证域
/foo.com
2. DNS TXT IPNS 记录
我们也可以在现有的 DNS 系统中添加 TXT 记录,这样就能通过域名访问IPFS 网络中的文件对象了
DNS TXT 记录
ipfs.benet.ai. TXT "ipfs=XLF2ipQ4jD3u ..."
表现为符号链接
ln -s /ipns/XLF2ipQ4jD3u /ipns/fs.benet.ai
IPFS 也支持可读标识符 Proquint,可以将二进制编码翻译成可读文件的方法
proquint语句
/ipns/dahih-dolij-sozuk-vosah-luvar-fuluh
分解为相应的形式
/ipns/KhAwNprxYVxKqpDZ
IPFS 还提供短地址的命名服务
用户可以从下面获取一个link
ipns/shorten.er/foobar
然后放到自己的命名空间
/ipns/XLF2ipQ4jD3udeX5xp1KBgeHRhemutaA8Vm
分布式哈希表DHT
第 1代 P2P 文件网络需要中央数据库协调,容易导致单点失效,甚至导致整个网络瘫痪
第 2 代分布式文件系统中,Gnutella 使用消息洪泛方法(message flooding)来定位数据 。查询消息会公布给全网所有的节点,直到找到这个信息,然后返回给查询者 。网络请求量非常大,很容易造成拥堵。
第 3 代分布式文件系统中,DHT 的创新提供了新的解决方案。DHT ( Distributed Hash Table)。全网维护一个巨大的文件索引哈希表。
这个哈希表的条目形如 <Key, Value>。这里 Key 通常是文件的某个哈希算法下的哈希值(也可以是文件名或者文件内容描述),而 Value 则是存储文件的 IP 地址。
这个哈希表会被分割成小块,按照一定的算法和规则分布到全网各个节点上,节点查询文件时,只要把查询报文路由到相应的节点即可。
下面介绍 3 种IPFS引用过的有代表性的分区表类型,分别是 Kademlia DHT、Coral DHT和S/Kademlia。
Kademlia DHT
特性
- 节点 ID 与KEY是同样的值域,都是使用 SHA-1 算法生成的 160 位摘要,这样大大简化了查询时的信息量,更便于查询
- 可以使用 XOR,计算任意两个节点的距离或节点和关键字的距离
- 查找一条请求路径的时候,每个节点的信息是完备的,只需要进行Log(n)量级次跳转。
- 可根据查询速度和存储量的需求调整每个节点需要维护的 DHT 大小
说明:
一个新来的网络节点在初次连接网络时会被分配一个ID ; 每个节点自身维护一个路由表和一个DHT。
路由表保存网络中一部分节点的连接信息,DHT 用于存放文件信息。
每个节点优先保存距离自己更近的节点信息,但一定确保距离在[2^n, 2^(n+1)-1] 的全部节点至少保存 k个 (k 是常数),我们称作 K-Bucket
每个网络节点需要优先存储与自己的 ID 距离较小的文件
每次检索时,计算查询文件的哈希值与自己的ID的距离,然后找到与这个距离对应的 K-Bucket,向 K-Bucket 中的节点查询,接受查询的节点也做同样的检查,如果发现自己存有这个数据,便将其返回给查询的节点。
Kademlia二叉状态树
Kademlia 网络的节点 ID 是由一棵二叉树维护的,最终生成的二叉树的特点如下:
-
每个网络节点从根节点出发,沿着它的最短唯一前缀到达,
-
每个网络节点是叶子节点。对于任意的一个树的节点我们可以沿着它的前缀作为路径,向下分解成一系列不包含自己的子树.Kademlia 二叉树的建立,需要确保每个网络的节点都能从树根沿着它的最短唯一前缀的路径到达。
每个 DHT条目包含 <key, value>对。key 是文件的哈希值,value 是节点ID。key 和 value 有相同的值域,都是 160 位。
每--个新加入网络的计算机都会被随机分配一个节点 ID 值。数据存放在 key 值与 ID 值最接近 key 值的节点上
<key,Value> 在 160 位 Hash 上,判断两个节点x、y的距离远近的方法是进行二进制运算异或d(x,y)=x⊕y
如果给定了 x,任意一个 a(a>=0) 会唯一确定另一个节点y,满足 d(x,y)=a.
假设这里的 x 是我们需要查询的文件 key,我们只需要不断更新y,使得y沿着 d(x, y) 下降的方向找下去,那么一定能收敛到距离x 最近的点。
而且每次更新总能筛选掉一半的节点,那么最多只需 Log N步即可到达
节点路由表K-Bucket
节点路由表用于保存每个节点与自己一定距离范围内其他节点的连接信息,每一条路由信息由如下3部分组成:IPAddress、UDP Port、NodeID
KAD路由表将距离分成160个K桶(存放K个数据的桶),分开存储。编号为i的路由表,存放着距离为[2^i, 2^(i+1)-1]的K条路由信息
每个K桶内部信息存放位置是根据上次看到的时间顺序排列的,最早看到的放在头部,最后看到的放在尾部
因为网络中节点可能处于在线或者离线状态,而在之前经常在线的节点,我们需要访问的时候在线的概率更大,那么我们会优先访问它(尾部的节点)。
通常来说当i值很小时,K桶通常是空的(以0为例,距离为0自然只有1个节点,就是自己);而当i值很大时,其对应K桶的项数又很可能特别多(因为范围很大)。这时,我们只选择存储其中的K个。在这里k的选择需要以系统性能和网络负载来权衡它的数量。
当节点x收到一个消息时,发送者y的IP 地址就被用来更新对应的K桶具体步骤如下。
1)计算自己和发送者的ID 距离: d(x,y)=x⊕y。
2)通过距离d选择对应的K进行更新操作。
3)如果y的IP地址已经存在于这个K桶中,则把对应项移到该K桶的尾部;如果y的IP 地址没有记录在该K桶中
①如果该桶的记录项小于k个,则直接把y的(IP address,UDP port,NodeID) 信息插入队列尾部。
②如果该K桶的记录项大于k个,则选择头部的记录项(假如是节点z)进行 RPC PING 操作。
如果z没有响应,则从桶中移除z的信息,并把的信息插人队列尾部.
如果z有响应,则把z的信息移到队列尾部,同时忽略,的信息。
路由查询机制
假如节点x要查找ID 值为t的节点,Kad 按照如下递归操作步骤进行路由查找:
1)计算到t的距离:d(x,t)=x⊕t。
2)从x的第log(d)个K桶中取出a个节点的信息,同时进行FIND_NODE操作。如果这个K桶中的信息少于a个,则从附近多个桶中选择距离最接近d的总共 a 个节点。
3)对接收到查询操作的每个节点,如果发现自己就是t,则回答自己是最接近t的;否则测量自己和t的距离,并从自己对应的K桶中选择a个节点的信息给 x。
4)x对新接收到的每个节点都再次执行FIND_NODE操作,此过程不断重复执行,直到每一个分支都有节点响应自己是最接近t的。
5)通过上述查找操作,x得到了k个最接近t的节点信息。
是k个最接近t的节点信息,而不是完全信息相等,因为网络中可能根本不存在ID为t的节点 a也是为权衡性能而设立的一个参数,就像k一样。
在 BitTorrent实现中,取值为a=3。这个递归过程一直持续到x=t,或者路由表中没有任何关于t的信息
这样的机制保证了每一次递归操作都能够至少获得距离减半(或距离减少 1bit)的效果,从而保证整个查询过程的收敛速度为 O(logN),这里N为网络全部节点的数量。
上述是查询节点 ID的方法,对于文件查询也是一样的方法。区别仅在于进行FIND Value操作,查找自己是否保存ID为t的文件。文件查询过程的收敛速度同样是 O(LogN)。
节点加入和离开
如果节点u要加入KAD网络,它必须和一个已经在KAD网络中的节点比如w,取得联系。
u首先把w插入自己适当的桶中,对自己的节点ID执行一次 FINDNODE 操作,然后根据接收到的信息更新自己的K桶内容。
通过对自己邻近节点由近及远的逐步查询,u完成了仍然是空的K桶信息的构建,同时也把自己的信息发布到其他节点的K桶中。
通过对自己邻近节点由近及远的逐步查询,u完成了仍然是空的K桶信息的构建,同时也把自己的信息发布到其他节点的K桶中。
以节点u为例,其路由表的生成过程如下:
1)最初,u的路由表为一个单个的K桶,覆盖了整个160bit ID 空间
2)当学习到新的节点信息后,则u会尝试把新节点的信息,根据其前缀值插入对应的K桶中。
①该桶没有满,则新节点直接插入这个K桶中;
②该K桶已经满了:如果该K桶覆盖范围包含了节点的ID,则把该K桶分裂为两个大小相同的新K桶,并对原桶内的节点信息按照新的K桶前缀值进行重新分配;如果该K桶覆盖范围没有包含节点的ID,则直接丢弃该新节点信息。
3)上述过程不断重复,直到满足路由表的要求。达到距离近的节点的信息多、距离远的节点的信息少的结果,这样就保证了路由查询过程能快速收敛。
Kademlia面临的攻击
攻击方式包括日蚀攻击、女巫攻击、流失攻击和对抗路由攻击
(1)日蚀攻击
如果一个节点在网络中能够自由选择它的ID,攻击者可以在网络中安放一些恶意节点,使得信息都必须经由恶意节点传递。那么这样一来,恶意节点就能够在网络中将一个或几个节点从网络中隐藏掉
(2)女巫攻击
在开放的对等网络里,攻击者可以假冒多个ID,用少数网络节点控制多个虚假身份。
(1)(2)解决方法: 非对称加密确保节点身份不被窃取,我们可以设置一定的计算量障碍,强迫节点进行一定的哈希运算来确保不能自由选择和批量生产ID。
每个节点在接入前必须解决两个密码学问题\
静态问题是:产生一对公钥和私钥,并且将公钥做两次哈希运算后,具有 c1 个前导零。那么公钥的一次哈希值,就是这个节点的NodeID。
动态问题是:不断生成一个随机数X,将X与NodeID求XOR后再求哈希,哈希值要求有 c2个前导零。
静态问题保证节点不再能自由选择节点 ID 了,而动态问题则提高了大量生成ID 的成本。那么女巫攻击和日蚀攻击将难以进行。
为确保节点身份不被窃取,节点需要对发出的消息进行签名。在其他节点接收到消息时,首先验证签名的合法性,然后检查节点ID是否满足上述两个难题的要求。
(3)流失攻击
攻击者拥有网络的一些节点,即恶意节点,这可能会在网络中引发大量流量流失,从而导致网络稳定性降低
(4)对抗路由攻击 恶意节点在收到查询指令后,不是按照KAD的要求返回距离Key最接近的网络节点,而是转移给同伙节点。同伙节点也做同样的操作,而不返回给查询节点所需要的信息,那么这样一来查询就会失效。
避免方法: 在查询时设计算法并行地查询,并且每一条查询路径不相交。这样一来,只要并行查询的路径中有一条不碰到恶意节点,查询就能成功了。
不相交路径查找算法
每次查询从d个不同的 Bucket选择k个节点。这d个Bucket并行查找,Bucket内部查找方式和KAD协议完全相同。这样一来,d条查找路径就能做到不相交
对于任意一个 Bucket,有失效的可能,但是只要d个Bucket中有一条查询到了所需要的信息,这个过程就完成了。
Coral DSHT
CDN的设计是为了避开互联网传输瓶颈,并且降低内容供应服务器的网络压力,使得内容能更快速、更稳定地传递给客户端。
CDN 的基本思想是在网络部署一些节点服务器,并且建立一套虚拟网络。网络中节点服务器之间实时更新连接信息、延时信息、用户距离参数等,然后将用户的请求重定向到最适合的节点服务器上。
好处:
- 通过节点服务器中转,用户访问网页的速度大大提高了;
- 其次,节点服务器会缓存内容服务器的查询信息,那么也降低了内容服务器的网络负载;
- 最后,内容服务器有可能出现暂时的离线,那么用户同样能通过节点服务器中的缓存读取
不同于经典的DHT方式,Coral首先对所有的节点评估连接情况,然后根据循环时间(Round-Trip Time)划分为几个等级(Coral中是3层),L2(<20ms)、L1(<60ms)、L0(其他)。Coral 还提供了两个操作接口,put和get,用于添加和查找一个键值对,以确定从哪一个等级的DSHT中查询。
Coral DSHT适用于软状态的键值对检索,也就是同一个Key可能会保存多个 Value。这种机制能把给定的 Key 映射到网络中的 Coral服务器地址.
比如,使用 DSHT来查询距离用户较近的域名服务器,查询拥有特定网站缓存信息的服务器;定位周围的节点来最小化请求延时。
索引机制和分层
Coral对路由表的处理也比较特殊,每一个 Coral 节点根据它们的延时特性放在不同的 DSHT 中
同一个 DSHT 的节点被称为一个集群,每个集群有一个最大延时时间,称为集群直径。
整个系统会预先设定一个直径,称为等级。 在每个等级划分下,每个节点都会是某一个DSHT的成员,一组节点只有满足两两直径小于第i个等级的极限时,它们才能成为一个集群。
在 Coral中,将 DSHT分成了三层,Level-2对应两两延时小于20毫秒,Level-1 对应两两延时小于60毫秒,Level-0对应其他的全部节点。
Coral在询问时,也会优先请求等级更高、相应时间更短的节点。如果失败了才会在下一级节点中请求。
基于键值对的路由层 Coral的键值对,有160bit.我们在此可以通过Put指令保存一个<Key, Value>对,用来表明 Key和Value是接近的;也可以通过 Get 指令,来查询对于同一个Key,节点的远近顺序如何。
Sloppy存储
在Kademlia 协议中,数据会直接保存到XOR更近的节点。但实际情况是,如果某些数据非常热门,其他节点也会大量查询,会因此造成拥塞,我们称作Hot-Spot;同时,一个缓存键值对存储了过多的值,我们称其为 Tree-saturation。
每一个 Coral节点定义有两种异常状态,Full和 Loaded。
Full 状态定义为:在当前节点R,已经存在L个<Key, Value>对使得 Key=k,并且这L个键值对的生存周期都大于新值的 1/2。
Loaded 状态定义为: 对于给定的 Key=k,在过去的一分钟里已经收到超过特定次请求。
那么 Coral 在执行存储操作的时候分为两步进行:
第1步为前向查询,Coral会持续迭代查找距离Key更近的节点ID,这一点和Kademlia协议完全一样。每一个节点返回两个信息,其一,该节点是否加载,其二,对于该 Key,这一节点存储有几个 Value,每一个 Value 的实效时间是多长.
客户端根据接收到的这些信息决定这些节点是否可以存储新的值。
前向查询的过程会一直继续,直到客户端找到一个距离 Key 值最近的可连接节点。对某个节点查询异常,这一节点将不再继续迭代查询。可连接节点将被逐一压进堆栈里。
块交换协议 BitTorrent
BitTorrent 是一种内容分发协议,采用内容分发和点对点技术,帮助用户相互更高效地共享大文件,减轻中心化服务器的负载。 BitTorrent 网络里,每个用户需要同时上传和下载数据.
文件的持有者将文件发送给其中一个或多个用户,再由这些用户转发给其他用户,用户之间相互转发自己所拥有的文件部分,直到每个用户的下载全部完成。这种方法可以减轻下载服务器的负载,下载者也是上传者,平摊带宽资源,从而大大加快文件的平均下载速度。
BitTorrent 术语含义
torrent:它是服务器接收的元数据文件 (通常结尾是Torent).这个文件记录了下载数据的信息 (但不包括文件自身),例如文件名、文件大小、文件的哈希值,以及 Tracker 的 URL 地址。
tracker:是指互联网上负责协调 BitTorrent 客户端行动的服务器。 当你打开一个 torrent 时,你的机器连接 tracker,并且请求一个可以接触的peers 列表。在传输过程中,客户端将会定期向 tracker 提交自己的状态。tracker 的作用仅是帮助 peers 相互达成连接,而不参与文件本身的传输。
peer: peer 是互联网上的另一台可以连接并传输数据的计算机。通常情况下,peer 没有完整的文件。peer 之间相互下载、上传。
seed: 有一个特定 torrent 完整拷贝的计算机称为 seed。文件初次发布时需要一个 seed 进行初次共享。
swarm: 连接一个 torrent 的所有设备群组。
Chocking: Chocking 阻塞是一种临时的拒绝上传策略,虽然上传停止了,但是下载仍然继续。BitTorrent 网络下载需要每个 peer 相互上传,对于不合作的 peer,会采取临时的阻断策略。
Pareto 效率: 帕累托效率 ( Pareto efhicieney) 是指资源分配已经到了物尽其用的阶段,对任意一个个体进一步提升效率只会导致其他个体效率下降。此时说明系统已经达到最优状态了。
针锋相对 (Tit-fot-Tat):它强调的是永远不先背叛对方,除非自己被背叛。在 BitTorrent 中表现为,Peer 给自己贡献多少下载速度,那么也就贡献多少上传速度给他。
内容的发布
新文件是如何在 BitTorrent 网络上传播的新的文件发行:\
-
首先,seed 会生成一个扩展名为 .torrent 的文件,它包含如下信息: 文件名、大小、tracker 的 URL
-
一次内容发布至少需要一个 tracker 和一个 seed,tracker 保存文件信息和 seed 的连接信息,而 seed 保存文件本身。
-
一旦 seed 向 tracker 注册,它就开始等待为需要这个 torrent 的 peer 上传相关信息 通过 .torrent 文件,peer 会访问 tracker,获取其他 peer/seed 的连接信息,例如 IP 和端口
-
tracker 和 peer 之间只需要通过简单的远程通信,peer 就能使用连接信息,与其他 peer/seed 沟通,并建立连接下载文件。
分块交换
peer 大多是没有完整的拷贝节点的,为了跟踪每个节点已经下载的信息有哪些,BitTorrent 把文件切割成大小为 256KB 的小片。
每一个下载者需要向他的 peer 提供其拥有的片
为了确保文件完整传输,这些已经下载的片段必须通过 SHA-1 算法验证
只有当片段被验证是完整的时,才会通知其他 peer 自己拥有这个片段,可以提供上传。
片段选择算法
如何合理地选择下载片段的顺序,对提高整体的速度和性能非常重要。如果某片段仅在极少数 peer 上有备份,则这些 peer 下线了,网络上就不能找到备份了,所有 peer 都不能完成下载。
一系列片段选择的策略:
优先完成单一片段:如果请求了某一片段的子片段,那么本片段会优先被请求。这样做是为了尽可能先完成一个完整的片段,避免出现每一个片段都请求了同一个子片段,但是都没有完成的情况
优先选择稀缺片段: 选择新的片段时,优先选择下载全部 peer 中拥有者最少的片段。
这样也就降低了两种风险,其一,某个 peer 正在提供上传,但是没有人下载(因为大家都有了这一片段); 其二,拥有缺片段的 peer 停止上传,所有 peer 都不能得到完整的文件。
第一个片段随机选择: 下载刚开始进行的时候,并不需要优先最稀缺的。第一个片断是随机选择的,直到第一个片断下载完成,才切换到“优先选择稀缺片段”的策略
结束时取消子片段请求: peer 会向它的所有的 peer 都发送对某片断的子片断的请求,一旦某些节点发送的子片断到了,那么就会向其他 peer 发送取消消息,取消对这些子片断的请求,以避免浪费带宽。
阻塞策略
BitTorrent 中文件分享完全依赖每个 peer,因此每个peer 都有义务来共同提高共享的效率。
- 对于合作者,会根据对方提供的下载速率给予同等的上传速率回报。
- 对于不合作者,就会临时拒绝对它的上传,但是下载依然继续。
某个 peer 不可能与无限个 peer 进行连接,通常情况只能连接 4个 peer
怎么控制才能决定选择哪些 peer 连接使得下载速度达到最优?
BitTorrent 网络每 10 秒重新计算一次,然后维持连接状态到下一个 10 秒才会计算下一次。
最优阻塞
除了提供给 peer 上传的链接,还有一个始终畅通的链接叫最优阻塞,不论目前的下载情况如何,它每间隔 30 秒就会重新计算一次哪一个链接应该是最优阻塞。30 秒的周期足够达到最大上传和下载速率了。
反对歧视
在特殊情况下,某个 peer 可能被全部的 peer 阻塞了,那么很显然,通过上面的方法,它会一直保持很低的下载速度,直到经历下一次最优阻塞。
为了减少这种问题,如果一段时间过后,从某个 peer 那里一个片断也没有得到,那么这个 peer 会认为自己被对方“怠慢”了,于是不再为对方提供上传。
完成后的上传
一旦某个 peer 完成下载任务了,就不再以它的下载速率决定为哪些 peer 提供上传服务
至此开始,该 peer 优先选择网络环境更好、连接速度更快的其他peer,这样能更充分地利用上传带宽。
版本控制的类型
版本控制系统是用于记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。
例如我们在做开发时,版本控制系统会帮我们实现对每-次修改的备份,可以方便地回到之前的任意一个版本。
实现版本控制的软件三类: 本地版本控制系统、中心化版本控制系统、分布式版本控制系统
本地版本控制系统
其中最流行的一种称为RCS,现今许多计算机系统上都还能看到它的踪影。
它的工作原理是在硬盘上保存补丁集(补丁是指文件修订前后的变化);通过应用所有的补丁,可以重新计算出各个版本的文件内容。
中心化版本控制系统
CVS、Subversion 及 Perforce
都有一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。多年以来,这已成为版本控制系统的标准做法。
每个人都可以在一定程度上看到项目中的其他人正在做些什么,而管理员也可以轻松掌控每个开发者的权限。
这种方案最显而易见的缺点是中央服务器的单点故障。如果中央服务器宕机 1 小时,那么在这 1小时内,谁都无法提交更新,也就无法协同工作。如果中心数据库所在的磁盘发生损坏,又没有及时做备份,毫无疑问你将丢失所有数据,包括项目的整个变更历史,只剩下人们在各自机器上保留的单独快照。
分布式版本控制系统
为了避免中心化版本控制系统单点故障的风险,分布式版本控制系统(Distributed Version Control System,DVCS) 面世了。有 Git、MercurialBazaar 及 Darcs 等
客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来。任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份。
快照流
Git 和其他版本控制系统的主要差别在于 Git 保存数据的方法。
其他大部分系统以文件变更列表的方式存储信息,这类系统(CVS、Subversion、Perforce、Bazaar等)将它们保存的信息看作一组随时间逐步累积的文件差异关系。
Git 更像把数据看作对小型文件系统的一组快照.每次你提交更新或在 Git 中保存项目状态时,它将为全部文件生成一个快照并保存这个快照的索引。
为了效率,如果文件没有修改,Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件,Git 保存数据的方式更像是一个快照流。
Git 存储的是数据随时间改变的快照。
本地执行操作
在 Git 中的绝大多数操作都只需要访问本地文件和资源,一般不需要来自网络上其他计算机的信息。本地磁盘上就有项目的完整历史,所以大部分操作看起来瞬间就完成了.
要浏览项目的历史,Git 不需外连到服务器去获取历史,然后再显示出来,它只需直接从本地数据库中读取,你就能立即看到项目历史。 如果你想查看当前版本与一个月前的版本之间引人的修改,Git 会查找到一个月前的文件做一次本地的差异计算,而不是由远程服务器处理或从远程服务器拉回旧版本文件再在本地处理。
这也意味着当你处于离线状态时,也可以进行几乎所有的操作。比如:在飞机或火车上想做些工作,你能愉快地提交,直到有网络连接时再上传,回家后VPN 客户端不正常,你仍能工作。
只添加数据
执行的 Git 操作,本质都是在 Git 数据库中增加操作数据。Git 上的操作几乎都是可逆的.一旦你将修改快照提交到 Git 系统中,就难再丢失数据。
完整性校验
Git 中所有数据在存储前都会计算校验和,然后以校验和来引用。这意味着不可能在 Git 不知情时更改任何文件内容或目录内容。这个功能由 Git 在底层实现。若你在编辑过程中丢失信息或损坏文件,Git 就能发现
Git 用以计算校验的哈希算法是 SHA-1。Git 会将文件的内容或目录结构一同计算,得出它们的哈希值,确保文件和路径的完整性。Git 数据库中保存的信息都是以文件内容的哈希值来索引的,而不是文件名。
工作区与工作状态
Git 有 3 种状态,你的文件可能处于其中之一:已提交(committed)、已修改(modified)和已暂存(staged)
已提交表示数据已经安全地保存在本地数据库中;
已修改表示修改了文件,但还没保存到数据库中;
已暂存表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中。
Git 项目的 3 个工作区域的概念: 工作目录、Git 仓库及暂存区域,Git 仓库包括本地仓库和远程仓库。
-
工作目录:直接编辑修改的目录。工作目录是将项目中某个版本独立提取出来的内容放在磁盘上供你使用或修改。
-
Git 仓库:保存项目的元数据和对象数据库。这是 Git 中最重要的部分从其他计算机复制仓库时,复制的就是这里的数据。
-
暂存区域: 是一个文件,保存了下次将提交的文件列表信息,一般在Git 仓库中。有时候也被称作“索引”,不过一般还是叫暂存区域。
基本的 Git 工作流程如下:
- 在工作目录中修改文件;
- 暂存文件,将文件的快照放人暂存区域;
- 提交更新,找到暂存区域的文件,将快照永久性存储到 Git 仓库。
分支
Git 保存的不是文件差异或者变化量,而是一系列文件快照。
在 Git 中提交时会保存一个提交 (commit) 对象,该对象包含一个指向暂存内容快照的指针;包含本次提交的作者等相关附属信息;包含零个或多个指向该提交对象的父对象指针:
首次提交是没有直接祖先的,普通提交有一个祖先,由两个或多个分支合并产生的提交则有多个祖先。
假设在工作目录中有 3 个文件,准备将它们暂存后提交,暂存操作会对每一个文件计算校验和,然后把当前版本的文件快照保存到 Git仓库中(Git 使用 blob 类型的对象存储这些快照),并将校验和加入暂存区域
git add README test.rb LICENSE
git commit -m 'initial commit of my project'
当使用 git commit 新建一个提交对象前,Git 会先计算每一个子目录(本例中就是项目根目录)的校验和
然后在 Git 仓库中将这些目录保存为树(tree)对象。
之后 Git 创建的提交对象,除了包含相关提交信息以外,还包含着指向这个树对象(项目根目录)的指针,如此它就可以在将来需要的时候,重现此次快照的内容了。
现在,Git 仓库中有 5 个对象:
3 个表示文件快照内容的 blob 对象;
1个记录着目录树内容及其中各个文件对应 blob 对象索引的 tree 对象;
1个包含指向 tree 对象(根目录)的索引和其他提交信息元数据的 commit 对象。
做些修改后再次提交,那么这次的提交对象会包含一个指向上次提交对象的指针。
Git 中的分支本质上仅是个指向 commit 对象的可变指针。Git 使用 master 作为分支的默认名字。第一个分支也常被称为主干。主干可被克隆为其他分支,每条分支的可变指针在每次提交时都会自动向前移动。
Merkle DAG & Merkle Tree
Merkle DAG是IPFS 的存储对象的数据结构,Merkle Tree 则用于区块链交易的验证 Merkle Tree 通常也被称为哈希树 (Hash Tree),顾名思义,就是存储哈希值的一棵树; 而 Merkle DAG 是默克尔有向无环图的简称
Merkle Tree 的叶子是数据块(例如,文件、交易)的哈希值。非叶节点是其对应子节点串联字符串的哈希。
Merkle DAG 的节点包括两个部分,Data 和 Link
Data 为二进制数据,Link 包含 Name、Hash 和 Size 这3个部分
从数据结构上看,Merkle DAG 是 Merkle Tree 更普适的情况。Merkle Tree 是特殊的 Merkle DAG Merkle Tree通常用于验证数据完整性,而Merkle DAG大多用于文件系统。
Merkle Tree
Hash 是一个把任意长度的数据映射成固定长度数据的函数。
对于数据完整性校验,最简单的方法是对整个数据做 Hash 运算,得到固定长度的Hash 值,然后把得到的 Hash 值公布在网上,这样用户下载到数据之后,对数据再次进行 Hash 运算,将运算结果与网上公布的 Hash 值进行比较,如果两个Hash 值相等,说明下载的数据没有损坏。
Hash List
如何确定数据块的完整性呢?
只需要为每个数据块计算 Hash 值。BT 下载的时候,在下载到真正数据之前,我们会先下载一个 Hash 列表。
把每个小块数据的 Hash 值拼到一起,然后对这个长字符串再做一次 Hash 运算,这样就得到 Hash 列表的根 Hash ( Top Hash 或 Root Hash)。
下载数据的时候,首先从可信的数据源得到正确的根 Hash,就可以用它来校验Hash 列表了,然后即可通过校验后的 Hash 列表校验数据块的完整性。
Merkle Tree 可以看作 Hash List 的泛化( Hash List 可以看作一种特殊的Merkle Tree,,即树高为 2 的多叉 Merkle Tree)
在最底层,和 Hash 列表一样,把数据分成小的数据块,有相应的 Hash 与它对应。但是往上走,并不是直接去运算根 Hash,而是把相邻的两个 Hash 合并成一个字符串,然后运算这个字符串的 Hash。
如果最底层的 Hash 总数是单数,那到最后必然出现一个“单身 Hash”,这种情况就直接对它进行 Hash 运算,所以也能得到它的“子Hash”,于是往上推,依然是一样的方式,可以得到数目更少的新一级 Hash,最终形成一棵倒挂的树,树根位置就是树的根 Hash,我们把它称为 Merkle Root。
Merkle Tree的特点
Merkle Tree 是一种树,大多数是二叉树,也可以是多叉树。
1)Merkle Tree 的叶子节点的 value 是数据集合的单元数据或者单元数据 Hash。
2)非叶子节点的 value 是根据它下面所有的叶子节点值,按照哈希算法计算而得出的。
Merkle Tree 和 Hash List 的主要区别是,可以直接下载并立即验证 Merkle Tree 的一个分支。 如果文件非常大,那么 Merkle Tree 和 Hash List都很大,但是 Merkle Tree 可以一次下载一个分支,然后立即验证这个分支,如果分支验证通过,就可以下载数据了;而 Hash List 只有下载整个 Hash List 才能验证。
Merkle Tree的应用
数字签名 每一个 Lamport 密钥只能被用来签名一个消息,但是与 Merkle Tree 结合起来可以签名多个消息。这种方法成为一种高效的数字签名框架,即 Merkle签名方法。
P2P 网络: 在 P2P 网络中,Merkle Tree 用来确保从其他节点接收的数据块没有损坏且没有被替换,甚至检查其他节点不会欺骗或者发布虚假的块。
BitTorrent因为为了保持 torrent 文件非常小那么数据块 Hash 的数量也得很小,这就意味着每个数据块相对较大。大数据块影响节点之间进行交易的效率,因为只有当大数据块全部下载下来并通过校验后,才能与其他节点进行交易。
为解决上面两个问题:用一个简单的 Merkle Tree 代替 Hash List。设计一个层数足够多的满二叉树,叶节点是数据块的 Hash,不足的叶节点用0来代替。上层的节点是其对应孩子节点串联的 Hash。Hash 算法和普通 torrent 一样采用 SHA-1
比特币: Bitcoin 的 Blockchain 利用Merkle proofs 来存储每个区块的交易。好处也就是中本聪描述到的“简化支付验证”
一个“轻客户端”(lightclient)可以仅下载链的区块头,即每个区块中的 80 字节的数据块,仅包含 5 个元素,而不是下载每一笔交易以及每一个区块。
5 个元素为上一区块头的 Hash 值、时间戳、挖矿难度值、工作量证明随机数(nonce)以及包含该区块交易的 Merkle Tree 的根 Hash。
如果客户端想要确认一个交易的状态,它只需简单地发起一个Merkle Proof请求,这个请求显示出这个特定的交易在 Merkle Tree 的一个叶子节点之中,而且这个 Merkle Tree 的树根在主链的一个区块头中。
Bitcoin 如何查询你当前有多少币?
一个比特币轻客户端可以使用一种协议,它涉及查询多个节点,并相信其中至少会有一个节点会通知你关于你的地址中任何特定的交易支出,而这可以让你实现更多的应用。但对于其他更为复杂的应用而言,这些是远远不够的。影响一笔交易的确切性质(precise nature),取决于此前的几笔交易,而这些交易本身则依赖于更为前面的交易,所以最终你可以验证整个链上的每一笔交易。
Merkle DAG
Merkle DAG 不需要进行树的平衡操作、非叶子节点允许包含数据等。Merkle DAG是IPFS 的核心概念。Merkle DAG 也是 Git、Bitcoin 等技术的核心。
散列树由内容块组成,每个内容块由其加密散列标识。你可以使用其散列引用这些块中的任何一个,这允许你构建使用这些子块的散列引用其“子块”的块树。
ipfs add 命令将从你指定的文件的数据中创建 Merkle DAG。这意味着你的文件被分解成块,然后使用“链接节点”以树状结构排列,以将它们连接在一起。给定文件的“散列”实际上是 DAG 中根节点(最上层)的散列。
Merkle DAG的功能
内容寻址: 使用多重 Hash 来唯一识别一个数据块的内容。
防篡改: 可以方便地检查 Hash 值来确认数据是否被篡改。
去重: 由于内容相同的数据块 Hash 值是相同的,很容易去掉重复的数据,节省存储空间。
第 3 条是 IPFS 系统最为重要的一个特性,在 IPFS 系统中,每个 Blob的大小限制在 256KB(暂定为 256KB,这个值可以根据实际的性能需求进行修改)以内,那些相同的数据就能通过 Merkle DAG 过滤掉,只需增加一个文件引用,而不需要占据存储空间。
数据对象格式
IPFS Object 是存储结构,我们前面提到IPFS 会限制每个数据大小在 256KB 以内。
在 IPFS Object 对象里我们保存有两个部分,一个是 Link,用于保存其他的分块数据的引用,另一个是 data,为本对象内容
Link 主要包括 3 个部分,分别是 Link 的名字、Hash和 Size。在这里 Link 只是对一个IPFS Object 的引用,它不再重复存储一个 IPFS 对象了。
数据对象格式
type IPFSObject struct {
links []IPFSLink // link数组
data []byte // 数据内容
}
type IPFSLink struct {
Name string // link的名字
Hash Multihash // 数据的加密哈希值
Size int // 数据大小
}
使用 Git 和 Merkle DAG 的集合会极大减少存储空间消耗。
如果使用 Merkle DAG 来存储,那么修改的内容可能只是很少的一部分。我们不再需要将整个修改后的文件再做一次备份了。这也就是 IPFS节省存储空间的原因。
IPLD
IPLD 是基于内容寻址的数据模型的抽象层,IPLD 能够连接起各类以内容寻址为主的数据结构.
设计 IPLD 的初衷是希望这一数据结构不仅能应用于 IPFS,而且能为其他通过哈希类型检索的数据提供一个通用的数据模型。
IPLD 实现跨系统和协议的引用,统一该类数据结构。我们主要介绍 IPLD 的数据模型和内容识别符 CID 的格式规则
IPLD 定义了3 种数据类型:
默克尔链接 (Merkle-Links)、默克尔有向无环图 ( Merkle-DAG)和默克尔路径 ( Merkle-Paths)
默克尔链接是连接两个默克尔对象的链接;
默克尔路径是由默克尔链接组成的、用于访问引用对象成员的路径;
默克尔有向无环图的边是默克尔链接,节点是一个对象。
默克尔链接
对象之间的链接对象是目标对象的哈希值引用,通过目标对象的哈希实现.
默克尔链接有如下两个功能:
1)加密完整性验证:用户可以通过对目标对象的哈希来测试数据的完整性。这一特性能广泛应用于安全、可靠的数据交换场景(例如 Git、BitTorrent)。
2)数据结构不可改变: 带有默克尔链接的数据结构在引用后不可以改变。
默克尔路径
默克尔路径是 UNIX 风格的路径,它包括一段默克尔路径的引用,以及对象内或使用另一个默克尔路径遍历到其他对象的引用。
<默克尔路径> ::= /对象名<默克尔路径>
内容识别符(CID)
CID 是一种自描述的内容寻址标识符,它使用哈希来实现内容寻址。 ID 目前有两个版本,分别为 CIDv0和CIDv1。CIDv0 只适用于IPFS 默认的编码规则和加密算法。而 CIDv1 适用算法和编码规则大大增加。
CIDv0 版本中,默认使用二进制编码,长度为 34 字节。对于一个输人 CID,我们首先判断它的长度是否是 46 字节,并且开头是Qm。如果是,那么表示它一定是 CIDvO 格式。
二进制 CID,若其长度是 46 字节,并且前导字节是0x12或者0x20.那么我们就可以确定它是 CIDVO,并且是完整的。
libp2p是什么
libp2p是能帮助你连接各个设备节点的网络通信库,两个节点只要它们有物理上连接的可能性,那么libp2p就会帮你完成这个连接。
libp2p 还是一个工具库,平常在做软件开发的时候,不仅要关注底层(例如:TCP 连接),还需要关注连接状态等信息。
ibp2p 抽象集成了所有开发者基本都需要的一些工具属性功能:
- 节点之间的链接复用;
- 节点信息之间的互相交换;
- 指定中继节点;
- 网络地址转换 (NAT);
- 分布式哈希表 (dht) 寻址;
- 消息往返时延(RTT) 统计等
IPFS 的很多功能就是对 libp2p 的一个简单抽象与包装。如果你有一些新的想法,完全可以基于 libp2p 库实现一个新的 IPFS 或者其他分布式系统。
libp2p现状
libp2p核心组件
第1层是接口层,它帮我们实现了 ID-service、pub-sub、dht、ping 偏应用属性的功能接口,开发人员只需关注这一层,即可快速上手,使用libp2p进行开发。
第2 层是 host 层,分为 routed host 和 basic host,这两个 host 是继承关系,routed 是 basic 的一个扩展实现。
在 libp2p中,一个host代表一个节点,所以在 IPFS 中,以host为单位进行数据分发与传输。接下来,我们将以 host层为顶,自底向上逐一介绍 libp2p 的核心组件
transport(传输层)
位于应用层和传输层中间,将 Websocket、TCP、UTP、Quic 等主流传输协议封装起来。
这有两个好处,
一是需要兼容目前主流的传输协议,但随着技术的发展,协议是不断演进的,我们都想尽可能地把一些协议演进的变化放人一个专门的模块来适配;
二是现有的传输 IP 地址和协议是分开进行的,传输层的核心是把 IP 地址和传输协议抽象为一个统一的接口,对外只要匹配好接口就可以按照原来的方式使用。
upgrader (升级器)
在 HTTPS 协议中,底层是 TCP,上面加了一个加密套接字层,其实这个加密套接字层就是 upgrader。
但是在 libp2p 中,它的功能更多一些,有 4 层。
libp2p TCP 连接过程中将会建立一个完整链接,需先经过 filter(filter 是一个地址过滤器),再经过 protector(私网),之后经过 secure(加密层),最后经过 muxer (复用机制)。
-
filter upgrader: 该层非常简单,用来判断一个地址是否在黑白名单里面。
-
protector upgrader: 本层称为保护网络,也可以称为私有网络。
它的运行原理是,在私有网络环境下,首先需要生成一个密钥,并分发到所有需要连接到该私有网络的节点之中。节点使用该密钥进行初始化。通信的时候,节点之间首先互相交换一个随机数,再利用该随机数和密钥来加密数据传输。私有网络是通过密钥进行连接的,如果没有密钥,则无法连接进行通信。
- secure upgrader: 该层类似 TLS 的加密链接层。 非对称加密用来握手,对称加密用来加密信道。
例如,节点之间的三次握手,第1次握手互换信息(公钥、nonce、节点支持的非对称加密的列表和对称加密列表、支持的哈希方式列表)。完成协商之后开始第2次握手,交换信息 (临时密钥、签名信息),根据对方的公钥进行加密;对方收到数据后使用自己的私钥去解密即可。至此,节点之间就完成了可用的通信密钥交换。第3次握手验证信息,验证双方有没有按照正确的方式完成信息交换。
- mux upgrader: 链路复用层,顾名思义其功能是复用链路,在一个链路上可以打开多个链接。
relay transport
主要涉及 NAT 的问题,中继方案实现方式值得注意的一点是:不管中继listener监听到了多少个物理链接,底层对应的都是一个物理链接,所以在中继场景下的链接都是轻量级的。
peer-store
peerstore 类似于生活中的电话本,记录了所有“联系人”相关信息
比如: key book 记录公私钥信息;
metrics记录链接的耗时时间。通过加权平均值的方式对该节点进行评估。
addr book 是地址信息,默认实现里面带超时的地址信息。当然这个超时时间可以设为零。
data store起到给地址打标签的作用。
swarm
swarm 是 libp2p 的核心组件之一,因为它是真正的网络层,所有与网络相关的组件全部位于 swarm 组件里面,地址簿、链接、监听器等组件都在这里进行管理。
swarm有回调机制,当有一个新的 stream 进来,调用中转函数进行逻辑处理。
transport管理功能可对多种transport管理,以实现更灵活的功能。
dialer 是拨号器,它包含 3 种拨号器: 同步拨号器、后台限制拨号器和有限制的拨号器。
NAT
NAT (Network Address Translation,网络地址转换) NAT 实际完成的是网络地址映射功能。该功能允许处于内网的网络设备充当服务器,可以被网络上其他设备访问 但是,实际中的网络非常复杂,NAT 并不总能成功。目前libp2p 实现了两种协议: Upnp 和 NAT-pmp,且在仅有一层 NAT 的时候有可能成功。
host
节点初始化
节点初始化时,首先用户配置信息,例如节点支持哪些传输协议。然后使用这些信息,生成一个新的 host,构建一个地址本。通过地址本构建网络层,再通过网络层构建 host,整个网络层就完成了初始化。
监听
监听过程是从初始链接到用户可用链接的过程。filter 到私网链接到构建加密传输到选择多路复用协议再到conn 这一流程就是 upgrader 的过程。
libp2p的用途
- 物联网
- 区块链
- 分布式消息
- 传输文件