深入雲存儲系統Swift存儲節點:存儲實現分析


   原文鏈接:http://www.cnblogs.com/yuxc/archive/2012/07/04/2575536.html 

   作者:余興超 @SAE.Sina Web Service Team

   請尊重作者辛勤勞動,轉載請標注鏈接和作者   

 

 

  在深入雲存儲系統Swift核心組件:Ring實現原理剖析深入雲存儲系統Swift核心組件:Ring數據結構及構建、重平衡操作兩篇博文中,我們詳細地分析了Swift中數據的映射機制和具體操作。那么在集群中的每一台存儲節點上,Swift是如何實現Account、Container、Object的具體存儲呢?本篇旨在分析Storage nodepartitionpartitiondata間的映射關系在實際存儲目錄中的以何種格式存儲,即怎么存,存什么。

   在Storage node上運行着Linux系統並使用了XFS文件系統,邏輯上使用一致性哈希算法將固定總數的partition映射到每個Storage node上,每個Data也使用同樣的哈希算法映射到Partition上,其層次結構如下圖所示:


Figure1:Stoage node hierachy

 

以我們的一台storage node sws51為例,該device的文件路徑掛載到/srv/node/sdc,目錄結構如下所示:

 

root@sws51:/srv/node/sdc# ls

accounts  async_pending  containers  objects  quarantined  tmp

 

 

  其中accountscontainersobjects分別是賬號、容器、對象的存儲目錄,async_pending是異步待更新目錄,quarantined是隔離目錄,tmp是臨時目錄。

 

1.objects目錄

  在objects目錄下存放的是各個partition目錄,其中每個partition目錄是由若干個suffix_path名的目錄和一個hashes.pkl文件組成,suffix_path目錄下是由objecthash_path名構成的目錄,在hash_path目錄下存放了關於object的數據和元數據,object存儲目錄的層次結構如圖2所示。

wps_clip_image-16011

Figure2: Object directory hierachy

 

hashes.pkl是存放在每個partition中的一個2進制pickle化文件。例如:

root@sws50:/srv/node/sdc/objects/100000# ls

8bd  hashes.pkl

 

In [1]: with open('hashes.pkl', 'rb') as fp:

   ...:     import pickle

   ...:     hashes = pickle.load(fp)

   ...:    

   ...:    

 

In [2]: hashes

Out[2]: {'8bd': '9e99c8eedaa3197a63f685dd92a5b4b8'}

 

8bdsuffix_dir,而9e99c8eedaa3197a63f685dd92a5b4b8則是該partition下數據的md5哈希值。

 

Object path生成過程

object的存儲路徑由object server進程內部稱為DiskFile類初始化時產生,過程如下:

1.由文件所屬的accountcontainerobject名稱產生'/account/container/object'格式的字符串,和HASH_PATH_SUFFIX組成新的字符串,調用hash_path函數,生成md5 hashname_hash。其中HASH_PATH_SUFFIX作為salt來增加安全性,HASH_PATH_SUFFIX值存放在/etc/swift/swift.conf中。

2. 調用storage_directory函數,傳入DATADIR, partition, hash_path參數生成DATADIR/partition/name_path[-3:]/name_path格式字符串

3. 連結path/devcie/storage_directory(DATADIR, partition,name_ hash)生成數據存儲路徑datadir

4. 調用normalize_timestamp函數生成“16.5位”的時間戳+擴展名的格式生成對象名稱

 

 

  例如,某object的存儲路徑為:/srv/node/sdc/objects/19892/ab1/136d0ab88371e25e16663fbd2ef42ab1/1320050752.09979.data

  其中每個目錄分別表示:

 

 

wps_clip_image-23876

Figure3: Object directory represention

 

Object數據

Object的數據存放在后綴為.data的文件中,它的metadata存放在以后綴為.meta的文件中,將被刪除的Object以一個0字節后綴為.ts的文件存放。

 

2.accounts目錄

accounts目錄下存放的是各個partition,而每個partition目錄是由若干個suffix_path目錄組成,suffix_path目錄下是由accounthsh名構成的目錄,在hsh目錄下存放了關於accountsqlite dbaccount存儲目錄的層次結構如圖4所示。

wps_clip_image-24231

Figure4: Account directory hierachy

 

Account path生成過程

  account使用AccountController類來生成path,其過程與object類似,唯一的不同之處在於,accountdb命名調用hash_path(account)來生成,而不是使用時間戳的形式。例如,某accountdb存儲路徑為:/srv/node/sdc/accounts/20443/ac8/c7a5e0f94b23b79345b6036209f9cac8/ c7a5e0f94b23b79345b6036209f9cac8.db

 

