Swift中大對象(大於5GB)的處理

來源:互聯網
上載者:User

本文翻譯整理自swift官方文檔《OpenStack Object Storage Administration Manual》中的“Managing Large Objects (Greater than 5 GB)”一節,並實測,驗證文檔中的內容。點這裡可以看到線上文檔。

 

預設情況中,Swfit上傳的最大單個對象的大小為5GB。然而,對於下載對象的大小卻是沒有限制的,這種“下載無限制”的概念是通過將分段對象組織起來類比實現的。大對象的每個分段被分別上傳,然後再建立一個資訊清單檔(manifest file)指明這個對象所包含的分段,下載時,清單所指示所有分段會被組織成一個單獨的對象進行下載。這種實現方式同時頁提高了上傳的速率,因為我們可以將一個對象的多個分段進行並行的上傳。

 

使用swift命令管理分段對象

嘗試這種特性的最快捷的方式,是使用OpenStackObject Storage Service用戶端工具。上傳對象時,你可以使用-S選項來指明每個分段的大小,例如:

# swift upload test_container -S 1073741824 large_file

這條命令會將large_file分成一組1G大小的分段,然後並行的上傳這些分段。一旦所有的分段都上傳完畢了,swift用戶端工具就會為這些分段建立一個manifest檔案,以保證這些分段可以作為一個對象large_file被下載。

以下命令將會下載整個大對象:

swift download test_container large_file

swift命令列工具採用一種嚴格的轉換方式來實現對分段對象的支援。在上面的例子中,swift命令列工具會首先將所有的分段上傳到另一個容器(test_container_segments)中,這些分段被命名為類似large_file/1290206778.25/21474836480/00000000, large_file/1290206778.25/21474836480/00000001等 這樣的名字。

使用一個分離的容器儲存分段的主要的好處是,當我們對主容器進行listings操作時,將不會把所有的分段名稱都展示出來。使用 <name>/<timestamp>/<size>/<segment> 這種格式明明對象的分段,是因為如果使用者上傳了一個擁有相同名稱的新對象,將會不覆蓋前一個對象,知道資訊清單檔被更新。

swift命令列工具會幫你管理這些分段檔案,在刪除、覆蓋的時候刪除這些老的分段。如果需要的話,你可以使用-leave-segments選項來覆蓋這些行為。在你希望對大對象進行多版本管理時,s這將會非常有用。

 

直接使用API管理大對象

你可以直接使用HTTP請求而非swift用戶端工具來操作分段和清單。你可以像上傳其他檔案一樣直接上傳分段、資訊清單檔,其中資訊清單檔只是一個擁有 X-Object-Manifest 頭的大小為0位元組的普通檔案。

一個對象的所有分段必須儲存在同一個容器中,擁有共同的對象名稱首碼,並按照他們應有的順序排序命名。它們可以和資訊清單檔不在同一個容器中,這可以將容器中的對象列表保持的比較乾淨。

資訊清單檔只是一個大小為0位元組、擁有 X-Object-Manifest: <container>/<prefix> 頭的普通檔案。其中<container>是對象分段所在的容器名稱,<prefix>是這些分段的公用首碼。

最好的方式是,先上傳所有的分段,再建立/更新資訊清單檔。在這種情況下,在對象完全上傳成功前,該對象是不會被下載請求訪問到的。你也可以上傳一堆新的分段到另外一個位置,然後更新資訊清單檔指向新的位置。在這些新分段上傳的過程中,原來的資訊清單檔還是可以被訪問的,即在新分段上傳的過程中,對該資訊清單檔的下載請求,還是返回原來的分段對象。

這裡有一個使用curl執行1個位元組的小分段上傳的範例:

# 首先,上傳所有的分段

$ curl -X PUT -H 'X-Auth-Token: <token>' http://<storage_url>/container/myobject/1 --data-binary '1'$ curl -X PUT -H 'X-Auth-Token: <token>' http://<storage_url>/container/myobject/2 --data-binary '2'$ curl -X PUT -H 'X-Auth-Token: <token>' http://<storage_url>/container/myobject/3 --data-binary '3'

# 然後,建立資訊清單檔

curl -X PUT -H 'X-Auth-Token: <token>' -H 'X-Object-Manifest: container/myobject/' http://<storage_url>/container/myobject --data-binary '' 

# 現在我們可以將這些分段當做一個對象進行下載

curl -H 'X-Auth-Token: <token>' http://<storage_url>/container/myobject

 

大對象的額外注意點

對資訊清單檔執行一個GET或HEAD請求,X-Object-Manifest: <container>/<prefix> 頭會聯通串聯後的對象一起返回,因此你可以根據 X-Object-Manifest 判斷是從哪裡擷取的分段。

對資訊清單檔執行GET或HEAD請求,回應標頭 Content-Length 的值為其指向的所有分段的大小總和,因此這個值是動態。因此,當你在建立資訊清單檔後,繼續上傳了額外的分段,將會使串聯對象變得更大,但無需重新構造資訊清單檔。

對資訊清單檔執行GET或HEAD請求,回應標頭 Content-Type 的值與使用PUT請求建立該清單時指定的 Content-Type 相同。因此,你可以通過重新發送PUT請求來改變 Content-Type 的值。

對資訊清單檔執行GET或HEAD請求,回應標頭 ETag 的值將為其指向的每個分段的ETag串聯字串的MD5值,因此這個值是動態。通常,swift中對象的ETag值是對象內容的MD5值,這對每個單獨的分段也是成立的。但是,資訊清單檔本身不會產生這樣的一個ETag值,因此這個方法至少可以用於判斷檔案的改變。

 

大Object Storage Service的曆史和背景

在目前的版本的實現方式之前,大對象的支援已經經曆了若干次迭代實現。

在swift中限制單個對象的大小的最主要原因是為了平衡ring上的不同分區。為了保證叢集中的磁碟可以被均勻的利用,很顯然我們需要將大檔案分解成更小的分段,然後在讀操作的過程中將小分段合并。

在提出大對象的支援之前,一些應用已經在用戶端將它們要上傳的對象分解成一些獨立的段,然後再對這些分段進行重組。這種設計使得用戶端可以支援備份並歸檔大資料集,然而由於網路中斷等問題,卻不能很好的提高效能、減少錯誤。採用這種方法最主要的缺點是,由於需要原始的劃分資訊來重組對象,因此在一些情境(如CDN源)中這種方法就不太實際。

為了消除用戶端對於儲存大於5GB對象需求的障礙,我們最初設計了一種對大對象上傳完全透明的支援方式。為了實現完全透明的大對象上傳,proxy server將會在對象上傳的過程中負責將這個大對象分段,而不需要修改任何的API。因此所有的分段都被完全隱藏在了用戶端API之後。

然而,這種實現方式將引起一些問題:它不支援用戶端並行的上傳一個對象,並且沒有錯誤恢複機制。相較於完全透明的分段方式帶來的益處,它的複雜性還是顯得太高了。

我們當前使用的“user manifest”設計時為了給用戶端提供一個透明的大對象下載方式,同時也可以支援分段上傳,並保持用戶端API的乾淨、整潔。

另一種“顯示的”user manifest實現方式也被討論過的,它需要預先定義列出已上傳分段的格式。雖然這種方式可以提供一些潛在的優點,但是為了實現一個更簡單的API,我們應避免將負擔推到用戶端(實際上,就是決定“X-Object-Manifest”的格式)。

在開發的過程中,我們注意到,這種基於公用路徑首碼的“隱式的”user manifest方法將被會被最終一致性視窗的大小影響,理論上會產生這種情況:在上傳了一個對象很短的時間內,對資訊清單檔執行GET操作,將會返回一個無效的對象。事實上,除非你的swift運行在一個並發上傳度非常高的測試環境中,你將不太可能會遇到這種情況(在並發上傳非常高的測試環境中不運行object-updaters和container-replicators)。

像所有的OpenStackObject Storage Service中的特性一樣,支援大對象是一個發展中的特性,它將繼續改善,並可能隨時間而改變。

 

實測結果1. swift CLI 進行大檔案上傳、下載

測試檔案repost_backup2.sql的大小為12.3G。

1)直接上傳,會報錯,檔案太大。 

2)指定分段上傳,每段5G。swift CLI工具將檔案分成3段,並建立了資訊清單檔。

3)上傳成功後,查看容器,發現新增了docs_segments容器。

4)目標容器中只有repost_backup2.sql資訊清單檔,並沒有分段對象;docs_segments容器中儲存分段對象。

5)下載時,將分段對象作為一個大對象下載。

 

實測結果2. curl 進行分段上傳、合并下載

1)分別上傳3個1位元組大小的檔案,公用首碼為"myobject/"

2)建立資訊清單檔,指定X-Object-Manifest的值為公用首碼"docs/myobject/",並命名為使用者知曉的myobject。

3)查看上傳結果,可以看到3個分段對象和1個資訊清單檔。

4)下載myobject對象,可以看到返回結果將3個分段檔案進行了串聯,並只返回一個myobject對象。

 

此外,通過仔細觀察HTTP的頭資訊,我們也可以應正“大對象的額外注意點”小節中闡述的內容。 

 

一些思考

至此,想到了一些在開發API時需要注意的問題:

  1. Object的類型定義。由於偽目錄的存在,需要為object增加subdir屬性,用於區別偽目錄和真實的對象;由於資訊清單檔的存在,object的屬性應增加對X-Object-Manifest中繼資料的過濾,用於判斷資訊清單檔;
  2. 容器的listing操作。為了對使用者隱藏分段過程,在執行listing操作的時候怎樣才能足夠“乾淨”?模仿swift CLI為每個容器建立一個“容器_segments”的分段容器,並限制使用者不能建立/訪問“_segments”結尾的容器?
  3. 根據2,想到對象版本的管理,類似的為具有版本的容器建立“容器_versions”的曆史版本容器,並限制使用者不能建立/訪問“_versions”結尾的容器?
  4. 對象可以並行上傳,是否也可以並行下載?
  5. 大對象的更新操作,可以對其每個分段進行檢測,只更新有變動的分段?

