qcow2文件的snapshot管理

qcow2是qemu虛擬機中特別常用的鏡像文件格式,QCOW即Qemu Copy-On-Write寫時拷貝,後面的2即爲版本,因爲在qcow2出現之前還有qcow格式的鏡像文件。從字面上理解,qcow/qcow2的文件組織形式應該是建立在Copy-On-Write這個基本的機制上,即當某個數據塊被引用多次(兩次或兩次以上)時,若某個實例嘗試寫該數據塊,爲了不讓其他實例看到該數據塊的變化,就會將該數據塊拷貝一份,然後將修改生效到拷貝的數據塊上。這個機制和內存的Copy-On-Write的機制是基本一致的。

qcow2鏡像文件組織結構

qcow2鏡像文件以固定大小的單元組成,每個單元稱作cluster,每個cluster在物理磁盤中都是連續分佈的,便於數據的讀取和寫入。cluster的大小即爲虛擬機看到的虛擬磁盤基本單元大小,類似於機械硬盤的sector大小,一個cluster可能對應一個sector或者連續多個sector,取決與cluster size和sector size的比例。

虛擬磁盤的數據被拆分爲一個個cluster,然後這些cluster用一個二級索引表組織起來,第一級稱作L1 Table,第二級稱作L2 Table,其組織形式如下所示:

其中L1 Table的大小可變,可能只佔用一個cluster,也可能佔用多個連續的cluster,L1 Table的大小在虛擬磁盤創建的那一刻就已經決定,並且在cluster大小相同的情況下,虛擬磁盤的大小越大,L1 Table的size也越大。每個L2 Table則固定佔用一個cluster,不同的L2 Table不需要放在相鄰的cluster上。L1 Table和L2 Table中每個entry的大小爲64bit,因爲以目前的磁盤容量來看,64bit的地址已經能夠完全滿足尋址需求了。

另外,由於qcow2文件支持在文件內部建立snapshot(在文件外建立snapshot即爲差分文件),故需要對每個cluster的使用進行記錄,即需要記錄下每個cluster被引用的次數,這樣當某個snapshot被刪除的時候,才能夠知道哪些cluster可以釋放出來繼續使用,哪些cluster還在被使用,主機不能主動修改。爲了維持對cluster引用次數的記錄,qcow2文件中同樣使用一個二級的頁表來記錄所有cluster的引用計數,如下圖所示:

recfound table爲第一級表,refcount block爲第二級表,refcount table的大小是可變的,並且需要佔用連續的cluster。在cluster size大小一樣的情況下,虛擬機磁盤的容量越大,refcount table的大小也越大,就像L1 Table一樣。每個refcount block佔用一個cluster,並且不同的refcount block不需要佔用連續的cluster。refcount block中,每個entry的大小是可以設置的,不一定是類似L2 Table的64bit,默認是16bit,因爲每個entry只是一個簡單的計數,沒必要使用64bit,浪費空間。

L1 Table和refcount table的索引入口都在qcow2文件的頭一個cluster中。qcow2鏡像文件的第一個cluster即爲qcow2鏡像文件的頭部,該頭部包含了許多重要的信息,其定義位於qemu源碼的block/qcow2.h頭文件中。

主要包括:

  • magic: QCOW magic字符串("QFI\xfb")
  • backing_file_offset,該qcow2鏡像文件的base文件名字在該qcow2文件中存放的位置。如果該qcow2文件沒有base文件,則該offset爲0。
  • cluster_bits,即用於定位一個cluster內某個字節數據所需要用到的地址位數,如cluster的大小爲512B,則cluster_bits爲9,若cluster的大小爲4KB,則cluster_bits爲12。
  • size,即爲虛擬磁盤的大小
  • l1_size,表示L1 table中entry的個數
  • l1_table_offset,表示存放L1 table的cluster在qcow2文件中的位置。
  • refcount_table_offset,在該qcow2文件中refcount table所存放的位置。
  • refcount_table_clusters,用於表示refcount table的大小,即佔用的cluster個數。
  • nb_snapshot,即該qcow2文件中,存在的snapshot的個數。
  • snapshot_offset,即存放snapshot的cluster在qcow2文件中的位置。

qcow2鏡像文件snapshot管理

qcow2文件第一個cluster中定義的l1_table_offset、l1_size、refcount_table_offset/clusters定義了L1 Table和refcount table的入口地址和大小,qcow2程序就可以通過該入口找到虛擬磁盤中的所有數據了。另外頭部中還提供了nb_snapshot和snapshot_offset,這兩個區域定義了該qcow2文件中存在的snapshot個數和存放snapshot所在的cluster位置。

snapshot本質上其實是qcow2文件對應的虛擬磁盤的不同入口,即不同的L1 Table,不同的snapshot對應不同的L1 Table,根據Copy-On-Write的基本管理機制,當不同的L1 Table引用相同的L2 Table時,它們將共用一份物理磁盤上的L2 Table,但是若通過L1 Table對對應的L2 Table進行修改時,將會觸發L2 Table進行拷貝和修改,使得不同的L1 Table使用不同的L2 Table,所以不同的snapshot只需要維護不同的L1 Table,不需要單獨對L2 Table進行維護。另外,一個qcow2鏡像文件中,所有的snapshot將會共用同一個refcount table和refcount block頁表。