wps_clip_image-9097

   Figure5: Object directory represention

Account db數據

 

  在accountdb文件中,包含了account_statcontainerincoming_sync outgoing_sync 4張表。

  表account_stat是記錄關於account的信息,如名稱、創建時間、container數統計等等,其schema如下:

CREATE TABLE account_stat (
                account TEXT,
                created_at TEXT,
                put_timestamp TEXT DEFAULT '0',
                delete_timestamp TEXT DEFAULT '0',
                container_count INTEGER,
                object_count INTEGER DEFAULT 0,
                bytes_used INTEGER DEFAULT 0,
                hash TEXT default '00000000000000000000000000000000',
                id TEXT,
                status TEXT DEFAULT '',
                status_changed_at TEXT DEFAULT '0',
                metadata TEXT DEFAULT ''
            );

 

  account表示account名稱,created_at表示創建時間,put_timestamp表示put request的時間戳,delete_timestamp表示delete request的時間戳,container_countcountainer的計數,object_countobject的計數,bytes_used表示已使用的字節數,hash表示db文件的hash值,id表示統一標識符,status表示account是否被標記為刪除,status_changed_at表示狀態修改時間,metadata表示account的元數據。

test賬號為例,該db的表account_stat中存放了以下數據項:

 

wps_clip_image-5299

 

  表container記錄關於container的信息schema如下:

CREATE TABLE container (
                ROWID INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT,
                put_timestamp TEXT,
                delete_timestamp TEXT,
                object_count INTEGER,
                bytes_used INTEGER,
                deleted INTEGER DEFAULT 0
            );
CREATE INDEX ix_container_deleted_name ON
                container (deleted, name);
CREATE TRIGGER container_delete AFTER DELETE ON container
            BEGIN
                UPDATE account_stat
                SET container_count = container_count - (1 - old.deleted),
                    object_count = object_count - old.object_count,
                    bytes_used = bytes_used - old.bytes_used,
                    hash = chexor(hash, old.name,
                                  old.put_timestamp || '-' ||
                                    old.delete_timestamp || '-' ||
                                    old.object_count || '-' || old.bytes_used);
            END;
CREATE TRIGGER container_insert AFTER INSERT ON container
            BEGIN
                UPDATE account_stat
                SET container_count = container_count + (1 - new.deleted),
                    object_count = object_count + new.object_count,
                    bytes_used = bytes_used + new.bytes_used,
                    hash = chexor(hash, new.name,
                                  new.put_timestamp || '-' ||
                                    new.delete_timestamp || '-' ||
                                    new.object_count || '-' || new.bytes_used);
            END;
CREATE TRIGGER container_update BEFORE UPDATE ON container
            BEGIN
                SELECT RAISE(FAIL, 'UPDATE not allowed; DELETE and INSERT');
            END;

  其中ROWID字段表示自增的主鍵,name字段表示container的名稱,put_timestampdelete_timestamp分別表示containerputdelete的時間戳,object_count表示container內的object數, bytes_used  表示已使用的空間,deleted表示container是否標記為刪除。

  賬號testaccount表中的數據項如下所示:

wps_clip_image-28912

 

  表incoming_sync記錄到來的同步數據項,其schema如下:

CREATE TABLE incoming_sync (
                remote_id TEXT UNIQUE,
                sync_point INTEGER,
                updated_at TEXT DEFAULT 0
            );
CREATE TRIGGER incoming_sync_insert AFTER INSERT ON incoming_sync
            BEGIN
                UPDATE incoming_sync
                SET updated_at = STRFTIME('%s', 'NOW')
                WHERE ROWID = new.ROWID;
            END;
CREATE TRIGGER incoming_sync_update AFTER UPDATE ON incoming_sync
            BEGIN
                UPDATE incoming_sync
                SET updated_at = STRFTIME('%s', 'NOW')
                WHERE ROWID = new.ROWID;
            END;

  remote_id字段表示遠程節點的idsync_point字段表示上一次更新所在的行位置,updated_at字段表示更新時間。

 

賬號test的表incoming_sync中的數據項如下所示:

wps_clip_image-16251

 

 

outgoing_sync表示推送出的同步數據項,其schema如下:

CREATE TABLE outgoing_sync (
                remote_id TEXT UNIQUE,
                sync_point INTEGER,
                updated_at TEXT DEFAULT 0
            );
