從以下三個方面對分布式存儲系統進行簡單介紹:
1.首先,什么是分布式存儲系統呢?
簡單的說,就是將文件存儲到多個服務器中。
2.其次,為什么需要分布式存儲系統?
因為單機存儲資源和計算資源已經不能滿足用戶的需求。
3.最后,如何實現一個分布式存儲系統或者說實現一個分布式存儲系統需要做哪些工作?
(1)既然是將文件存儲到多個服務器中那就需要確定將文件具體存儲到哪些服務器里,兩種方式,一種是通過控制服務器,由這個控制服務器負責統一調度,客戶端請求存儲一個文件時,首先與控制服務器交互,控制服務器返回需要保存到服務器的地址,讀取文件時也需要與控制服務器交互,獲取存儲位置信息,其中HDFS、GFS等分布式存儲使用此種技術,namenode就類似於控制服務器角色。另外一個方式是,不需要控制服務器,客戶端自己計算需要存儲到哪里,最簡單的方式是直接取hash,比如有8台存儲服務器,只需要把文件內容或者文件名取hash模8即可計算出應該存儲到哪台存儲服務器。但有個問題是,當服務器數量增減時,hash就失效了,幾乎需要重排遷移所有數據,根本沒有辦法實現水平擴展,這在分布式系統中是無法忍受的。為了避免出現這種情況,引入了一致性hash算法,又稱為環哈希,其中OpenStack Swift、華為FusionStorage就是使用的該方法。除了環hash,當然還有其他的類hash算法,比如CRUSH算法,其中開源分布式存儲系統Ceph就是使用的該方法。需要注意的是雖然基於hash的文件分布映射方法不需要控制節點計算需要存儲的位置,但仍然需要控制服務器保存一些集群元數據,比如集群的成員信息、映射規則、監控等等,如Ceph的mon服務。
(2)但是,如果只有一個控制服務,則存在單點故障,掛掉了就會導致服務不可用。為了避免單點故障,具備高可用特點,必然需要同時啟動多個控制服務,有多個控制服務就必須區分誰是leader,誰是slave,因此需要分布式一致性來協調選主,可以基於現有的分布式協調系統實現,如Zookeeper、Etcd服務等,也可以直接基於Paxos、Raft算法實現。
(3)確定了存儲路徑,接下來需要解決以什么樣的方式存儲文件,如果文件直接映射到物理主機或者物理硬盤,粒度太粗略,容易導致數據分布不均勻。如果踢掉一台服務器或者一塊硬盤,需要把這台服務器的數據遷移到重映射的另一台主機,遷移數據的IO都集中在這兩台主機之間,其它主機幫不上忙。於是引入了虛擬主機概念,OpenStack Swift中叫做partition以及Ceph中PG等都是類似的概念。原理就是在物理主機上面加一層邏輯主機,比如有8台物理主機,可以創建128個虛擬主機,然后把這8台物理主機映射到這128台邏輯主機上,這樣相當於每一台主機都虛擬成16台虛擬主機,當然實際上不一定是按照平均分,可以根據磁盤容量映射,磁盤空間大可以映射較多的虛擬主機。當然虛擬主機數量通常都會設置成2的冪,這樣很多計算都可以使用位運算進行優化,比如取模運算等。這樣文件塊會先根據虛擬主機計算存儲位置,然后再從表中查找虛擬主機映射的物理主機,文件塊分布更均勻,當踢掉一台主機時會重映射到多台主機中,數據遷移效率提升。
(4)如果文件很大怎么辦,可能在一台服務器根本存不下,即使存下了,也會導致各個服務器的磁盤利用率不均衡,甚至可能出現大量存儲碎片。於是我們自然想到的是把文件分塊,然后基於塊存儲,比如按照64MB大小分塊,如果存儲一個2GB的文件,則需要把文件分割成32個塊,然后逐塊存儲,存儲位置仍然使用前面提到的hash算法。分塊是存儲密度更大、更緊湊,幾乎所有的分布式存儲系統都會使用分塊技術。
(5)數據存儲完成后需要考慮如何進行數據保護,也就是可靠性,當集群中的出現節點異常或磁盤異常時數據依然可以進行正常讀取,為了解決這個問題,最容易想到的方法是使用冗余技術,即每一個塊,我都存儲多份,並分布到不同的服務器中,這樣即使其中一個服務器宕機了,也能從其他服務器中讀取塊,這個和RAID 1技術原理是一樣的。存儲多少份呢,這個需要權衡成本以及數據可靠性要求,通常來說存儲三份就夠了。有人會說,存儲三份,相當於使用了三倍的存儲空間,這樣存儲資源是不是有點太浪費了,而又不想犧牲數據可靠性。我們學習算法時經常使用時間換空間的思想,計算換存儲,這個仍然可以從RAID實現中獲取靈感,以RAID 5為例,通過奇偶校驗恢復數據,存儲利用率為(n-1)/n,相比RAID 1的1/2提高了存儲利用率,並且具有RAID 1一樣的可靠性,但需要耗費CPU計算奇偶位。奇偶校驗只能缺一位,自然可以想到進一步泛化,於是引入糾刪碼技術,原理其實就是類似解線性方程,關於糾刪碼技術介紹可以參考Erasure Code - EC糾刪碼原理。幾乎所有的分布式存儲系統都使用了冗余副本技術,大多數都會支持糾刪碼,比如Ceph、Swift。
無論使用純副本技術還是結合糾刪碼,必然還是需要把一個塊復制多份存儲,寫入多份,這里假設副本數為3份。這些副本如何寫入呢?當拿到三個副本的位置后,客戶端可以同時寫入三個副本,這種方式稱為直接復制(direct replication),這樣的問題是客戶端會同時占用3倍的業務網絡帶寬,吞吐量也只有1/3,glusterfs采用的是這種復制策略。另一種方式是客戶端只選擇其中一個主節點寫入數據,當寫完第一個節點的數據后,由第一個節點復制到第二個節點,再由第二個節點復制到第三個節點,以此類推直到寫完所有的副本,這種方式稱為鏈式復制(chain replication),Ceph、HDFS都是采用的該種策略,這樣由於客戶端其實只是寫了一份數據,不占用額外的業務網絡,而存儲節點之間的復制可以是一個專門的存儲網,不影響業務網絡。
(6)寫入多份數據,如何保證這些副本數據都是一樣的,如何保證三個數據同步呢,萬一哪台服務器掛了寫不進去怎么辦。於是引入了一致性策略。最簡單的方法,就是等所有的副本都完成時才返回結果,這樣保證寫入的三個副本肯定沒有問題,這就是強一致性,其中Ceph就是使用的強一致性模型,強一致性能夠保證多副本完全一致,並且不會讀取臟數據,但是性能不好,萬一有一台服務器巨慢則會拖垮整個集群,典型的木桶效應,因此強一致性天生難以支持跨區域部署,因為跨區域的遠端時延太長了,導致存儲系統性能低。為了避免這種情況,我們可以適當放寬條件,即只要保證一半以上的服務器寫入成功即返回,這樣即使其中有少數服務器拖后腿也沒有關系,不用等,讓他自個慢慢同步,最終一致即可。這就是典型的最終一致性模型,OpenStack Swift即采用該種策略,這種模型能夠提高讀寫性能,但可能讀取臟數據,比如剛好讀到還沒有來得及同步的服務器的數據塊。事實上高性能和強一致性是兩者不可兼得的,這就是著名的CAP理論,這里的C代表一致性,A代表可用性(在一定時間內,用戶的請求都會得到正確的應答),P代表分區容錯。正常情況下,存儲系統的所有節點都是互通的,處在一個網絡連通區域中,如果有些節點之間不連通了(節點掛了或者網絡故障),這就相當於把一個網絡連通區域割裂了幾個區域,彼此不能通信了,因此叫做分區。分布式存儲系統要系統出現分區時數據不丟(可靠性),數據可訪問(可用性),避免腦裂,因此P是100%需要滿足的,否則稍微一個網絡抖動,數據就損壞了。剩下的就是C和A之間的權衡,這個就看你要設計成什么存儲系統了,如果一致性不那么重要,比如對象存儲,上傳了一個新文件,即使馬上讀不到數據也無所謂,但是可能需要支持大規模的對象寫入,因此更關注A,設計為AP存儲系統。而對於一些實時性要求高的系統,必須保證寫入后數據一定能夠讀到正確的數據(而不是臟數據),就必須犧牲吞吐量,因此設計為CP存儲系統。
(7)為了節省存儲空間,可能會用到壓縮技術,壓縮大家都很熟悉了,這里不多介紹。
(8)如果是一個海量分布式存儲系統,尤其是提供公有雲服務,比如網盤服務,肯定會有用戶上傳一模一樣的文件,為了節省成本,自然想到避免存儲重復的文件,這就是重刪技術(Data deduplication),可參考int32bit:百度雲的「極速秒傳」使用的是什么技術?,簡單理解就是客戶端上傳文件時,先在本地計算下hash指紋,然后上傳到服務器比對,如果指紋一樣,說明文件已經存在,此時不需要上傳文件內容,直接鏈接下即可,不僅節省了存儲空間(比壓縮更省),還節省了上傳時間,實現秒傳。我了解的Fusion Storage是實現了重刪技術,OpenStack Swift、Ceph貌似都沒有。
(9)另一個問題是,如果集群徹底癱了,數據就徹底沒了,這可不能忍。為了解決這個問題,你自然會想到使用復制手段,即備份技術,把文件復制存儲到其它廉價存儲服務器中,比如S3。當用戶執行save操作時,復制這個文件並重命名為xxx-20180312233020(時間戳),這樣非常容易就能恢復到備份的任意版本,由於每次都要拷貝整個文件,因此稱為全量備份(full backup)。每次都復制顯然耗時耗空間,自然想到只復制上一次備份后改變的內容,這樣就可以節省存儲空間,即增量(incremental backup)備份。注意,備份一定要拷貝到其它存儲系統,如果僅僅是拷貝到當前存儲系統,不叫備份,只能叫副本,集群癱了,數據仍然不能恢復。副本主要用於防故障,即一塊硬盤壞了數據不丟且還能讀,備份還用於防人禍,比如誤刪操作,能回滾到前面的一個備份點上。
以上備份技術需要用戶自己手動執行,如果沒有實時備份,集群突然掛了,數據還是會丟。因此需要采取容災策略,其中一個容災策略就是異地同步技術,或者叫做復制技術(geo-replication/mirror),這個類似於mysql的主從同步,即在異地建立一個一模一樣的集群,這個集群正常情況下不向用戶提供存儲服務,僅僅同步本地的集群數據,當本地的集群掛了,能夠自動切換到異地集群,服務依然可用。注意這個和副本之間完全同步不一樣,復制技術通常采用異步策略,基於操作日志replay,mysql使用binlog,ceph使用journal日志。ceph的rbd mirror就是采用的此種技術,關於rbd mirro介紹參考Ceph Jewel Preview: Ceph RBD mirroring。
當然即使有如上的副本技術、備份技術、容災技術,集群癱了也可能短時間內不可用。這就引入兩個指標,一個是RPO,故障后數據可恢復到故障前哪個時間點,當然越小約好,0表示能立即恢復,無窮大表示數據廢了恢復不了了,3表示能恢復到故障前3秒,這其實和備份策略有關,比如每天0點備份,那備份點就是當天0點數據。另一個指標為RTO,即故障后多長時間可以恢復,0表示服務連續毫無影響,無窮大表示服務再也恢復不了了,60表示一個分鍾能恢復。
參考文檔:https://www.zhihu.com/question/25834847/answer/348271275