對(duì)象存儲(chǔ)是如何保存大小文件的?
對(duì)象存儲(chǔ)是一個(gè)相對(duì)較新并且在持續(xù)穩(wěn)步增長(zhǎng)的市場(chǎng)部分。對(duì)于新手而言,對(duì)象存儲(chǔ)用于保存大量非結(jié)構(gòu)化數(shù)據(jù),其中每個(gè)“對(duì)象”實(shí)際上是一個(gè)沒有特定格式的文件(也稱二進(jìn)制文件)。實(shí)際上,從小的對(duì)象(人類可讀取文件)到媒體(音頻和視頻)或其它行業(yè)特定格式(石油&天然氣,醫(yī)學(xué)成像等),對(duì)象存儲(chǔ)可以保存任何類型的數(shù)據(jù)。
與傳統(tǒng)存儲(chǔ)相比,采用對(duì)象存儲(chǔ)的好處良多?;趬K的系統(tǒng)(例如光纖通道和iSCSI)無法很好地向外擴(kuò)展,并且沒有真正的了解所存儲(chǔ)的數(shù)據(jù)。它們是以低延遲和高粒度提供內(nèi)容的“啞”塊設(shè)備。文件系統(tǒng)將一些結(jié)構(gòu)放在數(shù)據(jù)上,將文件對(duì)象放入層級(jí)結(jié)構(gòu)(文件夾/目錄)然后將元數(shù)據(jù)附加到這些對(duì)象上。然而,元數(shù)據(jù)通常只是基于存儲(chǔ)文件所需的信息(創(chuàng)建時(shí)間,時(shí)間更新,訪問規(guī)則)存儲(chǔ)文件。
對(duì)象存儲(chǔ)更進(jìn)一步消除了文件夾層次結(jié)構(gòu),具有高度可搜索的可擴(kuò)展元數(shù)據(jù)。在規(guī)模方面,對(duì)象存儲(chǔ)可以增加到多(即便不是上百)PB容量,通常對(duì)數(shù)據(jù)沒有地域限制。因?yàn)閷?duì)象存儲(chǔ)平臺(tái)提供了優(yōu)于傳統(tǒng)存儲(chǔ)的形式,越來越多的企業(yè)開始采用它。基于塊的存儲(chǔ)陣列無法很好地?cái)U(kuò)展,且?guī)в写罅縃DD和SSD的數(shù)據(jù)保護(hù)(例如RAID)的問題?;谖募南到y(tǒng)受到系統(tǒng)自身的可擴(kuò)展性限制,無論是在對(duì)象計(jì)數(shù)、并發(fā)、并行訪問或恢復(fù)時(shí)間方面,都能檢驗(yàn)出文件系統(tǒng)結(jié)構(gòu)的一致性。對(duì)象存儲(chǔ)代表一種更簡(jiǎn)單、更可擴(kuò)展的解決方案,通過標(biāo)準(zhǔn)的基于網(wǎng)絡(luò)的協(xié)議可輕松訪問。
我們都知道,保存像圖片、音視頻這類大文件,最佳的選擇就是 對(duì)象存儲(chǔ)。對(duì)象存儲(chǔ)不僅有很好的 大文件讀寫性能,還可以 通過水平擴(kuò)展實(shí)現(xiàn)近乎無限的容量,并且可以 兼顧服務(wù)高可用、數(shù)據(jù)高可靠 這些特性。
對(duì)象存儲(chǔ)之所以能做到這么「全能」,最主要的原因是,對(duì)象存儲(chǔ)是原生的分布式存儲(chǔ)系統(tǒng)。這里我們講的「原生分布式存儲(chǔ)系統(tǒng)」,是相對(duì)于 MySQL、Redis 這類單機(jī)存儲(chǔ)系統(tǒng)來說的。雖然這些非原生的存儲(chǔ)系統(tǒng),也具備一定的集群能力,但你也能感受到,用它們構(gòu)建大規(guī)模分布式集群的時(shí)候,其實(shí)是非常不容易的。
隨著云計(jì)算的普及,很多新生代的存儲(chǔ)系統(tǒng),都是原生的分布式系統(tǒng),它們一開始設(shè)計(jì)的目標(biāo)之一就是分布式存儲(chǔ)集群,比如說 Elasticsearch、Ceph 和國(guó)內(nèi)很多大廠推出的新一代數(shù)據(jù)庫(kù),大多都可以做到:
- 近乎無限的存儲(chǔ)容量;
- 超高的讀寫性能;
- 數(shù)據(jù)高可靠:節(jié)點(diǎn)磁盤損毀不會(huì)丟數(shù)據(jù);
- 實(shí)現(xiàn)服務(wù)高可用:節(jié)點(diǎn)宕機(jī)不會(huì)影響集群對(duì)外提供服務(wù)。
那這些原生分布式存儲(chǔ)是如何實(shí)現(xiàn)這些特性的呢?這里面同樣存在嚴(yán)重的「互相抄作業(yè)」的情況。這個(gè)也可以理解,除了存儲(chǔ)的數(shù)據(jù)結(jié)構(gòu)不一樣,提供的查詢服務(wù)不一樣以外,這些分布式存儲(chǔ)系統(tǒng),它們面臨的很多問題都是一樣的,那實(shí)現(xiàn)方法差不多也是可以理解。
對(duì)象存儲(chǔ)的查詢服務(wù)和數(shù)據(jù)結(jié)構(gòu)都非常簡(jiǎn)單,是最簡(jiǎn)單的原生分布式存儲(chǔ)系統(tǒng),對(duì)象存儲(chǔ)數(shù)據(jù)是如何保存大文件的?
對(duì)象存儲(chǔ)對(duì)外提供的服務(wù),其實(shí)就是一個(gè) 近乎無限容量的大文件 KV 存儲(chǔ),所以對(duì)象存儲(chǔ)和分布式文件系統(tǒng)之間,沒有那么明確的界限。對(duì)象存儲(chǔ)的內(nèi)部,肯定有很多的存儲(chǔ)節(jié)點(diǎn),用于保存這些大文件,這個(gè)就是數(shù)據(jù)節(jié)點(diǎn)的集群。
另外,我們?yōu)榱斯芾磉@些數(shù)據(jù)節(jié)點(diǎn)和節(jié)點(diǎn)中的文件,還需要一個(gè)存儲(chǔ)系統(tǒng)保存集群的節(jié)點(diǎn)信息、文件信息和它們的映射關(guān)系。這些為了管理集群而存儲(chǔ)的數(shù)據(jù),叫做元數(shù)據(jù) (Metadata)。
元數(shù)據(jù)對(duì)于一個(gè)存儲(chǔ)集群來說是非常重要的,所以保存元數(shù)據(jù)的存儲(chǔ)系統(tǒng)必須也是一個(gè)集群。但是元數(shù)據(jù)集群存儲(chǔ)的數(shù)據(jù)量比較少,數(shù)據(jù)的變動(dòng)不是很頻繁,加之客戶端或者網(wǎng)關(guān)都會(huì)緩存一部分元數(shù)據(jù),所以元數(shù)據(jù)集群對(duì)并發(fā)要求也不高。一般使用類似 ZooKeeper 或者 etcd 這類分布式存儲(chǔ)就可以滿足要求。
另外,存儲(chǔ)集群為了對(duì)外提供訪問服務(wù),還需要一個(gè) 網(wǎng)關(guān)集群,對(duì)外接收外部請(qǐng)求,對(duì)內(nèi)訪問元數(shù)據(jù)和數(shù)據(jù)節(jié)點(diǎn)。網(wǎng)關(guān)集群中的每個(gè)節(jié)點(diǎn)不需要保存任何數(shù)據(jù),都是無狀態(tài)的節(jié)點(diǎn)。有些對(duì)象存儲(chǔ)沒有網(wǎng)關(guān),取而代之的是客戶端,它們的功能和作用都是一樣的。
那么,對(duì)象存儲(chǔ)是如何來處理對(duì)象讀寫請(qǐng)求的呢?這里面處理讀和寫請(qǐng)求的流程是一樣的,我們一起來說。網(wǎng)關(guān)收到對(duì)象讀寫請(qǐng)求后,首先拿著請(qǐng)求中的 Key,去元數(shù)據(jù)集群查找這個(gè) Key 在哪個(gè)數(shù)據(jù)節(jié)點(diǎn)上,然后再去訪問對(duì)應(yīng)的數(shù)據(jù)節(jié)點(diǎn)讀寫數(shù)據(jù),最后把結(jié)果返回給客戶端。
以上是一個(gè)比較粗略的大致流程,實(shí)際上這里面包含很多的細(xì)節(jié),我們暫時(shí)沒有展開講。目的是讓你在整體上對(duì)對(duì)象存儲(chǔ),以至于分布式存儲(chǔ)系統(tǒng),有一個(gè)清晰的認(rèn)知。
對(duì)象是如何拆分和保存的?
一般來說,對(duì)象存儲(chǔ)中保存的文件都是圖片、視頻這類大文件。在對(duì)象存儲(chǔ)中,每一個(gè)大文件都會(huì)被拆成多個(gè)大小相等的塊兒(Block),拆分的方法很簡(jiǎn)單,就是把文件從頭到尾按照固定的塊兒大小,切成一塊兒一塊兒,最后一塊兒長(zhǎng)度有可能不足一個(gè)塊兒的大小,也按一塊兒來處理。塊兒的大小一般配置為幾十 KB 到幾個(gè) MB 左右。
把大對(duì)象文件拆分成塊兒的目的有兩個(gè):
- 第一是為了 提升讀寫性能,這些塊兒可以分散到不同的數(shù)據(jù)節(jié)點(diǎn)上,這樣就可以并行讀寫。
- 第二是 把文件分成大小相等塊兒,便于維護(hù)管理。
對(duì)象被拆成塊兒之后,還是太過于碎片化了,如果直接管理這些塊兒,會(huì)導(dǎo)致元數(shù)據(jù)的數(shù)據(jù)量會(huì)非常大,也沒必要管理到這么細(xì)的粒度。所以一般都會(huì)再把塊兒聚合一下,放到塊兒的容器里面。
這里的「容器」就是存放一組塊兒的邏輯單元。容器這個(gè)名詞,沒有統(tǒng)一的叫法,比如在 ceph 中稱為 Data Placement。容器內(nèi)的塊兒數(shù)大多是固定的,所以容器的大小也是固定的。
到這里,這個(gè) 容器的概念,就比較 類似 于我們之前講 MySQL 和 Redis 時(shí)提到的**「分片」的概念** 了,都是復(fù)制、遷移數(shù)據(jù)的基本單位。每個(gè)容器都會(huì)有 N 個(gè)副本,這些副本的數(shù)據(jù)都是一樣的。其中有一個(gè)主副本,其他是從副本,主副本負(fù)責(zé)數(shù)據(jù)讀寫,從副本去到主副本上去復(fù)制數(shù)據(jù),保證主從數(shù)據(jù)一致。
對(duì)象存儲(chǔ)一般都不記錄類似 MySQL 的 Binlog 這樣的日志。主從復(fù)制的時(shí)候,復(fù)制的不是日志,而是整塊兒的數(shù)據(jù)。這么做有兩個(gè)原因:
- 第一個(gè)原因是基于性能的考慮。
我們知道操作日志里面,實(shí)際上就包含著數(shù)據(jù)。在更新數(shù)據(jù)的時(shí)候,先記錄操作日志,再更新存儲(chǔ)引擎中的數(shù)據(jù),相當(dāng)于在磁盤上串行寫了 2 次數(shù)據(jù)。對(duì)于像數(shù)據(jù)庫(kù)這種,每次更新的數(shù)據(jù)都很少的存儲(chǔ)系統(tǒng),這個(gè)開銷是可以接受的。但是對(duì)于對(duì)象存儲(chǔ)來說,它每次寫入的塊兒很大,兩次磁盤 IO 的開銷就有些不太值得了。 - 第二個(gè)原因是它的存儲(chǔ)結(jié)構(gòu)簡(jiǎn)單,即使沒有日志,只要按照順序,整塊兒的復(fù)制數(shù)據(jù),仍然可以保證主從副本的數(shù)據(jù)一致性。
以上我們說的 對(duì)象(也就是文件)、塊兒和容器,都是 邏輯層面的概念,數(shù)據(jù)落實(shí)到副本上,這些副本就是真正物理存在了。這些副本再被分配到 數(shù)據(jù)節(jié)點(diǎn) 上保存起來。這里的數(shù)據(jù)節(jié)點(diǎn)就是運(yùn)行在服務(wù)器上的服務(wù)進(jìn)程,負(fù)責(zé)在本地磁盤上保存副本的數(shù)據(jù)。
了解了對(duì)象是如何被拆分并存儲(chǔ)在數(shù)據(jù)節(jié)點(diǎn)上之后,我們?cè)賮砘仡?/span>一下數(shù)據(jù)訪問的流程。當(dāng)我們請(qǐng)求一個(gè) Key 的時(shí)候,網(wǎng)關(guān)首先去元數(shù)據(jù)中查找這個(gè) Key 的元數(shù)據(jù)。然后根據(jù)元數(shù)據(jù)中記錄的對(duì)象長(zhǎng)度,計(jì)算出對(duì)象有多少塊兒。接下來的過程就可以分塊兒并行處理了。對(duì)于每個(gè)塊兒,還需要再去元數(shù)據(jù)中,找到它被放在哪個(gè)容器中。
我剛剛講過,容器就是分片,怎么把塊兒映射到容器中,這個(gè)方法就是我們?cè)凇禡ySQL 存儲(chǔ)海量數(shù)據(jù)的最后一招:分庫(kù)分表》 課中講到的幾種分片算法。不同的系統(tǒng)選擇實(shí)現(xiàn)的方式也不一樣,有用哈希分片的,也有用查表法把對(duì)應(yīng)關(guān)系保存在元數(shù)據(jù)中的。找到容器之后,再去元數(shù)據(jù)中查找容器的 N 個(gè)副本都分布在哪些數(shù)據(jù)節(jié)點(diǎn)上。然后,網(wǎng)關(guān)直接訪問對(duì)應(yīng)的數(shù)據(jù)節(jié)點(diǎn)讀寫數(shù)據(jù)就可以了。
對(duì)象存儲(chǔ)是最簡(jiǎn)單的分布式存儲(chǔ)系統(tǒng),主要由 數(shù)據(jù)節(jié)點(diǎn)集群、**元數(shù)據(jù)集群 **和 **網(wǎng)關(guān)集群(或者客戶端)**三部分構(gòu)成。
- 數(shù)據(jù)節(jié)點(diǎn)集群:負(fù)責(zé)保存對(duì)象數(shù)據(jù),
- 元數(shù)據(jù)集群:負(fù)責(zé)保存集群的元數(shù)據(jù),
- 網(wǎng)關(guān)集群和客戶端:對(duì)外提供簡(jiǎn)單的訪問 API,對(duì)內(nèi)訪問元數(shù)據(jù)和數(shù)據(jù)節(jié)點(diǎn)讀寫數(shù)據(jù)。
為了便于維護(hù)和管理,大的對(duì)象被拆分為若干固定大小的塊兒,塊兒又被封裝到容器(也就分片)中,每個(gè)容器有一主 N 從多個(gè)副本,這些副本再被分散到集群的數(shù)據(jù)節(jié)點(diǎn)上保存。
對(duì)象存儲(chǔ)雖然簡(jiǎn)單,但是它具備一個(gè)分布式存儲(chǔ)系統(tǒng)的全部特征。所有分布式存儲(chǔ)系統(tǒng)共通的一些特性,對(duì)象存儲(chǔ)也都具備,比如說數(shù)據(jù)如何分片,如何通過多副本保證數(shù)據(jù)可靠性,如何在多個(gè)副本間復(fù)制數(shù)據(jù),確保數(shù)據(jù)一致性等等。
不僅是學(xué)會(huì)對(duì)象存儲(chǔ),還要對(duì)比分析一下,對(duì)象存儲(chǔ)和其他分布式存儲(chǔ)系統(tǒng),比如 MySQL 集群、HDFS、Elasticsearch 等等這些,它們之間有什么共同的地方,差異在哪兒。想通了這些問題,你對(duì)分布式存儲(chǔ)系統(tǒng)的認(rèn)知,絕對(duì)會(huì)上升到一個(gè)全新的高度。然后你再去看一些之前不了解的存儲(chǔ)系統(tǒng),就非常簡(jiǎn)單了。
對(duì)象存儲(chǔ)并不是基于日志來進(jìn)行主從復(fù)制的。假設(shè)我們的對(duì)象存儲(chǔ)是一主二從三個(gè)副本,采用半同步方式復(fù)制數(shù)據(jù),也就是主副本和任意一個(gè)從副本更新成功后,就給客戶端返回成功響應(yīng)。主副本所在節(jié)點(diǎn)宕機(jī)之后,這兩個(gè)從副本中,至少有一個(gè)副本上的數(shù)據(jù)是和宕機(jī)的主副本上一樣的,我們需要找到這個(gè)副本作為新的主副本,才能保證宕機(jī)不丟數(shù)據(jù)。
但是沒有了日志,如果這兩個(gè)從副本上的數(shù)據(jù)不一樣,我們?nèi)绾未_定哪個(gè)上面的數(shù)據(jù)是和主副本一樣新呢?一般都是基于版本號(hào)來解決,在 Leader上,KEY 每更新一次,KEY 的版本號(hào)就加 1,版本號(hào)作為 KV 的一個(gè)屬性,一并復(fù)制到從節(jié)點(diǎn)上,通過比較版本號(hào)就可以知道哪個(gè)節(jié)點(diǎn)上的數(shù)據(jù)是最新的。
如果用比較時(shí)間戳的方式來解決這個(gè)問題。這個(gè)方法理論上可行,但實(shí)際上非常難實(shí)現(xiàn),因?yàn)樗蠹荷系拿總€(gè)節(jié)點(diǎn)的時(shí)鐘都必須時(shí)刻保持同步,這個(gè)要求往往非常難達(dá)到。