每個snapshot table entry的格式如下所示:

對qcow2文件中snapshot的管理對應到qemu-img snapshot命令,snapshot命令支持創建(-c)、刪除(-d)、羅列(-l)和應用(-a)的操作。

  • 創建,其底層操作就是將當前使用的L1 Table拷貝一份到新分配的一個或多個連續的cluster中,然後將該新拷貝的L1 Table的地址更新到qcow2頭部snapshot_offset指向的snapshot table中。
  • 刪除,其底層操作就是將指定snapshot對應的L1 Table刪除掉,並且更新snapshot table對應的entry。
  • 羅列,即將snapshot中存放的所有snapshot列出來。
  • 應用,即將指定的snapshot對應的L1 Table更新到qcow2文件頭中,若不先將當前qcow2文件header中的L1 Table進行保存,就會造成部分的數據丟失。

另外,還需要特別注意的是,在對snapshot進行管理的同時,一個特別重要的工作就是對qcow2文件中各個cluster的引用計數進行更新,也就是對refcount table和refcount blocks進行更新,snapshot創建時,需要對應L1 Table所能索引到的cluster的引用計數都加1,而刪除snapshot,則將相應的引用計數減1,當引用計數爲0時,則表示該cluster沒有被使用。

更新引用計數時,由於L1 Table和L2 Table中,並非所有的entry都是有效的(當虛擬機使用磁盤時,未使用到某個cluster對應的磁盤位置時,qcow2並不會對該cluster分配相應的物理磁盤空間,這也就爲什麼qcow2文件該開始創建的時候很小(1MB以下),而使用的時候慢慢增加),所以qemu需要遍歷整個L1 Table和所有的L2 Table,找出所有有效的entry,然後將相應entry對應的cluster引用計數更新到refcount table和refcount block中,相當於進行snapshot管理(除羅列操作)的時候,將需要遍歷L1 Table、所有有效的L2 Table、refcount table和所有有效的refcount block。極端情況下,對snapshot進行管理的時候,cluster的大小將會對snapshot操作的耗時產生比較大的影響,甚至相差兩個數量級。

例如:同樣針對40GB大小的虛擬磁盤

  • 若cluster的大小爲默認的64KB,則一個L2 Table將包含64KB/8B=8K個entry,每個entry可以記錄一個cluster,所以一個L2 Table可以覆蓋64KBx8K=512MB的磁盤空間,虛擬磁盤大小爲40GB,則需要40GB/512MB=80個L2 Table,即L1 Table需要80個entry,一個cluster即可覆蓋。所以需要64KB的L1 Table和80x64K=5120KB,即大概5MB的L2 Table。
  • 若cluster的大小爲4KB,則一個L2 Table將包含4KB/8B=512個entry,每個entry可以記錄一個cluster,所以一個L2 Table可以覆蓋4KBx512=2MB的磁盤空間,虛擬磁盤大小爲40G,則需要40GB/2MB=20K個entry,即20K個L2 Table,20K個L1 Table entry將需要佔用20K/512=40個cluster,即單純L1 Table就需要佔用40個cluster,即40x4KB=160KB的磁盤空間,而若所有L2 Table都有效,則需要20Kx4KB=80MB的磁盤空間。

所以64KB cluster size和4KB cluster size對應的L1 Table、L2 Table綜合大概是5MB和80MB,更要命的是,這5MB是以64KB大小爲單位散落在磁盤中,而80MB則是以4KB大小爲單位散落在磁盤中,在一個普通的SSD上,4KB大小的隨機讀寫速度就只有25MB/s和7MB/s,而64KB大小的隨機讀寫則是89MB/s和30MB/s。綜合起來,兩者snapshot操作的耗時差一兩個數量級就能解釋得通了。

所以,若對snapshot操作的性能有要求的話,需要特別注意qcow2文件的cluster大小,該大小可以在qcow2鏡像文件時通過-o選項設置,如

qemu-img create -f qcow2 -o cluster_size=16K test.img 40G

最後,qcow2程序中,爲了加快程序對鏡像文件的訪問,其實會將L1 Table和refcount table一直存放在內存中,因爲這兩個表比較小,而且會頻繁訪問,而針對L2 Table和refcount block,則會建立一個cache機制,該cache會將最近訪問到的L2 Table和refcount block放到內存中,從而加快對鏡像文件的訪問。L2 Table和refcount block的cache大小並不是在創建鏡像文件時,通過qemu-img的參數來設定,而是通過qemu的-drive參數來確定,即可以根據虛擬機的運行環境進行設置,如下所示(具體可以參考說明文檔docs/qcow2-cache.txt):

發佈了36 篇原創文章 · 獲贊 21 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章