IPFS 到底是怎么工作的?
简介
我们知道,一个存储服务,最基本的功能就是存和取。IPFS 中提供了这两种语义,那就是 add 和 get 操作。
在 IPFS 系统中执行 add 操作,就是执行了一次存操作,放在网络的概念里,就是“上传”操作。而 get 就更好理解了,就是取操作,在网络世界里,也叫 “下载”。
IPFS 号称点对点无中心化文件系统,没有单点故障,也就是文件一旦被“上传”到 IPFS 网络中,就会被永久保存。而要想下载一个本地没有的文件,只要 IPFS 网络中有,简单的执行 get 就很快能下载到数据。那么 add 操作的背后到底做了什么?get 又是怎么获取数据的?
这就是本文要探究的主题!
先来看一下 add 和 get 的基本操作过程
当一个 IPFS 节点执行 add 操作时,它会把文件进行分块 block,通过构建一个 Merkle 树根节点,来把每个子块节点都连接起来,每个 block 都会用一个唯一的 Cid 进行标识。
block 数据会被保存到本地的 blockstore 中。但是需要注意的是,除此之外,block 数据并不会立刻主动上传到 IPFS 网络中(也即,与其连接的 peers 节点中)。除非,某 peer 节点曾经请求过该 block 数据。
add 执行逻辑如下图所示:
理解这一点非常重要,因为,我们很容易会把 IPFS 想象成一个会自动备份数据的分布式数据库,就像传统的冗余备份机制一样。实际上,IPFS 并不会这样做。这是由 IPFS 在公网环境中运行和传统分布式数据库在私有网络中运行的场景要求不一样所导致的。作为互联网基础设施,这种设计不仅减少网络带宽占用,还能为网络提供可靠、恒久的数据保存机制。
现在就来来了解一下 get 操作背后的原理,先看下图:
上图展示了 ipfs 执行 get 命令的执行流程。
对于当前节点来说,所有与其连接的 peers 节点会构成一个 swarm 网络。
当本地节点发出一个 get 请求时,它首先会从本地的 blockstore 中查找请求的数据,如果没有找到,它便会向 swarm 网络发出请求,通过 DHT Routing 找到拥有该数据的节点,一旦找到一个拥有所请求数据的节点,该节点会把数据反馈回来。然后,本地节点会把收到的 block 数据缓存一份到本地的 blockstore 中,这样,整个网络中就相当于多了一份原数据的拷贝。当有更多的节点都请求该数据的时候,就变得更加容易,而由于越来越多的节点都存有该数据,数据就变得几乎不可丢失。
这也就是 IPFS 网络能够永久保存数据的原理,只要有任何一个 IPFS 节点拥有某数据,这个数据就可以被全网所获取。
那么,执行 IPFS 的 add 命令之后,为什么直接访问 ipfs.io
网关就能获取到数据呢?
比如,在浏览器中打开类似 https://ipfs.io/ipfs/QmR4WZy1rfXX868yFsTcqHun5y61c1jh2oQhDqWD97FEM2 这样的网站地址,就能直接访问到刚才我们添加的数据!
原理是这样的:
IPFS 网关,即 ipfs.io,实际上扮演的是一个 IPFS 节点的作用,当我们打开上述网站的时候,其实就是向 IPFS 网关发出了一次请求,IPFS 网关会代理我们(因为我们不是 IPFS 节点,我们只是浏览器而已)向拥有这个数据的 Peer 节点(就是我们本地节点)发出 get 请求,一旦获取到数据,网关会先自己缓存一份,然后把请求到的数据通过 HTTP 协议转发给我们!
也就是说,任何一台机器,只要打开浏览器,都能通过上述地址访问到我们刚才执行 add 命令时添加的数据。一旦 IPFS 网关第一次缓存节点数据之后,再次请求时,它就无需再向原节点请求数据了,只要 Hash 值没有变化,就可以直接把之前缓存的数据返回给浏览器。
当然,这个缓存的数据是有时效的,通常是一周左右就会失效。这个是由 ipfs daemon 内置的默认时效所设定。因为作为网关节点,其磁盘容量也是有限的,不可能无限保存所有的数据,采用缓存时效机制不仅能解决资源访问问题,还能避免数据膨胀给节点带了的负担,当越来越多的机器加入 IPFS 网络并且承担网关的作用,那么数据时效的概率就会大大降低。
更多细节
实际上,Peer 节点在执行 add 命令时,还会广播自己拥有的块信息。同时,它还会维护一个该 swarm 网络中所有已发给当前节点的 block 请求列表,一旦 add 命令都添加的数据满足请求列表,就会向对应节点主动发送数据,并更新该列表。
- crontab命令详解
- hdu----(4308)Saving Princess claire_(搜索)
- spark2 sql读取数据源编程学习样例1
- golang的一个分页方法
- spark2 sql读取json文件的格式要求
- 容器化RDS|调度策略
- Go语言并发编程总结
- hdu------(4302)Holedox Eating(树状数组+二分)
- spark2的SparkSession思考与总结2:SparkSession有哪些函数及作用是什么
- GO语言并发编程之互斥锁、读写锁详解
- spark2.2 SparkSession思考与总结1
- 【译】Spring 官方教程:Spring Security 架构
- hdu----(4301)Divide Chocolate(状态打表)
- hdu------(4300)Clairewd’s message(kmp)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- PHP 静态属性和静态方法
- 通过 Trait 水平扩展 PHP 类功能
- 通过对象组合水平扩展 PHP 类功能
- DO,DTO,VO,POJO 你知道吗?
- Python 爬虫进阶必备 | AES-CBC 的 Pyhon 实现要怎么写?给代码就完事了
- Babel:下一代Javascript语法编译器
- 如何定位及优化SQL语句的性能问题
- Java线程安全如何进行原子操作,一致性的最佳实践
- 冒泡排序
- 现有CDP-DC集群启用Auto-TLS
- 打卡001/这是一篇软文
- 技术角 | 在CentOS 8上使用Elastic Stack: Elasticsearch/Kibana 7.8部署与认证配置
- 技术角 | 解决ES SQL命令行启动报错 ./x-pack-env: No such file or directory
- Linux常用命令归类总结
- 潜藏在PHP安全的边缘——浅谈PHP反序列化漏洞