CREATE TRIGGER outgoing_sync_insert AFTER INSERT ON outgoing_sync
            BEGIN
                UPDATE outgoing_sync
                SET updated_at = STRFTIME('%s', 'NOW')
                WHERE ROWID = new.ROWID;
            END;
CREATE TRIGGER outgoing_sync_update AFTER UPDATE ON outgoing_sync
            BEGIN
                UPDATE outgoing_sync
                SET updated_at = STRFTIME('%s', 'NOW')
                WHERE ROWID = new.ROWID;
            END;

  remote_id字段表示遠程節點的idsync_point字段表示上一次更新所在的行位置,updated_at字段表示更新時間。

賬號test的表remote_id中的數據項如下所示:

wps_clip_image-4803

 

 

3.Container目錄

Container目錄結構和生成過程與Account類似,Containerdb中共有5張表,其中incoming_syncoutgoing_syncschemaAccount中的相同。其他3張表分別為container_statobjectsqlite_sequence

container_stat與表account_stat相似,其區別是container_stat存放的是關於container信息:

CREATE TABLE container_stat (
                account TEXT,
                container TEXT,
                created_at TEXT,
                put_timestamp TEXT DEFAULT '0',
                delete_timestamp TEXT DEFAULT '0',
                object_count INTEGER,
                bytes_used INTEGER,
                reported_put_timestamp TEXT DEFAULT '0',
                reported_delete_timestamp TEXT DEFAULT '0',
                reported_object_count INTEGER DEFAULT 0,
                reported_bytes_used INTEGER DEFAULT 0,
                hash TEXT default '00000000000000000000000000000000',
                id TEXT,
                status TEXT DEFAULT '',
                status_changed_at TEXT DEFAULT '0',
                metadata TEXT DEFAULT '',
                x_container_sync_point1 INTEGER DEFAULT -1,
                x_container_sync_point2 INTEGER DEFAULT -1
            );

 

  其中account字段表示container所示的accountcontainer字段表示container名稱,created_at表示創建時間,put_timestamp表示put request的時間戳,delete_timestamp表示delete request的時間戳,object_count表示object計數,bytes_used表示使用空間,hash表示db文件的哈希值, reported_put_timestamp, reported_delete_timestamp, reported_object_count, reported_bytes_used表示reported的狀態信息,id表示統一標識符,status表示container狀態,status_changed_at表示更改時間,metadata表示container的元數據,x_container_sync_point1表示同步點1x_container_sync_point2表示同步點2.

以名稱為testcontainer db為例,其中的表container_stat數據項如下:

wps_clip_image-25120

 

objectschema如下:

CREATE TABLE object (
                ROWID INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT,
                created_at TEXT,
                size INTEGER,
                content_type TEXT,
                etag TEXT,
                deleted INTEGER DEFAULT 0
            );
CREATE INDEX ix_object_deleted_name ON object (deleted, name);
CREATE TRIGGER object_delete AFTER DELETE ON object
            BEGIN
                UPDATE container_stat
                SET object_count = object_count - (1 - old.deleted),
                    bytes_used = bytes_used - old.size,
                    hash = chexor(hash, old.name, old.created_at);
            END;
CREATE TRIGGER object_insert AFTER INSERT ON object
            BEGIN
                UPDATE container_stat
                SET object_count = object_count + (1 - new.deleted),
                    bytes_used = bytes_used + new.size,
                    hash = chexor(hash, new.name, new.created_at);
            END;
CREATE TRIGGER object_update BEFORE UPDATE ON object
            BEGIN
                SELECT RAISE(FAIL, 'UPDATE not allowed; DELETE and INSERT');
            END;

 

test container db的表object數據項如下所示:

wps_clip_image-18073

 

4. tmp目錄

tmp目錄作為account/container/object serverpartition目錄內寫入數據前的臨時目錄。

例如,在client向服務端上傳某一文件,object server調用DiskFile類的mkstemp方法在創建路徑為path/device/tmp的目錄。在數據上傳完成之后,調用put()方法,將數據移動到相應路徑。

 

5. async_pending目錄

   本地server在與remote server建立http連接或者發送數據時超時導致更新失敗時,將把文件放入async_pending目錄。這種情況經常發生在系統故障或者是高負荷的情況下。如果更新失敗,次更新被加入隊列,然后Updater繼續處理這些失敗的更新工作。例如,假設一個container server處於負荷下此時一個新的對象被加入到系統。當Proxy成功地響應Client的請求,這個對象將變為直接可訪問的。但是container服務器並沒有更新對象列表,本次更新將進入隊列等待延后的更新。所以,container列表不可能馬上就包含這個新對象。隨后Updater使用object_sweep掃描device上的async pendings目錄,遍歷每一個prefix目錄並執行升級。一旦完成升級,則移除pending目錄下的文件(實際上,是通過調用renamer函數將文件移動到object相應的目錄下)