這些都是可以做的工作點,前3個屬於API層的約束問題,較好實現;後2個應權衡swift的實現機制,做一些測試進行驗證。

 

更新更新 

為了給自己和看文的你更多的思考空間,我就不在上面每條問題的下面直接給出目前的想法了,更新在這裡=D:

1. 關於Object的類型定義問題。

確實需要增加一個 X-Object-Manifest 屬性,雖然分段的過程我們是希望對SDK使用者透明,但也不排除人家也有這個權利知道某個資訊清單檔的真實身份。做了測試,在對Container中的對象進行listing操作時,以JSON格式擷取詳情為例,以下為返回內容:

[
{"hash": "c4ca4238a0b923820dcc509a6f75849b", "last_modified": "2013-03-21T13:51:18.917260", "bytes": 1, "name": "1", "content_type": "application/x-www-form-urlencoded"}, {"hash": "c81e728d9d4c2f636f067f89cc14862c", "last_modified": "2013-03-21T13:51:41.530770", "bytes": 1, "name": "2", "content_type": "application/x-www-form-urlencoded"}, {"hash": "eccbc87e4b5ce2fe28308fd9f2a7baf3", "last_modified": "2013-03-21T13:51:59.653640", "bytes": 1, "name": "3", "content_type": "application/x-www-form-urlencoded"}, {"hash": "d41d8cd98f00b204e9800998ecf8427e", "last_modified": "2013-03-21T13:55:33.353110", "bytes": 0, "name": "myobject", "content_type": "application/x-www-form-urlencoded"}, {"hash": "c4ca4238a0b923820dcc509a6f75849b", "last_modified": "2013-03-21T13:54:19.956880", "bytes": 1, "name": "myobject/1", "content_type": "application/x-www-form-urlencoded"}, {"hash": "c81e728d9d4c2f636f067f89cc14862c", "last_modified": "2013-03-21T13:54:32.005000", "bytes": 1, "name": "myobject/2", "content_type": "application/x-www-form-urlencoded"}, {"hash": "eccbc87e4b5ce2fe28308fd9f2a7baf3", "last_modified": "2013-03-21T13:54:42.420390", "bytes": 1, "name": "myobject/3", "content_type": "application/x-www-form-urlencoded"}, {"hash": "d41d8cd98f00b204e9800998ecf8427e", "last_modified": "2013-03-21T13:27:49.747290", "bytes": 0, "name": "repost_backup2.sql", "content_type": "application/x-sql"},{"hash": "b2cfa4183267af678ea06c7407d4d6d8", "last_modified": "2013-03-21T13:16:14.842310", "bytes": 10, "name": "testFile", "content_type": "application/octet-stream"}
]

標紅的兩個對象實際上都是資訊清單檔,但是在列表中卻與其他普通對象沒有任何區別:除了長度為0。但需要知道的是,就是一個普通對象也可以是長度為0的呀。所以 X-Object-Manifest 在這裡幾乎用不到。

然而,對資訊清單檔執行HEAD或GET操作,我們就可以看到 X-Object-Manifest 了,並且對象的長度也不再是0,而是分段對象的總和,即大對象的真正長度。因此,我們為Object對象添加manifest屬性的用武之地就是“對資訊清單檔執行HEAD或GET操作”。

2. 容器的listing操作。

Swift CLI 將分段對象單獨存在另一個容器中的方式蠻好的,這樣可以避免對目標容器listing時出現很多詭異的分段對象(分段對象的屬性和普通對象無區別,所以很難做過濾)。

然而,swift CLI 建立的容器也與普通容器沒差別,因此在對Account做listing操作的時候會多出一些xxx_segments容器,這樣也是不好的。

一種想法是限制使用者容器命名,以“_segments”結尾的名稱不得用於容器;

另一種想法是保留一個 X-Container-Meta-Segment 屬性,類似程式設計語言裡的保留字,屬性值為分段對象的資訊清單檔所在的容器名稱(不同容器公用一個分段容器時,可以考慮設定其他的值,總之是需要這個屬性來做區別)。這樣可以在SDK層根據X-Container-Meta-Segment過濾,從而在account listing時隱藏分段對象。

可是,以上兩種基於API層的過濾改進還是存在問題,即對account進行HEAD操作時顯示的容器數量還是包含分段容器的,因此如果要很完美的做到“透明”,則不可避免的需要動到源碼,而容器在ring上的分布也因此受到影響。

感覺上還是沒有完美的方案,所以根據需求權衡吧,或者直接放棄“透明”的想法,讓SDK使用者自己也做一些業務約束。

 

 

相關文章

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.