本文是這個系列的第一篇,著重介紹相關(guān)背景和元數(shù)據(jù)服務。
一般來說,我們根據(jù)存儲的訪問接口以及應用場景,把分布式存儲分為三種類型,包括分布式塊存儲,分布式文件存儲,和分布式對象存儲。
其中,分布式塊存儲的主要應用場景包括:
1.虛擬化:比如像 KVM,VMware,XenServer等Hypervisor,以及像Openstack,AWS 等云平臺。塊存儲在其中的角色是支撐虛擬機中的虛擬盤的存儲。
2.數(shù)據(jù)庫:比如MySQL,Oracle等。很多 DBA都將數(shù)據(jù)庫的數(shù)據(jù)盤運行在一個共享的塊存儲服務上,例如分布式塊存儲。此外也有很多客戶直接把數(shù)據(jù)庫運行在虛擬機中。
3.容器:容器最近幾年在企業(yè)中使用越來越廣泛。一般來說,容器中運行的應用都是無狀態(tài)的,但在很多應用場景下,應用也會有數(shù)據(jù)持久化的需求。應用可以選擇將數(shù)據(jù)持久化到數(shù)據(jù)庫中,也可以選擇將數(shù)據(jù)持久化到一個共享虛擬磁盤上。這個需求對應到Kubernetes 中,就是 Persistent Volume 這個功能。
今天我將主要圍繞 SmartX 如何打造分布式塊存儲進行介紹。SmartX 從 2013 年成立開始,到目前已經(jīng)積累了 5 年左右的分布式塊存儲的研發(fā)經(jīng)驗,所以今天我們除了分享 SmartX 如何實現(xiàn)我們自己研發(fā)的分布式塊存儲 ZBS 以外,還會詳細介紹我們在分布式塊存儲的研發(fā)過程中的一些思考和選擇。此外也將介紹一下我們產(chǎn)品未來的規(guī)劃。
從廣泛意義上講,分布式存儲中通常需要解決三個問題,分別是元數(shù)據(jù)服務,數(shù)據(jù)存儲引擎,以及一致性協(xié)議。
其中,元數(shù)據(jù)服務提供的功能一般包括:集群成員管理,數(shù)據(jù)尋址,副本分配,負載均衡,心跳,垃圾回收等等。數(shù)據(jù)存儲引擎負責解決數(shù)據(jù)在單機上存儲,以及本地磁盤的管理,磁盤故障處理等等。每一個數(shù)據(jù)存儲引擎之間是隔離的,在這些隔離的存儲引擎之間,需要運行一個一致性協(xié)議,來保證對于數(shù)據(jù)的訪問可以滿足我們期望的一致性狀態(tài),例如強一致,弱一致,順序一致,線性一致等等。我們根據(jù)不同的應用場景,選擇一個適合的一致性協(xié)議,這個協(xié)議將負責數(shù)據(jù)在不同的節(jié)點之間的同步工作。
有了這三部分,我們基本上就掌握了一個分布式存儲的核心。不同的分布式存儲系統(tǒng)之間的區(qū)別,基本也都來自于這三個方面的選擇不同。
接下來我會分別從這三個方面介紹一下我們在做SmartX ZBS 系統(tǒng)設計的時候是怎樣思考的,以及最終決定采用哪種類型的技術(shù)和實現(xiàn)方法。
首先我們來介紹一下元數(shù)據(jù)服務。我們先來談談我們對元數(shù)據(jù)服務的需求。
所謂元數(shù)據(jù)就是『數(shù)據(jù)的數(shù)據(jù)』,比如說數(shù)據(jù)放在什么位置,集群中有哪些服務器,等等。如果元數(shù)據(jù)丟失了,或者元數(shù)據(jù)服務無法正常工作,那么整個集群的數(shù)據(jù)都無法被訪問了。
由于元數(shù)據(jù)的重要性,所以對元數(shù)據(jù)的第一個需求就是可靠性。元數(shù)據(jù)必須是保存多份的,同時元數(shù)據(jù)服務還需要提供 Failover 的能力。
第二個需求就是高性能。盡管我們可以對 IO 路徑進行優(yōu)化,使得大部分 IO 請求都不需要訪問元數(shù)據(jù)服務,但永遠都有一些 IO 請求還是需要修改元數(shù)據(jù),比如數(shù)據(jù)分配等等。為避免元數(shù)據(jù)操作成為系統(tǒng)性能的瓶頸,元數(shù)據(jù)操作的響應時間必須足夠短。同時由于分布式系統(tǒng)的集群規(guī)模在不斷的擴大,對于元數(shù)據(jù)服務的并發(fā)能力也有一定的要求。
最后一個需求是輕量級。由于我們產(chǎn)品大部分使用場景是私有部署,也就是我們的產(chǎn)品是部署在客戶的數(shù)據(jù)中心的,且由客戶自己運維,而非我們的運維人員運維。這個場景和很多互聯(lián)網(wǎng)公司自己來運維自己的產(chǎn)品是完全不同的場景。所以對于 ZBS 來說,我們更強調(diào)整個系統(tǒng),尤其是元數(shù)據(jù)服務的輕量級,以及易運維的能力。我們期望元數(shù)據(jù)服務可以輕量級到可以把元數(shù)據(jù)服務和數(shù)據(jù)服務混合部署在一起。同時我們希望大部分的運維操作都可以由程序自動完成,或用戶只需要在界面上進行簡單的操作就可以完成。如果大家了解 HDFS 的話,HDFS 中的元數(shù)據(jù)服務的模塊叫做 Namenode,這是一個非常重量級的模塊。Namenode 需要被獨立部署在一臺物理服務器上,且對硬件的要求非常高,且非常不易于運維,無論是升級還是主備切換,都是非常重的操作,非常容易因操作問題而引發(fā)故障。
以上就是我們對元數(shù)據(jù)服務的需求。接下來我們來看一下具體有哪些方法可以構(gòu)造一個元數(shù)據(jù)服務。
談到存儲數(shù)據(jù),尤其是存儲結(jié)構(gòu)化的數(shù)據(jù),我們第一個想到的就是關(guān)系型數(shù)據(jù)庫,例如 MySQL,以及一些成熟的 KV 存儲引擎,例如 LevelDB,RocksDB 等。但這種類型的存儲最大的問題就是無法提供可靠的數(shù)據(jù)保護和 Failover 能力。LevelDB 和 RocksDB 雖然非常輕量級,但都只能把數(shù)據(jù)保存在單機上。而盡管 MySQL 也提供一些主備方案,但我們認為 MySQL 的主備方案是一個太過笨重的方案,且缺乏簡易的自動化運維方案,所以并不是一個十分好的選擇。
其次,我們來看一下一些分布式數(shù)據(jù)庫,例如MongoDB 和 Cassandra。這兩種分布式數(shù)據(jù)庫都可以解決數(shù)據(jù)保護和提供 Failover 機制。但是他們都不提供 ACID 機制,所以在上層實現(xiàn)時會比較麻煩,需要額外的工作量。其次就是這些分布式數(shù)據(jù)庫在運維上也相對復雜,不是很易于自動化運維。
也有一種選擇是基于 Paxos 或者 Raft 協(xié)議自己實現(xiàn)一個框架。但這樣實現(xiàn)的代價非常大,對于一個創(chuàng)業(yè)公司不是一個很劃算的選擇。并且我們創(chuàng)業(yè)的時間是 2013 年,當時 Raft 也只是剛剛提出。
第四種是選擇 Zookeeper。Zookeeper 基于 ZAB 協(xié)議,可以提供一個穩(wěn)定可靠地分布式存儲服務。但 Zookeeper 的最大的問題是能夠存儲的數(shù)據(jù)容量非常有限。為了提高訪問速度,Zookeeper 把存儲的所有數(shù)據(jù)都緩存在內(nèi)存中,所以這種方案導致元數(shù)據(jù)服務所能支撐的數(shù)據(jù)規(guī)模嚴重受限于服務器的內(nèi)存容量,使得元數(shù)據(jù)服務無法做到輕量級,也無法和數(shù)據(jù)服務混合部署在一起。
最后還有一種方式是基于Distributed Hash Table(DHT)的方法。這種方法的好處元數(shù)據(jù)中不需要保存數(shù)據(jù)副本的位置,而是根據(jù)一致性哈希的方式計算出來,這樣就極大地降低了元數(shù)據(jù)服務的存儲壓力和訪問壓力。但使用DHT 存在的問題,就喪失了對數(shù)據(jù)副本位置的控制權(quán),在實際生產(chǎn)環(huán)境中,非常容易造成集群中的產(chǎn)生數(shù)據(jù)不均衡的現(xiàn)象。同時在運維過程中,如果遇到需要添加節(jié)點,移除節(jié)點,添加磁盤,移除磁盤的情況,由于哈希環(huán)會發(fā)生變化,一部分數(shù)據(jù)需要重新分布,會在集群中產(chǎn)生不必要的數(shù)據(jù)遷移,而且數(shù)據(jù)量往往非常大。而這種于運維操作在一個比較大規(guī)模的環(huán)境中幾乎每天都會發(fā)生。大規(guī)模的數(shù)據(jù)遷移很容易影響到線上的業(yè)務的性能,所以DHT 使得運維操作變得非常麻煩。
以上介紹的方法都存在各種各樣的問題,并不能直接使用。最終 ZBS 選擇了使用 LevelDB(也可以替換成 RocksDB)和 Zookeeper 結(jié)合的方式,解決元數(shù)據(jù)服務的問題。首先,這兩個服務相對來說都非常輕量級;其次 LevelDB 和 Zookeeper 使用在生產(chǎn)中也非常穩(wěn)定。
我們采用了一種叫做 Log Replication 的機制,可以同時發(fā)揮 LevelDB 和 Zookeeper 的優(yōu)點,同時避開他們自身的問題。
這里我們簡單的介紹一下Log Replication。簡單來說,我們可以把數(shù)據(jù)或者狀態(tài)看作是一組對數(shù)據(jù)操作的歷史集合,而每一個操作都可以通過被序列化成Log 記錄下來。如果我們可以拿到所有的 Log,并按照 Log 里面記錄的操作重復一遍,那么我們就可以完整的恢復數(shù)據(jù)的狀態(tài)。任何一個擁有Log 的程序都可以通過重放 Log 的方式恢復數(shù)據(jù)。如果我們對Log 進行復制,實際上也就相當于對數(shù)據(jù)進行了復制。這就是 Log Replication 最基本的想法。
我們具體來看一下 ZBS 是如何利用 Zookeeper + LevelDB 完成 Log Replication 操作的。首先,集群中有很多個 Meta Server,每個 Server 本地運行了一個 LevelDB 數(shù)據(jù)庫。Meta Server 通過 Zookeeper 進行選主,選出一個 Leader 節(jié)點對外響應元數(shù)據(jù)請求,其他的 Meta Server 則進入Standby 狀態(tài)。
當 Leader 節(jié)點接收到元數(shù)據(jù)的更新操作后,會將這個操作序列化成一組操作日志,并將這組日志寫入Zookeeper。由于 Zookeeper 是多副本的,所以一旦 Log 數(shù)據(jù)寫入 Zookeeper,也就意味著 Log 數(shù)據(jù)是安全的了。同時這個過程也完成了對 Log 的復制。
當日志提交成功后,Meta Server 就可以將對元數(shù)據(jù)的修改同時提交到本地的 LevelDB 中。這里 LevelDB 中存儲的是一份全量的數(shù)據(jù),而不需要以 Log 的形式存儲。
對于非 Leader 的 Meta Server 節(jié)點,會異步的從 Zookeeper 中拉取 Log,并將通過反序列化,將 Log 轉(zhuǎn)換成對元數(shù)據(jù)的操作,再將這些修改操作提交到本地的LevelDB 中。這樣就能保證每一個 Meta Server 都可以保存一個完整的元數(shù)據(jù)。
前面提到,由于 Zookeeper 存儲數(shù)據(jù)的容量受限于內(nèi)存容量。為了避免 Zookeeper 消耗過多內(nèi)存,我們對 Zookeeper 中的 Log 定期執(zhí)行清理。只要 Log 已經(jīng)被所有的 Meta Server 同步完, Zookeeper 中保存的 Log 就可以被刪除了,以節(jié)省空間。通常我們在 Zookeeper 上只保存 1GB 的 Log,已經(jīng)足夠支撐元數(shù)據(jù)服務。
Failover的邏輯也非常簡單。如果 Leader 節(jié)點發(fā)生故障,其他還存活的的 Meta Server 通過 Zookeeper 再重新進行一次選主,選出一個新的 Meta Leader。這個新的 Leader 將首先從 Zookeeper 上同步所有還未消耗的日志,并在提交到本地的 LevelDB 中,然后就可以對外提供元數(shù)據(jù)服務了。
現(xiàn)在我們總結(jié)一下 ZBS 中元數(shù)據(jù)服務實現(xiàn)的特點。
首先,這個原理非常容易理解,而且實現(xiàn)起來非常簡單。由 Zookeeper 負責選主和 Log Replication,由 LevelDB 負責本地元數(shù)據(jù)的存儲。背后的邏輯就是盡可能的將邏輯進行拆分,并盡可能的復用已有項目的實現(xiàn)。
其次,速度足夠快。Zookeeper 和 LevelDB 本身的性能都不錯,而且在生產(chǎn)中,我們將 Zookeeper 和 LevelDB 運行在 SSD 上。在實際測試中,對于單次元數(shù)據(jù)的修改都是在毫秒級完成。在并發(fā)的場景下,我們可以對元數(shù)據(jù)修改的日志做 Batch,以提高并發(fā)能力。
此外,這種方式支持 Failover,而且 Failover 的速度也非??臁ailover 的時間就是選主再加上 Log 同步的時間,可以做到秒級恢復元數(shù)據(jù)服務。
最后說一下部署。在線上部署的時候,我們通常部署 3 個或 5 個Zookeeper 服務的實例以及至少 3 個 Meta Server 服務的實例,以滿足元數(shù)據(jù)可靠性的要求。元數(shù)據(jù)服務對資源消耗都非常小,可以做到和其他服務混合部署。
以上是一些基本的原理,我們再來看一下 ZBS 內(nèi)部的對于元數(shù)據(jù)服務的具體實現(xiàn)。
我們將上述的Log Replication 邏輯封裝在了一個 Log Replication Engine 中,其中包含了選主、向Zookeeper 提交 Log、向 LevelDB 同步數(shù)據(jù)等操作,進一步簡化開發(fā)復雜度。
在 Log Replication Engine 的基礎(chǔ)之上,我們實現(xiàn)了整個 Meta Sever 的邏輯,其中包含了 Chunk Manager,NFS Manger,iSCSI Manager,Extent Manager 等等很多管理模塊,他們都可以通過 Log Replication Engine,管理特定部分的元數(shù)據(jù)。RPC 模塊是 Meta Server 對外暴露的接口,負責接收外部的命令,并轉(zhuǎn)發(fā)給對應的 Manager。例如創(chuàng)建/刪除文件,創(chuàng)建/刪除虛擬卷等等。此外,Meta Server 中還包含了一個非常復雜的調(diào)度器模塊,里面包含了各種復雜的分配策略,恢復策略,負載均衡策略,以及心跳,垃圾回收等功能。
以上就是關(guān)于元數(shù)據(jù)服務部分的介紹。