為了驗證以上過程,通過執行一個並發上傳1000個文件的腳本,觀察sws50async_pending目錄下的所發生的變化。async_pending的路徑為/srv/node/sdc/async_pending/,在執行腳本前,該目錄下為空。腳本執行完畢后,async_pending目錄下產生了一些prefix目錄,cd到一個prefixcb9的目錄中,觀察其中的數據:

 

root@sws50:/srv/node/sdc/async_pending/cb9# ll

total 24

-rw------- 1 swift swift 324 2011-11-08 10:15 69a5ee25ea7a4a4b08ea47102930fcb9-1320718532.01864

-rw------- 1 swift swift 324 2011-11-08 10:15 69a5ee25ea7a4a4b08ea47102930fcb9-1320718537.04863

-rw------- 1 swift swift 324 2011-11-08 10:15 69a5ee25ea7a4a4b08ea47102930fcb9-1320718543.08122

-rw------- 1 swift swift 324 2011-11-08 10:15 69a5ee25ea7a4a4b08ea47102930fcb9-1320718550.13288

-rw------- 1 swift swift 324 2011-11-08 10:15 69a5ee25ea7a4a4b08ea47102930fcb9-1320718558.18801

-rw------- 1 swift swift 324 2011-11-08 10:16 69a5ee25ea7a4a4b08ea47102930fcb9-1320718567.25494

 

文件路徑的組成如下圖所示,其中數據名稱是由hash_path后面緊跟-,后面是以發送container requestheader中包含的時間戳所產生:

 

wps_clip_image-22259

 Figure6:  async_pendingdirectory represention

 

2分鍾之后,查看該目錄下的文件,僅剩下一個文件:

root@sws50:/srv/node/sdc/async_pending/cb9# ll

total 4

-rw------- 1 swift swift 356 2011-11-08 10:18 69a5ee25ea7a4a4b08ea47102930fcb9-1320718567.25494

最后,async_pending目錄變為空。

 

  account和containerdb pending文件並不會獨立地存在於async_pending目錄下,它們的pending文件會與其db文件在一個目錄下存放。例如:

containerdb文件為b8e7f40f8c2012d17aca4e0483d391d0.db,其pending文件為b8e7f40f8c2012d17aca4e0483d391d0.db.pending,一起存放在suffix目錄1d0下。

再次執行測試腳本觀察1d0目錄下的變化,執行前pending文件的大小為0kb,執行過程中,pending的大小慢慢增加到12kb左右,接着又緩慢下降直到0kb。讀取此過程某一時刻的pending文件。

其中內容如下所示:

':gAIoVQM3MzVxAVUQMTMyMTE4NjczMy4yNTQ0N3ECSgBwAQBVGGFwcGxpY2F0aW9uL29jdGV0LXN0

\ncmVhbXEDVSBkYmQzZjhmYjQ1ZmQyZjBkZGZmNTA1ODZkNWU0ZGY3ZnEESwB0Lg==\n:gAIoVQM3Mzh

xAVUQMTMyMTE4NjczMy41MjM4MXECTQDcVRhhcHBsaWNhdGlvbi9vY3RldC1zdHJl\nYW1xA1UgOGI3YzR

iZGVlYzNkZGU4ZDI5OWU1Yzk1ZmE1N2ExZWVxBEsAdC4=\n:gAIoVQM3MzlxAVUQMTMyMTE4NjczMy42M

zg0NnECTQCgVRhhcHBsaWNhdGlvbi9vY3RldC1zdHJl\nYW1xA1UgMmQ1ZDlhYjk0MzlkMTNiMmZhODhiZmF

mNTk3NTRkMjZxBEsAdC4=\n:g..........................................AIoVQM3NDdxAVUQMTM

yMTE4NjczNC40MzIxNnECSgBoAQBVGGFwcGxpY2F0aW9uL29jdGV0LXN0\ncmVhbXEDVSBjYTgzNmZhY2Fh

MzY0MGQwNDc4YTU5OGQzZmUzYmRiNHEESwB0Lg==\n:gAIoVQM3NDlxAVUQMTMyMTE4NjczNC42MzA

1NXECTQCUVRhhcHBsaWNhdGlvbi9vY3RldC1zdHJl\nYW1xA1UgY2Y5NWU3MDIxNWEzOTFlNzcwZDBkODB

