IPFS 和區塊鏈有着很是緊密的聯繫, 隨着區塊鏈的不斷髮展,對數據的存儲需求也愈來愈高。本文從IPFS 的底層設計出發, 結合源代碼, 分析了IPFS 的一些技術細節。javascript
IPFS 和區塊鏈有着很是緊密的聯繫, 隨着區塊鏈的不斷髮展,對數據的存儲需求也愈來愈高, 因爲性能和成本的限制,現有的區塊鏈設計方案大部分都選擇了把較大的數據存儲在鏈外,經過對數據進行加密, 哈希運算等手段來防止數據被篡改, 在區塊鏈上只引用所存數據的hash 值, 從而知足業務對數據的存儲需求。 本文從IPFS 的底層設計出發, 結合源代碼, 分析了IPFS 的一些技術細節。 因爲IPFS還在不斷更新中, 文中引用的部分可能和最新代碼有所出入。java
閱讀本文須要讀者node
維基百科上是這樣解釋的:是一個旨在建立持久且分佈式存儲和共享文件的網絡傳輸協議。git
上面的解釋稍顯晦澀, 個人理解是:算法
1. 首先它是一個FS(文件系統)編程
2. 其次它支持點對點傳輸json
既然是文件系統, 那它和普通的文件系統有什麼區別呢? 有如下幾點區別:bootstrap
那麼問題來了, 既然文件被切分紅了多個block,如何組織這些block 數據,組成邏輯上的文件呢? 在IFPS中採用的merkledag, 下面是 merkledag的一個示意圖:瀏覽器
簡單來講, 就是2種數據結構merkle 和DAG(有向無環圖)的結合, 經過這種邏輯結構, 能夠知足:安全
肯定了數據模型後, 接下來要作的事: 如何把數據分發到不一樣的網絡節點上, 達到分佈式存儲和共享的目的? 咱們先思考一下, 經過網絡,好比HTTP, 訪問某個文件的步驟,首先咱們要知道存儲這個文件的服務器地址, 而後咱們須要知道這個文件對應的ID, 好比文件名。前者咱們能夠抽象成網絡節點尋址, 後者咱們抽象成文件對象尋址; 在IPFS中, 這兩種尋址方式使用了相同的算法, KAD, 介紹KAD算法的文章不少,這裏不贅述, 只簡單說明一下核心思想:
KAD 最精妙之處就是使用XOR 來計算ID 之間的距離,而且統一了節點ID 和 對象ID的尋址方式。 採用 XOR(按比特異或操做)算法計算 key 之間的「距離」。
這種作法使得它具有了相似於「幾何距離」的某些特性(下面用 ⊕ 表示 XOR)
經過KAD算法,IPFS 把不一樣ID的數據塊分發到與之距離較近的網絡節點中,達到分佈式存儲的目的。
經過IPFS獲取文件時,只須要根據merkledag, 按圖索驥,根據每一個block的ID, 經過KAD算法從相應網絡節點中下載block數據, 最後驗證是否數據完整, 完成拼接便可。
下面咱們再從技術實現的角度作更深刻的介紹。
咱們先看一下IPFS的系統架構圖, 分爲5層:
站在數據的角度來看, 又能夠分爲2個大的模塊:
下面分別介紹IFPS 中的2個主要部分IPLD 和 libP2P。
1.IPLD
經過hash 值來實現內容尋址的方式在分佈式計算領域獲得了普遍的應用, 好比區塊鏈, 再好比git repo。 雖然使用hash 鏈接數據的方式有類似之處, 可是底層數據結構並不能通用, IPFS 是個極具野心的項目, 爲了讓這些不一樣領域之間的數據可互操做, 它定義了統一的數據模型IPLD, 經過它, 能夠方便地訪問來自不一樣領域的數據。
前面已經介紹數據的邏輯結構是用merkledag表示的, 那麼它是如何實現的呢? 圍繞merkledag做爲核心, 它定義瞭如下幾個概念:
圍繞這些定義它實現了下面幾個components
咱們知道,數據是多樣性的,爲了給不一樣的數據建模, 咱們須要一種通用的數據格式, 經過它能夠最大程度地兼容不一樣的數據, IPFS 中定義了一個抽象的集合, multiformat, 包含multihash、multiaddr、multibase、multicodec、multistream幾個部分。
(一)multihash
自識別hash, 由3個部分組成,分別是:hash函數編碼、hash值的長度和hash內容, 下面是個簡單的例子:
這種設計的最大好處是很是方便升級,一旦有一天咱們使用的hash 函數再也不安全了, 或者發現了更好的hash 函數,咱們能夠很方便的升級系統。
(二)multiaddr
自描述地址格式,能夠描述各類不一樣的地址
(三)multibase
multibase 表明的是一種編碼格式, 方便把CID 編碼成不一樣的格式, 好比這裏定義了2進制、8進制、10進制、16進制、也有咱們熟悉的base58btc 和 base64編碼。
(四)multicodec
mulcodec 表明的是自描述的編解碼, 實際上是個table, 用1到2個字節定了數據內容的格式, 好比用字母z表示base58btc編碼, 0x50表示protobuf 等等。
五)multistream
multistream 首先是個stream, 它利用multicodec,實現了自描述的功能, 下面是基於一個javascript 的例子; 先new 一個buffer 對象, 裏面是json對象, 而後給它加一個前綴protobuf, 這樣這個multistream 就構造好了, 能夠經過網絡傳輸。在解析時能夠先取codec 前綴,而後移除前綴, 獲得具體的數據內容。
結合上面的部分, 咱們重點介紹一下CID。
CID 是IPFS分佈式文件系統中標準的文件尋址格式,它集合了內容尋址、加密散列算法和自我描述的格式, 是IPLD 內部核心的識別符。目前有2個版本,CIDv0 和CIDv1。
CIDv0是一個向後兼容的版本,其中:
爲了更靈活的表述ID數據, 支持更多的格式, IPLD 定義了CIDv1,CIDv1由4個部分組成:
IPLD 是IPFS 的數據描述格式, 解決了如何定義數據的問題, 下面這張圖是結合源代碼整理的一份邏輯圖,咱們能夠看到上面是一些高級的接口, 好比file, mfs, fuse 等。 下面是數據結構的持久化部分,節點之間交換的內容是以block 爲基礎的, 最下面就是物理存儲了。好比block 存儲在blocks 目錄, 其餘節點之間的信息存儲在leveldb, 還有keystore, config 等。
2.數據如何傳輸呢?
接下來咱們介紹libP2P, 看看數據是如何傳輸的。libP2P 是個模塊化的網絡協議棧。
作過socket編程的小夥伴應該都知道, 使用raw socket 編程傳輸數據的過程,無非就是如下幾個步驟:
libP2P 也是這樣,不過區別在於它把各個部分都模塊化了, 定義了通用的接口, 能夠很方便的進行擴展。
(一)架構圖
由如下幾個部分組成,分別是:
下面咱們對它們作分別介紹, 咱們先看關鍵的路由部分。
(二)Peer Routing
libP2P定義了routing 接口,目前有2個實現,分別是KAD routing 和 MDNS routing, 擴展很容易, 只要按照接口實現相應的方法便可。
ipfs 中的節點路由表是經過維護多個K-BUCKET來實現的, 每次新增節點, 會計算節點ID 和自身節點ID 之間的common prefix, 根據這個公共前綴把節點加到對應的KBUCKET 中, KBUCKET 最大值爲20, 當超出時,再進行拆分。
更新路由表的流程以下:
除了KAD routing 以外, IPFS 也實現了MDNS routing, 主要用來在局域網內發現節點, 這個功能相對比較獨立, 因爲用到了多播地址, 在一些公有云部署環境中可能沒法工做。
(三)Swarm(傳輸和鏈接)
swarm 定義瞭如下接口:
下面咱們重點看下是如何動態協商stream protocol 的,整個流程以下:
另外爲了提升協商效率, 也提供了一個ls 消息, 用來查詢目標節點支持的所有協議。
(四)Distributed Record Store
record 表示一個記錄, 能夠用來存儲一個鍵值對,好比ipns name publish 就是發佈一個objectId 綁定指定 node id 的record 到ipfs 網絡中, 這樣經過ipns 尋址時就會查找對應的record, 再解析到objectId, 實現尋址的功能。
(五)Discovery
目前系統支持3種發現方式, 分別是:
最後總結一下源代碼中的邏輯模塊:
從下到上分爲5個層次:
本文從定義數據和傳輸數據的角度分別介紹了IPFS的2個主要模塊IPLD 和 libP2P:
這兩部分相輔相成, 雖然都源自於IPFS項目,可是也能夠獨立使用在其餘項目中。
IPFS的遠景目標就是替換如今瀏覽器使用的 HTTP 協議, 目前項目還在迭代開發中, 一些功能也在不斷完善。爲了解決數據的持久化問題, 引入了filecoin 激勵機制, 經過token激勵,讓更多的節點加入到網絡中來,從而提供更穩定的服務。