一乘碑、概述
IPFS 和區(qū)塊鏈有著非常緊密的聯(lián)系跪解, 隨著區(qū)塊鏈的不斷發(fā)展悔常,對數(shù)據(jù)的存儲需求也越來越高怀吻, 由于性能和成本的限制瞬浓,現(xiàn)有的區(qū)塊鏈設(shè)計(jì)方案大部分都選擇了把較大的數(shù)據(jù)存儲在鏈外,通過對數(shù)據(jù)進(jìn)行加密蓬坡, 哈希運(yùn)算等手段來防止數(shù)據(jù)被篡改猿棉, 在區(qū)塊鏈上只引用所存數(shù)據(jù)的hash 值, 從而滿足業(yè)務(wù)對數(shù)據(jù)的存儲需求。 本文從IPFS 的底層設(shè)計(jì)出發(fā)屑咳, 結(jié)合源代碼萨赁, 分析了IPFS 的一些技術(shù)細(xì)節(jié)。 由于IPFS還在不斷更新中兆龙, 文中引用的部分可能和最新代碼有所出入杖爽。
閱讀本文需要讀者
- 了解網(wǎng)絡(luò)編程
- 了解分布式存儲
- 了解基本的區(qū)塊鏈知識
二、什么是IPFS紫皇?
維基百科上是這樣解釋的:是一個旨在創(chuàng)建持久且分布式存儲和共享文件的網(wǎng)絡(luò)傳輸協(xié)議慰安。
上面的解釋稍顯晦澀, 我的理解是:
首先它是一個FS(文件系統(tǒng))
其次它支持點(diǎn)對點(diǎn)傳輸
既然是文件系統(tǒng)聪铺, 那它和普通的文件系統(tǒng)有什么區(qū)別呢化焕? 有以下幾點(diǎn)區(qū)別:
- 存儲方式: 它是分布式存儲的, 為了方便傳輸,文件被切分成多個block, 每個block 通過hash運(yùn)算得到唯一的ID铃剔, 方便在網(wǎng)絡(luò)中進(jìn)行識別和去重撒桨。 考慮到傳輸效率, 同一個block 可能有多個copy, 分別存儲在不同的網(wǎng)絡(luò)節(jié)點(diǎn)上键兜。
- 內(nèi)容尋址方式: 每個block都有唯一的ID凤类,我們只需要根據(jù)節(jié)點(diǎn)的ID 就可以獲取到它所對應(yīng)的block。
那么問題來了普气, 既然文件被切分成了多個block谜疤,如何組織這些block 數(shù)據(jù),組成邏輯上的文件呢? 在IFPS中采用的merkledag, 下面是 merkledag的一個示意圖:
簡單來說, 就是2種數(shù)據(jù)結(jié)構(gòu)merkle 和DAG(有向無環(huán)圖)的結(jié)合茎截, 通過這種邏輯結(jié)構(gòu)苇侵, 可以滿足:
- 內(nèi)容尋址: 使用hash ID來唯一識別一個數(shù)據(jù)塊的內(nèi)容
- 防篡改: 可以方便的檢查哈希值來確認(rèn)數(shù)據(jù)是否被篡改
- 去重: 由于內(nèi)容相同的數(shù)據(jù)塊哈希是相同的,可以很容去掉重復(fù)的數(shù)據(jù)企锌,節(jié)省存儲空間
確定了數(shù)據(jù)模型后, 接下來要做的事: 如何把數(shù)據(jù)分發(fā)到不同的網(wǎng)絡(luò)節(jié)點(diǎn)上于未, 達(dá)到分布式存儲和共享的目的撕攒? 我們先思考一下, 通過網(wǎng)絡(luò)烘浦,比如HTTP抖坪, 訪問某個文件的步驟,首先我們要知道存儲這個文件的服務(wù)器地址闷叉, 然后我們需要知道這個文件對應(yīng)的ID擦俐, 比如文件名。前者我們可以抽象成網(wǎng)絡(luò)節(jié)點(diǎn)尋址握侧, 后者我們抽象成文件對象尋址蚯瞧; 在IPFS中, 這兩種尋址方式使用了相同的算法品擎, KAD, 介紹KAD算法的文章很多埋合,這里不贅述, 只簡單說明一下核心思想:
KAD 最精妙之處就是使用XOR 來計(jì)算ID 之間的距離萄传,并且統(tǒng)一了節(jié)點(diǎn)ID 和 對象ID的尋址方式甚颂。 采用 XOR(按比特異或操作)算法計(jì)算 key 之間的“距離”。
這種做法使得它具備了類似于“幾何距離”的某些特性(下面用 ⊕ 表示 XOR)
- (A ⊕ B) == (B ⊕ A) XOR 符合“交換律”秀菱,具備對稱性振诬。
- (A ⊕ A) == 0 反身性,自身距離為零
- (A ⊕ B) > 0 【不同】的兩個 key 之間的距離必大于零
- (A ⊕ B) + (B ⊕ C) >= (A ⊕ C) 三角不等式
通過KAD算法衍菱,IPFS 把不同ID的數(shù)據(jù)塊分發(fā)到與之距離較近的網(wǎng)絡(luò)節(jié)點(diǎn)中赶么,達(dá)到分布式存儲的目的。
通過IPFS獲取文件時梦碗,只需要根據(jù)merkledag, 按圖索驥禽绪,根據(jù)每個block的ID, 通過KAD算法從相應(yīng)網(wǎng)絡(luò)節(jié)點(diǎn)中下載block數(shù)據(jù), 最后驗(yàn)證是否數(shù)據(jù)完整洪规, 完成拼接即可印屁。
下面我們再從技術(shù)實(shí)現(xiàn)的角度做更深入的介紹。
三斩例、IPFS的系統(tǒng)架構(gòu)
我們先看一下IPFS的系統(tǒng)架構(gòu)圖雄人, 分為5層:
- 一層為naming, 基于PKI的一個命名空間;
- 第二層為merkledag础钠, IPFS 內(nèi)部的邏輯數(shù)據(jù)結(jié)構(gòu)恰力;
- 第三層為exchange, 節(jié)點(diǎn)之間block data的交換協(xié)議;
- 第四層為routing, 主要實(shí)現(xiàn)節(jié)點(diǎn)尋址和對象尋址旗吁;
- 第五層為network, 封裝了P2P通訊的連接和傳輸部分踩萎。
站在數(shù)據(jù)的角度來看, 又可以分為2個大的模塊:
- IPLD( InterPlanetary Linked Data) 主要用來定義數(shù)據(jù)很钓, 給數(shù)據(jù)建模香府;
- libp2p解決的是數(shù)據(jù)如何傳輸?shù)膯栴}。
下面分別介紹IFPS 中的2個主要部分IPLD 和 libP2P码倦。
1.IPLD
通過hash 值來實(shí)現(xiàn)內(nèi)容尋址的方式在分布式計(jì)算領(lǐng)域得到了廣泛的應(yīng)用企孩, 比如區(qū)塊鏈, 再比如git repo袁稽。 雖然使用hash 連接數(shù)據(jù)的方式有相似之處勿璃, 但是底層數(shù)據(jù)結(jié)構(gòu)并不能通用, IPFS 是個極具野心的項(xiàng)目推汽, 為了讓這些不同領(lǐng)域之間的數(shù)據(jù)可互操作补疑, 它定義了統(tǒng)一的數(shù)據(jù)模型IPLD, 通過它民泵, 可以方便地訪問來自不同領(lǐng)域的數(shù)據(jù)癣丧。
前面已經(jīng)介紹數(shù)據(jù)的邏輯結(jié)構(gòu)是用merkledag表示的, 那么它是如何實(shí)現(xiàn)的呢栈妆? 圍繞merkledag作為核心胁编, 它定義了以下幾個概念:
- merkle link 代表dag 中的邊
- merkel-dag 有向無環(huán)圖
- merkle-path 訪問dag節(jié)點(diǎn)的類似unix path的路徑
- IPLD data model 基于json 的數(shù)據(jù)模型
- IPLD serialized format 序列化格式
- canonical 格式: 為了保證同樣的logic object 總是序列化為一個同樣的輸出, 而制定的確定性規(guī)則
圍繞這些定義它實(shí)現(xiàn)了下面幾個components
- CID 內(nèi)容ID
- data model 數(shù)據(jù)模型
- serialization format 序列化格式
- tools & libraries 工具和庫
- IPLD selector 類似CSS 選擇器鳞尔, 方便選取dag中的節(jié)點(diǎn)
- IPLD transformation 對dag 進(jìn)行轉(zhuǎn)換計(jì)算
我們知道嬉橙,數(shù)據(jù)是多樣性的,為了給不同的數(shù)據(jù)建模寥假, 我們需要一種通用的數(shù)據(jù)格式市框, 通過它可以最大程度地兼容不同的數(shù)據(jù), IPFS 中定義了一個抽象的集合糕韧, multiformat, 包含multihash枫振、multiaddr、multibase萤彩、multicodec粪滤、multistream幾個部分。
(一)multihash
自識別hash, 由3個部分組成雀扶,分別是:hash函數(shù)編碼杖小、hash值的長度和hash內(nèi)容, 下面是個簡單的例子:
這種設(shè)計(jì)的最大好處是非常方便升級,一旦有一天我們使用的hash 函數(shù)不再安全了予权, 或者發(fā)現(xiàn)了更好的hash 函數(shù)昂勉,我們可以很方便的升級系統(tǒng)。
(二)multiaddr
自描述地址格式扫腺,可以描述各種不同的地址
(三)multibase
multibase 代表的是一種編碼格式岗照, 方便把CID 編碼成不同的格式, 比如這里定義了2進(jìn)制斧账、8進(jìn)制谴返、10進(jìn)制、16進(jìn)制咧织、也有我們熟悉的base58btc 和 base64編碼。
(四)multicodec
mulcodec 代表的是自描述的編解碼籍救, 其實(shí)是個table习绢, 用1到2個字節(jié)定了數(shù)據(jù)內(nèi)容的格式, 比如用字母z表示base58btc編碼蝙昙, 0x50表示protobuf 等等闪萄。
五)multistream
multistream 首先是個stream, 它利用multicodec奇颠,實(shí)現(xiàn)了自描述的功能败去, 下面是基于一個javascript 的例子; 先new 一個buffer 對象烈拒, 里面是json對象圆裕, 然后給它加一個前綴protobuf, 這樣這個multistream 就構(gòu)造好了, 可以通過網(wǎng)絡(luò)傳輸荆几。在解析時可以先取codec 前綴吓妆,然后移除前綴, 得到具體的數(shù)據(jù)內(nèi)容吨铸。
結(jié)合上面的部分行拢, 我們重點(diǎn)介紹一下CID。
CID 是IPFS分布式文件系統(tǒng)中標(biāo)準(zhǔn)的文件尋址格式诞吱,它集合了內(nèi)容尋址舟奠、加密散列算法和自我描述的格式, 是IPLD 內(nèi)部核心的識別符。目前有2個版本房维,CIDv0 和CIDv1沼瘫。
CIDv0是一個向后兼容的版本,其中:
- multibase 一直為 base58btc
- multicodec 一直為 protobuf-mdag
- version 一直為 CIDv0
- multihash 表示為cidv0 ::= <multihash-content-address>
為了更靈活的表述ID數(shù)據(jù)握巢, 支持更多的格式晕鹊, IPLD 定義了CIDv1,CIDv1由4個部分組成:
- multibase
- version
- multicodec
- multihash
IPLD 是IPFS 的數(shù)據(jù)描述格式, 解決了如何定義數(shù)據(jù)的問題溅话, 下面這張圖是結(jié)合源代碼整理的一份邏輯圖晓锻,我們可以看到上面是一些高級的接口, 比如file, mfs, fuse 等飞几。 下面是數(shù)據(jù)結(jié)構(gòu)的持久化部分砚哆,節(jié)點(diǎn)之間交換的內(nèi)容是以block 為基礎(chǔ)的, 最下面就是物理存儲了屑墨。比如block 存儲在blocks 目錄躁锁, 其他節(jié)點(diǎn)之間的信息存儲在leveldb, 還有keystore, config 等卵史。
2.數(shù)據(jù)如何傳輸呢战转?
接下來我們介紹libP2P, 看看數(shù)據(jù)是如何傳輸?shù)囊郧ibP2P 是個模塊化的網(wǎng)絡(luò)協(xié)議棧槐秧。
做過socket編程的小伙伴應(yīng)該都知道, 使用raw socket 編程傳輸數(shù)據(jù)的過程忧设,無非就是以下幾個步驟:
- 獲取目標(biāo)服務(wù)器地址
- 和目標(biāo)服務(wù)器建立連接
- 握手協(xié)議
- 傳輸數(shù)據(jù)
- 關(guān)閉連接
libP2P 也是這樣刁标,不過區(qū)別在于它把各個部分都模塊化了, 定義了通用的接口址晕, 可以很方便的進(jìn)行擴(kuò)展膀懈。
(一)架構(gòu)圖
由以下幾個部分組成,分別是:
- Peer Routing
- Swarm (傳輸和連接)
- Distributed Record Store
- Discovery
下面我們對它們做分別介紹, 我們先看關(guān)鍵的路由部分谨垃。
(二)Peer Routing
libP2P定義了routing 接口启搂,目前有2個實(shí)現(xiàn),分別是KAD routing 和 MDNS routing, 擴(kuò)展很容易乘客, 只要按照接口實(shí)現(xiàn)相應(yīng)的方法即可狐血。
ipfs 中的節(jié)點(diǎn)路由表是通過維護(hù)多個K-BUCKET來實(shí)現(xiàn)的, 每次新增節(jié)點(diǎn)易核, 會計(jì)算節(jié)點(diǎn)ID 和自身節(jié)點(diǎn)ID 之間的common prefix, 根據(jù)這個公共前綴把節(jié)點(diǎn)加到對應(yīng)的KBUCKET 中, KBUCKET 最大值為20匈织, 當(dāng)超出時,再進(jìn)行拆分牡直。
更新路由表的流程如下:
除了KAD routing 之外缀匕, IPFS 也實(shí)現(xiàn)了MDNS routing, 主要用來在局域網(wǎng)內(nèi)發(fā)現(xiàn)節(jié)點(diǎn), 這個功能相對比較獨(dú)立, 由于用到了多播地址碰逸, 在一些公有云部署環(huán)境中可能無法工作乡小。
(三)Swarm(傳輸和連接)
swarm 定義了以下接口:
- transport 網(wǎng)絡(luò)傳輸層的接口
- connection 處理網(wǎng)絡(luò)連接的接口
- stream multiplex 同一connection 復(fù)用多個stream的接口
下面我們重點(diǎn)看下是如何動態(tài)協(xié)商stream protocol 的,整個流程如下:
- 默認(rèn)先通過multistream-select 完成握手
- 發(fā)起方嘗試使用某個協(xié)議饵史, 接收方如果不接受满钟, 再嘗試其他協(xié)議胜榔, 直到找到雙方都支持的協(xié)議或者協(xié)商失敗。
另外為了提高協(xié)商效率湃番, 也提供了一個ls 消息夭织, 用來查詢目標(biāo)節(jié)點(diǎn)支持的全部協(xié)議。
(四)Distributed Record Store
record 表示一個記錄吠撮, 可以用來存儲一個鍵值對尊惰,比如ipns name publish 就是發(fā)布一個objectId 綁定指定 node id 的record 到ipfs 網(wǎng)絡(luò)中, 這樣通過ipns 尋址時就會查找對應(yīng)的record, 再解析到objectId, 實(shí)現(xiàn)尋址的功能泥兰。
(五)Discovery
目前系統(tǒng)支持3種發(fā)現(xiàn)方式弄屡, 分別是:
- bootstrap 通過配置的啟動節(jié)點(diǎn)發(fā)現(xiàn)其他的節(jié)點(diǎn)
- random walk 通過查詢隨機(jī)生成的peerID, 從而發(fā)現(xiàn)新的節(jié)點(diǎn)
- mdns 通過multicast 發(fā)現(xiàn)局域網(wǎng)內(nèi)的節(jié)點(diǎn)
最后總結(jié)一下源代碼中的邏輯模塊:
從下到上分為5個層次:
- 最底層為傳輸層鞋诗, 主要封裝各種協(xié)議膀捷, 比如TCP,SCTP削彬, BLE担孔, TOR 等網(wǎng)絡(luò)協(xié)議
- 傳輸層上面封裝了連接層,實(shí)現(xiàn)連接管理和通知等功能
- 連接層上面是stream 層吃警, 實(shí)現(xiàn)了stream的多路復(fù)用
- stream層上面是路由層
- 最上層是discovery, messaging以及record store 等
四、總結(jié)
本文從定義數(shù)據(jù)和傳輸數(shù)據(jù)的角度分別介紹了IPFS的2個主要模塊IPLD 和 libP2P:
- IPLD 主要用來定義數(shù)據(jù)啄育, 給數(shù)據(jù)建模
- libP2P 解決數(shù)據(jù)傳輸問題
這兩部分相輔相成酌心, 雖然都源自于IPFS項(xiàng)目,但是也可以獨(dú)立使用在其他項(xiàng)目中挑豌。
IPFS的遠(yuǎn)景目標(biāo)就是替換現(xiàn)在瀏覽器使用的 HTTP 協(xié)議安券, 目前項(xiàng)目還在迭代開發(fā)中, 一些功能也在不斷完善氓英。為了解決數(shù)據(jù)的持久化問題侯勉, 引入了filecoin 激勵機(jī)制, 通過token激勵铝阐,讓更多的節(jié)點(diǎn)加入到網(wǎng)絡(luò)中來址貌,從而提供更穩(wěn)定的服務(wù)。
本文轉(zhuǎn)載自《從數(shù)據(jù)的角度帶你深入了解IPFS》