做者:ZStack 王爲算法
從 2015 年到如今,ZStack 有一條宗旨一直沒有變過,就是向客戶交付穩定、可靠、高性能的雲平臺,這條宗旨在前幾年讓咱們一直聚焦雲平臺自己,包括虛擬化、雲網絡、雲編排、存儲管理等等這些功能。數據庫
在這裏面最讓咱們頭痛的,即便不是第一也能進前三的存在,就是存儲管理。編程
考慮到存儲對業務的無比的重要性,以及咱們做爲一家創業公司的支持能力,咱們一開始一直是基於一些開源的存儲方案對客戶提供服務:緩存
綜合考慮前面的各類存儲,NFS、OCFS2 的不完美促使咱們提供一個可以管理共享存儲的存儲方案,這個方案要能達到下面的要求:安全
最終,在2018 年咱們決定本身開發一個面向共享塊存儲的存儲方法,命名很直接就叫 SharedBlock。整個方案是這樣的:服務器
SharedBlock 在推出後,應用在了不少的生產客戶上,特別是能夠利舊 SAN 存儲特色讓 SharedBlock 快速部署在大量以往使用虛擬化的客戶上。網絡
後來隨着 5G 和物聯網、雲端互聯的發展,讓市場迫切須要一個價格不高、能夠簡便部署、軟硬一體的超融合產品,所以咱們就在考慮一個兩節點一體機的產品,經過和硬件廠商合做設計,能夠實現 2U 的一體機包含足夠用戶使用的硬盤、獨立的模塊和雙電冗餘,咱們但願能經過這個產品將客戶的本來單節點運行的應用平滑升級到兩節點備份,讓客戶的運行在軌道站點、製造業工廠這些「端」應用既享受到雲的便利,又不須要複雜的運維和部署。這就是咱們的 Mini Storage。session
在開發這些存儲產品的過程當中,咱們踩了無數的坑,也收穫了不少經驗。架構
下面先說說將存儲作正確有多難,在今年說這個話題有一個熱點事件是避不開的,就是今年的 FOSDEM 19' 上 PostgreSQL 的開發者在會上介紹了 PostgreSQL 開發者發現本身使用 fsync() 調用存在一個十年的 bug——併發
這樣 PG 可能誤覺得數據已經同步而移動了 journal 的指針,實際上數據並無同步到磁盤,若是磁盤持續沒有修復且忽然丟失內存數據就會存在數據丟失的狀況。
在這場 session 上 PG 的開發者吐槽了 kernel 開發以及存儲開發裏的不少問題,不少時候 PG 只是想更好地實現數據庫,但卻發現常常要爲 SAN/NFS 這些存儲操心,還要爲內核的未文檔的行爲買單。
這裏說到 NFS,不得很少提兩句,在 Google 上搜索 "nfs bug" 能夠看到五百萬個結果,其中不乏 Gitlab 之類的知名廠商踩坑,也不乏 Redhat 之類的操做系統嘗試提供遇到 NFS 問題的建議:
從咱們一個雲廠商的角度看來,虛擬機存儲使用 NFS 遇到的問題包括但不限於這幾個:
最終咱們的建議就是生產環境、較大的集羣的狀況下,最起碼,少用 NFS 4.0 之前的版本……
另外一個出名的文章是發表在 14 年 OSDI 的這篇 All File Systems Are Not Created Equal,做者測試了數個文件系統和文件應用,在大量系統中找到了不乏丟數據的 Bug, 在此以後諸如 FSE'16 的 Crash consistency validation made easy 又找到了 gmake、atom 等軟件的各類丟數據或致使結果不正確的問題:
上面咱們舉了不少軟件、文件系統的例子,這些都是一些單點問題或者局部問題,若是放在雲平臺的存儲系統上的話,複雜度就會更高:
1. 首先,私有云面臨的是一個離散碎片的環境,咱們都知道 Android 開發者每每有比 iOS 開發者有更高的適配成本,這個和私有云是相似的,由於客戶有:
1)不一樣廠商的設備
2)不一樣的多路徑軟件
3)不一樣的服務器硬件、HBA 卡;
雖然 SCSI 指令是通用的,但實際上對 IO 出錯、路徑切換、緩存使用這些問題上,不一樣的存儲+多路徑+HBA 能夠組成不一樣的行爲,是最容易出現難以調試的問題地方,例若有的存儲配合特定 HBA 就會產生下面的 IO 曲線:
2. 因爲咱們是產品化的私有云,產品化就意味着整套系統不多是託管運維,也不會提供駐場運維,這樣就會明顯受客戶良莠不齊的運維環境和運維水平限制:
1)升級條件不一樣,有的用戶但願一旦部署完就不再要升級不要動了,這就要求咱們發佈的版本必定要是穩定可靠的,由於發出去可能就沒有升級的機會了,這點和互聯網場景有明顯的區別;
2)聯網條件不一樣,通常來講,來自生產環境的數據和日誌是相當重要的,但對產品化的廠商來講,這些數據倒是彌足珍貴,由於有的客戶機房不只不容許鏈接外網,甚至咱們的客戶工程師進機房的時候手機也不容許攜帶;
3)運維水平不一樣,對於一個平臺系統,若是運維水平不一樣,那麼能發揮的做用也是不一樣的,好比一樣是硬件故障,對於運維水平高的客戶團隊可能很快可以確認問題並找硬件廠商解決,而有的客戶就須要咱們先幫忙定位分析問題甚至幫助和硬件廠商交涉,就須要消耗咱們不少精力。
3. 漫長的存儲路徑,對於平臺來講,咱們不只要操心 IO 路徑——Device Mapper、多路徑、SCSI、HBA 這些,還要操心虛擬化的部分——virtio 驅動、virtio-scsi、qcow2…… 還要操心存儲的控制平面——快照、熱遷移、存儲遷移、備份…… 不少存儲的正確性驗證只涉及選舉、IO 這部分,而對存儲管理並無作足夠的關注,而根據咱們的經驗,控制面板一旦有 Bug,破壞力可能比數據面更大。
說了這麼多難處,咱們來講說怎麼解決。提到存儲的正確性,接觸過度布式系統的同窗可能會說 TLA+,咱們先對不熟悉 TLA+ 的同窗簡單介紹下 TLA+。
2002 Lamport 寫了一本書《Specifying Systems》基本上算是 TLA+ 比較正式的第一本書,瞭解的朋友可能知道在此以前 Lamport 在分佈式系統和計算結科學就很出名了——LaTex、Lamport clock、PAXOS 等等,TLA+ 剛開始的時候沒有特別受重視,他的出名是來自 AWS 15 年發表在 ACM 會刊的《How Amazon Web Services Uses Formal Methods》。
從本質上講,形式化驗證並非新東西,大概在上世紀就有了相關的概念,TLA+ 的優點在於它特別適合驗證分佈式系統的算法設計。由於對於一個可驗證的算法來講,核心是將系統時刻的狀態肯定化,並肯定狀態變化的條件和結果,這樣 TLA+ 能夠經過窮舉+剪枝檢查當有併發操做時會不會有違反要求(TLA+ 稱之爲 invariant)的地方——例如帳戶餘額小於 0,系統中存在了多個 leader 等等。
看最近的幾場 TLA Community Meeting,能夠看到 Elasticserach、MongoDB 都有應用。
那麼既然這個東西這麼好,爲何在國內開發界彷佛並無特別流行呢?咱們在內部也嘗試應用了一段時間,在 Mini Storage 上作了一些驗證,感受若是 TLA+ 想應用更普遍的話,可能仍是有幾個問題須要優化:
固然了,涉及到算法的正確性證實,形式化證實依然是不可替代的,但不得不說目前階段在雲平臺存儲上應用,還沒作到所有覆蓋,固然了咱們也看到 TLA+ 也在不斷進步——
這裏特別是第三點,若是咱們的 Spec 可以被轉換成代碼,那麼咱們就能夠將核心代碼的算法部分抽象出來,作成一個單獨的庫,直接使用被 Spec 證實過的代碼。
分佈式系統的測試和驗證,這幾年還有一個很熱門的詞彙,就是混沌工程。
混沌工程對大多數人來講並非一個新鮮詞彙,能夠說它是在單機應用轉向集羣應用,面向系統編程轉向到面向服務編程的必然結果,咱們已經看到不少互聯網應用聲稱在混沌工程的幫助下提升了系統的穩定性如何如何,那麼對於基礎架構軟件呢?
在必定程度上能夠說 ZStack 很早就開始在用混沌工程的思想測試系統的穩定性,首先咱們有三個關鍵性的外部總體測試:
上面這些方法,在大量調用 API、測試 IO 以外,很重要的一點就是注入錯誤,例如強制關閉虛擬機、物理機,經過可編程 PDU 模擬斷電等等,可是這些方法有一些缺陷:
總之大部分混沌工程所提供的手段(隨機關閉節點、隨機殺進程、經過 tc 增長延時和 iproute二、iptables 改變網絡等等)並不能知足 ZStack 的徹底模擬用戶場景的需求。
在這種狀況下,咱們將擴展手段放在了幾個方向上:
使用 fiurun + fiuctl 能夠對某個應用在須要的時刻控制系統調用。
fiu 對注入 libaio 沒有直接提供支持,但好在 fio 擴展和編譯都極爲簡單,所以咱們能夠輕鬆的根據本身的需求增長 module。
2. systemtap,systemtap 是系統界的經典利器了,能夠對內核函數的返回值根據需求進行修改,對內核理解很清晰的話,systemtap 會很好用,若是是對存儲進行錯誤注入,能夠重點搜 scsi 相關的函數,以及參考這裏:Kernel Fault injection framework using SystemTap
3. device-mapper,device-mapper 提供了 dm-flakey、dm-dust、dm-delay,固然你也能夠寫本身的 target,而後能夠搭配 lio 等工具就能夠模擬一個 faulty 的共享存儲,得益於 device-mapper 的動態加載,咱們能夠動態的修改 target 和參數,從而更真實的模擬用戶場景下的狀態;
4. nbd,nbd 的 plugin 機制很是便捷,咱們能夠利用這一點來修改每一個 IO 的行爲,從而實現出一些特殊的 IO pattern,舉例來講,咱們就用 nbd 模擬過用戶的順序寫很快但隨機寫異常慢的存儲設備;
5. 此外,還有 scsi_debug 等 debug 工具,但這些比較面向特定問題,就不細說了。
上面兩張圖對這些錯誤注入手段作了一些總結,從系統角度來看,若是咱們在設計階段可以驗證算法的正確性,在開發時注意開發可測試的代碼,經過海量測試和錯誤注入將路徑完整覆蓋,對遇到的各類 IO 異常經過測試 case 固化下來,咱們的存儲系統必定會是愈來愈穩定,持續的走在「正確」的道路上的。