IPFS#
分布式文件系統,原理類似於 bt, 透過文件分塊,每個塊對應 CID 以及各級 Hash 做存儲和校驗,透過 DHT (Distributed Hash Table) 做查找和路由.
IPFS 文檔#
https://docs.ipfs.io/ 主要看 Concepts 和 How-tos 部分
IPFS 對應每個 Content 會產生不同的 CID, 對於需要固定鏈接的需求,可以透過 IPNS 實現,但是 IPNS 不適合於快速變化的內容,IPNS 的更新間隔是以分鐘記的
使用 IPFS 做的一些服務#
- 在線音樂: https://github.com/icidasset/diffuse, https://diffuse.sh/
- 在線視頻: https://github.com/download13/ipfstube, http://www.ipfs.guide/, https://ipfstube.erindachtler.me/ https://ipfs.video/
- 網關測速: https://ipfs.runfission.com/ipns/ipnso.com
- Rick Astley - Never Gonna Give You Up: QmcniBv7UQ4gGPQQW2BwbD4ZZHzN3o3tPuNLZCbBchd1zh
網關#
官方有一個可用的網關列表: https://ipfs.github.io/public-gateway-checker/ 下面是測試過的一些可用網關
- http://ipfs.io
- https://ipfs.greyh.at/ (國內可用)
- https://ninetailed.ninja/ (國內可用,速度不錯)
- dweb.link
- ipfs.runfission.com (國內可用,速度不錯)
- http://trusti.id/
- http://ipfs.globalupload.io/
go-ipfs#
IPFS 的 Go 語言項目是其主要的開源實現: https://github.com/ipfs/go-ipfs/
go-ipfs 的配置說明#
詳細的配置說明在 https://github.com/ipfs/go-ipfs/blob/master/docs/config.md 這個比手冊寫的詳細
編譯 go-ipfs#
參考 https://github.com/ipfs/go-ipfs#download-and-compile-ipfs
Github 項目源代碼有 30 多 MB, clone 的時間會比較長,如果太慢並且出錯,需要加個代理。編譯使用 make build 命令,說是編譯,其實大部分時間是花在下載依賴庫上,
在執行前設置一下 GOPROXY 避免下載超時
# 檢查go版本, 要1.13以上
go version
# 設置代理
git config --global https.proxy 'socks5://127.0.0.1:10080'
# 檢查
git config -l
#
git clone https://github.com/ipfs/go-ipfs.git
cd go-ipfs/
# 設置GOPROXY
export GOPROXY=https://goproxy.cn
# 檢查
echo $GOPROXY
# 編譯
make build
# 檢查編譯結果
./cmd/ipfs/ipfs version
在 Amlogic S905L 的 Armbian 上運行 IPFS#
從 GO IPFS Github 項目下載 Arm64 版本,Armbian 版本為 5.99, 內核 5.30.
# 下載
wget https://github.com/ipfs/go-ipfs/releases/download/v0.5.1/go-ipfs_v0.5.1_linux-arm64.tar.gz
# 解壓
tar xvf go-ipfs_v0.5.1_linux-amd64.tar.gz
# 直接運行 install.sh 安裝, 這個腳本會將ipfs目錄複製到 /usr/local/bin 下
cd go-ipfs
./install.sh
初始化後,使用普通用戶啟動
# 查看版本
ipfs --version
# 初始化節點
ipfs init
# 查看說明
ipfs cat /ipfs/QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc/readme
# 這個命令不會後台運行, 建議先建一個screen session, 在screen session裡執行
ipfs daemon
如果 IPFS 不是運行在當前電腦,那麼配置需要做兩處改動後才能訪問 webui
一處是 Addresses.API 和 Addresses.Gateway
"Addresses": {
...
"API": "/ip4/127.0.0.1/tcp/5001",
"Gateway": "/ip4/127.0.0.1/tcp/8080"
}
將 API 的地址改為 /ip4/0.0.0.0/tcp/5001 這樣會監聽所有網口,如果你的伺服器有公網 IP, 這樣會有安全問題,可以將這個 IP 設為內網網口地址.
將 Gateway 的地址改為內網地址或公網地址,如果 IPFS 處於內網,外網可以透過 NAT 訪問,這個可以配成內網地址.
另一處是 API.HTTPHeaders, 避免從當前電腦訪問 IPFS 的 webui 時,出現 cross site 錯誤.
"API": {
"HTTPHeaders": {}
},
在 HTTPHeaders 裡增加兩項,可以參考 Gateway.HTTPHeaders
"Access-Control-Allow-Methods": [
"GET","PUT","POST"
],
"Access-Control-Allow-Origin": [
"*"
]
修改完配置,重啟 IPFS 後就可以訪問 http://IP:5001/webui/ 可以圖形化的查看當前節點狀態,存儲的塊數量和大小,連接的節點明細,以及查看當前的配置。這個界面甚至提供了一些演示用的文件瀏覽.
透過 Gateway, 可以訪問 IPFS 上的文件以及遠程可以查詢到 CID 的文件。任何訪問過的文件塊都會被緩存,再次訪問時,相應的塊會直接走緩存,不會再從遠程下載.
選擇文件上傳後,會在頁面後台進行傳輸 (尚未確認是否可以關閉標籤頁), 在狀態頁能看到總存儲在增長。對於不同大小的文件,其發布可見的延遲是不一樣的,對於 10MB 以內的文件,透過遠程 gateway 很快就能發現,對於接近 500MB 的文件,大概需要半個小時到一小時。這和節點發布自身的內容 CID 到其他節點的機制有關,因為單個文件塊為 256KB (262144Byte), 對於 500MB 大小的文件,會產生 2,000 多個 CID, 發布需要的時間會長得多.
在 webui 上添加文件,需要等文件上傳完整後,才能做 pin 操作,如果是透過 CID 上傳文件,在提交 CID 後並不會觸發文件同步,對這個 CID 進行 pin 操作後,會觸發 ipfs 開始從其他節點同步,等文件從其他節點同步完成後,會出現在 pin 列表.
安裝到服務#
創建文件 /etc/systemd/system/ipfs.service 寫入
[Unit]
Description=IPFS Daemon
After=syslog.target network.target remote-fs.target nss-lookup.target
[Service]
Type=simple
ExecStart=/usr/local/bin/ipfs daemon --enable-namesys-pubsub
User=milton
[Install]
WantedBy=multi-user.target
然後透過 systemctl 添加
配置說明#
IPFS 對外提供的端口主要是 3 個: API, Gateway 和 Swarm, 其中
- API: 默認使用端口 5001, 在上面提供了 webui, 用於管理控制 IPFS. 設置監聽網口時應當注意不要開放到公網
- Gateway: 默認使用端口 8080, 在上面提供 ipfs/CID 的內容查找下載服務
- Swarm: 默認使用端口 4001, 這個端口用於監聽其他 IPFS 節點的請求
- Addresses.NoAnnounce 添加需要排除的內網 IP, 這些 IP 不會發布,但是注意不要排除 127.0.0.1 和::1, 這兩個貌似被用於其他節點檢測當前節點是否支持 ipv4 或 ipv6, 如果排除掉,會無法與其他節點保持連接 (能 ping 能 connect, 但是 swarm peers 裡查不到)
- Swarm.AddrFilters 添加需要忽略的內網 IP, 對於在 ID 上 announce 內網 IP 的 peer, 這些內網 IP 的地址會被過濾掉
- Discovery.MDNS.Enabled 將這個選項改為 false, 就不會在內網發起節點搜索
- Peering.Peers 添加需要保持連接的節點
固定節點#
對於自建的網絡,需要讓自己的節點之間保持連接,但是 IPFS 默認的機制下,即使將自建節點設置為 bootstrap, 在啟動一段時間後,在連接的節點不斷增加後,依然會將與其他自建節點之間的連接關閉。要保持連接,需要使用配置文件中的 Peering 部分,格式如下,第二個為 ipfs.runfission.com 的接入地址
{
"Peering": {
"Peers": [
{
"ID": "QmPeerID1",
"Addrs": ["/ip4/18.1.1.1/tcp/4001"]
},
{
"ID": "QmVLEz2SxoNiFnuyLpbXsH6SvjPTrHNMU88vCQZyhgBzgw",
"Addrs": ["/ip4/3.215.160.238/tcp/4001", "/ip4/3.215.160.238/udp/4001/quic"]
}
]
}
...
}
使用 Peering 配置的節點:
- 在連接管理中會保護與這個節點之間的連接,IPFS 永遠不會主動 (自動) 關閉這個連接,在連接數達到上限時也不會關閉這個連接.
- 在啟動時就會建立連接
- 如果因為網絡原因或對方節點掉線等原因導致連接丟失,IPFS 會不斷嘗試重新連接,嘗試的間隔長度在 5 秒至 10 分鐘之間隨機.
在公網 NAT 下運行 IPFS#
運行環境#
公網 IP 的伺服器是 Centos7, 公網 IP 為 118.119.120.121, 內網 IP 為 192.168.13.10
內網伺服器 Ubuntu18.04, 內網 IP 為 192.168.13.25
在公網伺服器上設置端口轉發#
配置一下全局轉發開關
firewall-cmd --permanent --zone=public --add-masquerade
將公網 IP 端口 4002 轉發至內網伺服器的 4001 端口
# TCP端口轉發
firewall-cmd --permanent --zone=public --add-forward-port=port=4002:proto=tcp:toaddr=192.168.13.25:toport=4001
# UDP端口轉發
firewall-cmd --permanent --zone=public --add-forward-port=port=4002:proto=udp:toaddr=192.168.13.25:toport=4001
# 設置生效
firewall-cmd --reload
# 檢查
firewall-cmd --zone=public --list-all
如果網關是 OpenWRT 路由,則直接在防火牆 -> 端口轉發中添加轉發規則即可。注意在添加完轉發規則後,還需要在通信規則中允許 WAN 到此設備此端口的訪問.
限制連接的節點數量#
需要透過 Swarm/ConnMgr/HighWater 這個參數,在 0.6.0 時這個設置並不能很好地工作,長時間運行伺服器後節點數量會遠超設置的限制,在 0.7.0 上是有效的
"Swarm": {
...
"ConnMgr": {
"GracePeriod": "30s",
"HighWater": 500,
"LowWater": 100,
"Type": "basic"
},
...
}
在內網伺服器上配置 IPFS 服務
# 安裝過程略
# 初始化為伺服器模式
ipfs init --profile=server
# 修改配置, 看下面具體說明
vi .ipfs/config
# 啟動
ipfs daemon
伺服器模式和普通模式相比,有幾點變化
- Addresses.NoAnnounce 伺服器模式會列出所有的內網 IP, 這些 IP 不會發布
- Swarm.AddrFilters 伺服器模式會列出所有內網 IP, 對於使用內網 IP 來連接的 peer, 都會被過濾掉
- Discovery.MDNS.Enabled 伺服器模式時這個選項為 false, 避免在內網發起節點搜索
需要配置的地方除了普通節點的 API, Gateway 和 API HTTPHeaders, 還需要配置 Addresses.Announce , 需要添加本節點的公網 IP 和端口
"Announce": [
"/ip4/118.119.120.121/tcp/4002",
"/ip4/118.119.120.121/udp/4002/quic"
],
遇到的問題#
使用 OpenWRT 配置的網關不存在這個問題,但是使用 Centos 做網關時似乎不能將外網 IP 正確傳入,導致大量第三方節點的顯示 IP 為網關 IP, 對本節點連接可以成功,但是 ping 失敗。所以對於 Swarm.AddrFilters 裡面的列表要刪除同網段的部分.
具體原因可以透過本節點的 swarm peers 看到,從本節點主動連接的節點,記錄的是公網 IP, 但是本節點被動連接 (即透過網關 118.119.120.121 連接) 的節點,記錄的都是網關的內網 IP, 按照 AddrFilters 的規則,這些節點會被丟棄。就會出現 ipfs swarm connect 成功,但是 ipfs ping 失敗的問題.
$ ipfs swarm peers
/ip4/104.131.131.82/udp/4001/quic/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ
/ip4/111.231.85.77/tcp/4001/p2p/QmWv1eLMNHPpwYKzREQBEpDfYjW6YXCrVpVyBZVjAuSd2i
...
/ip4/192.168.13.10/tcp/10041/p2p/QmXUZth5Pr2u1cW65F7gUeFwjkZFfduE1dwqiysNnrTwXd
/ip4/192.168.13.10/tcp/10053/p2p/QmPM3bepMUKpYTza67coD1Ar3efL7FPBFbGRMc42QLf4q9
/ip4/192.168.13.10/tcp/10202/p2p/QmVBzjW2MyNrSuR48GjcqB6SAJTnw8Y9zaFbgbegh4bRx4
/ip4/192.168.13.10/tcp/1024/p2p/QmbBEfFfw59Vya9CJuzgt4qj9f57sPZGiyK7fup8iKqkTr
/ip4/192.168.13.10/tcp/1025/p2p/QmcmhwCeLkBJvcq6KJzN58BRZg1B1N8m3uA3JyQKpVn64E
...
/ip4/192.168.13.10/udp/6681/quic/p2p/QmcSCBpek4YF5aAsY7bUMxiL7tacoYMeXUJUpU4wctqX4w
/ip4/192.168.13.10/udp/8050/quic/p2p/QmWeuXCNKAHfbineKMqo3U3dvVSz2em1w67pj5Up6tkUXo
/ip4/206.189.69.143/tcp/31071/p2p/12D3KooWHDr5W3Tse17mr4HSzuQm44dVQYp8Bb638mQknsyeHXSP
/ip4/206.189.69.250/tcp/30511/p2p/12D3KooWRd1BNPd8PMfxpCT7TNCFY4XSZsy8v8Cmm36H136yxzub
...
再進一步檢查是否可以從本節點訪問被動連接的節點,取其中一個 Peer ID 做 ping 測試
ipfs ping QmXUZth5Pr2u1cW65F7gUeFwjkZFfduE1dwqiysNnrTwXd
PING QmXUZth5Pr2u1cW65F7gUeFwjkZFfduE1dwqiysNnrTwXd.
Pong received: time=26.43 ms
Pong received: time=25.70 ms
Pong received: time=26.31 ms
...
說明這些記錄為網關內網 IP 的節點是可用的,應當保留
這些透過網關連接的節點,可以分為兩類:無公網 IP 的節點和有公網 IP 的節點:
- 對於有公網 IP 的節點,尚不清楚是否會在初次連接後使用對方 Announce 的地址回 ping, 如果會並且會根據此結果更新節點的地址,那麼這些節點會在這個顯示為內網 IP 的列表中短暫停留後,更新為公網 IP 地址,這樣在其他節點接入後,共享節點信息時,其他節點可以透過其公網 IP 地址進行連接.
- 對於無公網 IP 的節點,伺服器節點根據對方 Announce 的地址回 ping 是無法連接的,只能透過本方網關的內網 IP 進行連接,所以會一直停留在這個顯示為內網 IP 的列表中。這些節點是無法共享給其他節點的.
優化下載速度#
如果是希望透過 CID 讀取文件,首先要選擇速度較快的網關,透過 ipfs.io 網關獲取文件是最可靠的,但是因為連接問題,速度可能會很慢.
如果是希望透過 CID 發送文件,要務必保證對方使用的網關在當前存放文件的實例的連接列表中,所以維護一個速度較快的網關列表很重要,將這些網關加入自己的 Peering.Peers, 將極大提升自己的文件發布速度.
升級 IPFS#
大版本的升級,需要使用 fs-repo-migrations 工具,參考 Doc 裡的說明,就是兩步,備份.ipfs 目錄,運行 fs-repo-migrations 命令,在這之前需要把 ipfs 服務停掉.
IPFS Desktop#
在 Windows10 下安裝 IPFS Desktop, 默認安裝到用戶目錄下 C:\Users\Milton\AppData\Local\Programs\IPFS Desktop, 安裝完成後程序目錄 265M,
數據目錄在 C:\Users\Milton.ipfs, 格式和內容與 go-ipfs 無異。後台有兩個 IPFS Desktop 進程,兩個 ipfs 進程,共佔用內存約 500M.
狀態窗口實際上是一個 webview, 展示了 webui 的內容.
配置只是在默認的配置上將水位線改為 50, 300, 其他是一樣的.
資源如果存在 (緩存) 於直接連接的節點則可以訪問,否則無法訪問.
IPFS 私有網絡和集群#
參考 https://labs.eleks.com/2019/03/ipfs-network-data-replication.html
將 IPFS 運行為服務,開機自啟動
# 創建服務文件
sudo vi /etc/systemd/system/ipfs.service
# 文件內容
[Unit]
Description=IPFS Daemon
After=syslog.target network.target remote-fs.target nss-lookup.target
[Service]
Type=simple
ExecStart=/usr/local/bin/ipfs daemon --enable-namesys-pubsub
User=root
[Install]
WantedBy=multi-user.target
# 文件內容結束
# 添加服務
sudo systemctl daemon-reload
sudo systemctl enable ipfs
sudo systemctl start ipfs
sudo systemctl status ipfs
將 IPFS Cluster 添加為服務
# 創建服務
sudo nano /etc/systemd/system/ipfs-cluster.service
# 文件內容開始, 注意After裡有ipfs服務, 以此保證啟動順序
[Unit]
Description=IPFS-Cluster Daemon
Requires=ipfs
After=syslog.target network.target remote-fs.target nss-lookup.target ipfs
[Service]
Type=simple
ExecStart=/home/ubuntu/gopath/bin/ipfs-cluster-service daemon
User=root
[Install]
WantedBy=multi-user.target
# 文件內容結束
# 添加到系統服務
sudo systemctl daemon-reload
sudo systemctl enable ipfs-cluster
sudo systemctl start ipfs-cluster
sudo systemctl status ipfs-cluster
IPFS 的應用場景#
經過實際測試,對於在公共網絡中的 IPFS 節點,一旦建立了連接進行 CID 的發布和讀取速度是很快的,除去網絡本身的延遲,從請求到開始傳輸基本上在 2 秒內,傳輸速度取決於兩點間的帶寬.
文件共享#
IPFS 對內容的索引形式很適合做團隊間的文件共享,因為每一次改動都會產生索引變化,可以做版本化控制。分布式的文件接入點和下載點方便集群伸縮擴展,帶緩存的特性能降低熱點數據對帶寬資源的影響.
音視頻分發#
用於取代現有的 PT 下載網絡。因為 PT 傳播的單個文件很大,需要對整個 PT 群中的節點做統一的 pin 管理,一方面確保各終端節點對熱點內容的訪問速度,另一方面確保長尾內容有足夠的備份,不會丟失.
流媒體和下載加速#
IPFS 的特性,天然地可以用於取代 CDN 服務。靜態文件例如圖片,CSS, JS, 以及壓縮包,甚至一些時效性要求不高的直播業務。現在運營商寬帶已經普及 IPv6, 這些有公網 IPv6 地址的家庭帶寬資源可以充分利用起來做區域內容加速.
libp2p#
libp2p 是從 IPFS 中獨立出來的一個封裝好的 p2p 模塊,模塊中已經內置了 PeerId, MultiAddress, Protocol Handler 這些機制,可以很方便擴展出自己的應用.
Go 語言的實現 https://github.com/libp2p/go-libp2p, 樣例代碼 https://github.com/libp2p/go-libp2p-examples
在代碼裡的使用,一般是以下步驟:
- 創建 Host
- 對指定的協議,在 Host 上 SetStreamHandler
- 如果本地還有端口提供服務,則創建對應服務並監聽端口
- 指定目標節點和協議,創建 Stream
- 往 Stream 寫數據
- 從 Stream 讀數據,根據業務需求關閉或不關閉 Stream
在 Host 啟動後如果需要保持運行,可以使用以下方式
# 方法1, 使用select
select {} // hang forever
# 方法2, 使用內置服務的ListenAndServe
http.ListenAndServe(serveArgs, p)
# 方法3, 使用channel
<-make(chan struct{}) // hang forever