jZjlhN2Q5OTlxBEsAdC4=\n:gAIoVQM3NTBxAVUQMTMyMTE4NjczNC43NTA2MXECSgAoAQBVGGFwcGxpY2

F0aW9uL29jdGV0LXN0\ncmVhbXEDVSAyYzU4Zjc3ZGIwMGUxMTgxNjZmNjg2Zjc0YzlmZmNjZHEESwB0Lg==\n'

 

使用:對以上字符串進行分割成list,我們得到第一個非空元素

y[1]'gAIoVQM3MzVxAVUQMTMyMTE4NjczMy4yNTQ0N3ECSgBwAQBVGG

FwcGxpY2F0aW9uL29jdGV0LXN0\ncmVhbXEDVSBkYmQzZjhmYjQ1ZmQyZjBk

ZGZmNTA1ODZkNWU0ZGY3ZnEESwB0Lg==\n'

 

使用pickle模塊對其進行解碼pickle.loads(y[1].decode('base64')),獲得一個dict類型的數據:

name, timestamp, size, content_type, etag,deleted=

('735',

'1321186733.25447',

94208,

'application/octet-stream',

'dbd3f8fb45fd2f0ddff50586d5e4df7f',

0)

表示一個名稱為735,大小為94208Bmd5哈希值為dbd3f8fb45fd2f0ddff50586d5e4df7f,可訪問的字節流文件。此過程由ContainerBroker類的_commit_puts方法完成,隨后使用put_object方法把這些數據項放入container dbobject表中,.pending文件中的數據類型與object表中的字段定義一致。

accountcontainerdbobject兩者的pending文件處理方式中發現其不同之處在於,dbpending文件在更新完其中的一項數據之后,刪除pending文件中的相應的數據項,而object的數據在更新完成之后,移動pending文件到目標目錄。

 

6. quarantined目錄

Auditor進程會在本地服務器上每隔一段時間就掃面一次磁盤來檢測accountcontainerobject的完整性。一旦發現不完整的數據,該文件就會被隔離,該目錄就稱為quarantined目錄。為了限制Auditor消耗過多的系統資源,其默

認掃描間隔是30秒,每秒最大的掃描文件數為20,最高速率為10Mb/s

obj auditor使用AuditorWorker類的object_audit方法來檢查文件的完整性,該方法封裝了obj serverDiskFile類,該類有一個_handle_close_quarantine方法,用來檢測文件是否需要被隔離,如果發現損壞,則直接將文件移動到隔離目錄下。隨后replicator從其他replica那拷貝新的文件來替換,最后Server計算文件的hash值是否正確。整個處理流程如下所示:

 

image

 Figure7: quarantined object處理流程

 

為了驗證auditor的有效性,做一個簡單的測試,在路徑為srv/4/node/sdb4/objects/210/82c/003499609ba80372d62aa39a9f9a482c/1321186693.02822.data的文件中隨意寫入了一串字符。約過了10秒之后,發現sdb4/目錄下建立了一個quarantined目錄,其中包含object目錄,里面損壞了的文件,其路徑為/srv/4/node/sdb4/quarantined/objects/003499609ba80372d62aa39a9f9a482c/1321186693.02822.data。此時,打開原目錄下的文件,已恢復為修改前的狀態。

account使用AccountAuditor類的account_audit方法,container使用ContainerAuditor類的container_audit方法對目錄下的db文件進行檢查。然而,我在測試時,對container目錄下的某db文件進行了修改,大約經過了數分鍾后,該db文件才被隔離。在閱讀源代碼時,發現accountcontainerauditor的掃面間隔與object差異較大。在類初始化時設置了一個interval的變量,默認為1800s,然后在run_forever中傳入該參數到random函數來設置休眠時間,每次執行的間隔在180~1800s之間,也就是330分鍾。設置較長間隔的原因,我認為主要是由於每次檢查db文件前,需要鎖住db文件,如果檢查的頻率過於頻繁會影響存儲節點的db正常的讀寫性能。

 

總結

存儲結點上的各路徑由不同的進程產生和維護,Accounts目錄存放了關於account的信息,主要記錄account下的container信息,Containes目錄存放了關於 container的信息,主要記錄了該container下的object信息,Objects目錄則是存放了文件的數據和元數據,tmp目錄用於數據寫入以上目錄前的臨時目錄, async_pending存放未能及時更新而被加入更新隊列的數據,quaraninued路徑用於隔離發生損壞的數據

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM