《大話 Ceph 》系列文章通過通俗易懂的語言并結合基礎實驗,用最簡單的描述來講解 Ceph 中的重要概念。讓讀者對分布式存儲系統有一個清晰的了解。
這篇文章主要介紹了 RBD 在 Ceph 底層的存儲方式,解釋了 RBD 的實際占用容量和 RBD 大小的關系,用幾個檔案的例子示範了檔案在 RBD (更恰當的是 xfs)中的存儲位置,最後組裝了一個 RBD ,給出了一些 FAQ。
RBD : Ceph’s RADOS Block Devices , Ceph block devices are thin-provisioned, resizable and store data striped over multiple OSDs in a Ceph cluster.
上面是官方的闡述,簡單的說就是:
RBD 就是 Ceph 裡的塊裝置,一個 4T 的塊裝置的功能和一個 4T 的 SATA 類似,挂載的 RBD 就可以當磁盤用。
<code>resizable</code>:這個塊可大可小。
<code>data striped</code>:這個塊在 Ceph裡面是被切割成若幹小塊來儲存,不然 1PB 的塊怎麼存的下。
<code>thin-provisioned</code>:精簡置備,我認為的很容易被人誤解的 RBD 的一個屬性,1TB 的叢集是能建立無數 1PB 的塊的。說白了就是,塊的大小和在 Ceph 中實際占用的大小是沒有關系的,甚至,剛建立出來的塊是不占空間的,今後用多大空間,才會在 Ceph 中占用多大空間。打個比方就是,你有一個 32G 的 U盤,存了一個2G的電影,那麼 RBD 大小就類似于 32G,而 2G 就相當于在 Ceph 中占用的空間。
RBD,和下面說的<code>塊</code>,是一回事。
實驗環境很簡單,可以參考一分鐘部署單節點Ceph這篇文章,因為本文主要介紹RBD,對 Ceph 環境不講究,一個單 OSD 的叢集即可。
建立一個1G的塊 <code>foo</code>,因為是<code>Hammer</code>預設是<code>format 1</code>的塊,具體和<code>format 2</code>的差別會在下文講到:
Tips : <code>rbd</code> 的指令如果省略 <code>-p / --pool</code>參數,則會預設<code>-p rbd</code>,而這個rbd pool是預設生成的。
對剛剛的<code>rbd info</code>的幾行輸出簡單介紹下:
<code>size</code>: 就是這個塊的大小,即1024MB=1G,<code>1024MB/256 = 4M</code>,共分成了256個對象(object),每個對象4M,這個會在下面詳細介紹。
<code>order 22</code>, 22是個編号,4M是22, 8M是23,也就是2^22 bytes = 4MB, 2^23 bytes = 8MB。
<code>block_name_prefix</code>: 這個是塊的最重要的屬性了,這是每個塊在ceph中的唯一字首編号,有了這個字首,把伺服器上的OSD都拔下來帶回家,就能複活所有的VM了。
<code>format</code> : 格式有兩種,1和2,下文會講。
觀察建<code>foo</code>前後的<code>ceph -s</code>輸出,會發現多了兩個檔案,檢視下:
再檢視這兩個檔案裡面的内容:
這時候我們再建立一個 RBD 叫<code>bar</code>,再次對比檢視:
多出了個<code>bar.rbd</code>檔案,很容易聯想到這個檔案的内容是和<code>foo.rbd</code>内容類似的,唯一不同的是儲存了各自的<code>block_name_prefix</code>。然後,<code>rbd_directory</code>裡面多出了<code>bar</code>這個塊名字。可以得出以下的推論:
每個塊(RBD)剛建立(format 1)時都會生成一個<code>rbdName.rbd</code>這樣的檔案(ceph裡的對象),裡面儲存了這個塊的<code>prefix</code>。
同時,<code>rbd_directory</code>會增加剛剛的建立的<code>rbdName</code>,顧名思義這個檔案就是這個pool裡面的所有RBD的索引。
為了簡單試驗,删掉剛剛的<code>bar</code>隻留下<code>foo</code>:
RBD 使用
建好了塊,我們就開始使用這個塊了:
我喜歡記點東西,比如上面的<code>33M</code>就是剛格式化完的<code>xfs</code>系統的大小,算是一個特點吧。
先别急着用,叢集發生了點變化,觀察下:
比剛剛多了13個檔案,而且特别整齊還!觀察這些檔案的字尾,可以發現,字尾是以16進制進行編碼的,那麼從<code>0x00 -> 0xff</code>是多大呢,就是十進制的<code>256</code>,這個數字是不是很眼熟:
可是這裡隻有13個檔案,并沒有256個啊,這就是RBD的<code>精簡置備</code>的一個驗證,剛剛建立<code>foo</code>的時候,一個都沒有呢,而這裡多出的13個,是因為剛剛格式化成<code>xfs</code>時生成的。我們着重關注索引值為<code>0x00 & 0x01</code>這兩個碎片檔案(Ceph Object):
這裡的每一行輸出都是很值得思考的,首先我們導出剛剛提到的兩個對象,檢視第一個對象,開頭就是<code>XFSB</code>,可以驗證這是剛剛<code>mkfs.xfs</code>留下來的,這時候檢視檔案大小,發現并沒有4M那麼大,别擔心一會會變大的,值得關注的是<code>file</code>第0x00個對象,輸出居然是<code>XFS filesystem data</code>,進一步驗證了剛剛<code>mkfs.xfs</code>的足迹,這和整個塊的<code>file</code>資訊是一樣的,我猜測<code>xfs</code>把檔案系統核心資訊就儲存在塊裝置的最最前面的128KB。而後面的第0x01個對象裡面的<code>IN</code>輸出是64,我不負責任得猜想這個可能是傳說中的<code>inode</code>。抛去猜想這裡給到的結論是:
在使用塊裝置的容量後,會按需生成對應的對象,這些對象的共同點是:命名遵循 <code>block_name_prefix+index</code>, <code>index</code> range from [0x00, 0xff],而這個區間的大小正好是所有對象數的總和。
現在讓我們把<code>foo</code>塞滿:
這裡寫了将近1G的資料,重點在後面的<code>258 objects</code>,如果了解了前面說的内容,這258個對象自然是由<code>rbd_directoy</code>和<code>foo.rbd</code>還有256個<code>prefix+index</code>對象構成的,因為我們用完了這個塊的所有容量,是以自然就生成了所有的256的小4M對象。
寫入檔案
我們把環境恢複到<code>foo</code>被填滿的上一步,也就是剛剛<code>mkfs.xfs</code>和<code>mount /dev/rbd0 /foo</code>這裡。向這個塊裡面寫入檔案:
這裡我之是以隻導出了<code>0x01</code>這個對象,是因為我之前已經導出過所有的對象,經過對比後發現,在寫入檔案之後,隻有這個檔案的大小增大了<code>diff</code>之後,很快找到了寫入的内容。
這裡我們找到了檔案名和檔案的内容,這很<code>xfs</code>!因為是<code>xfs</code>将這些檔案和他們的内容組織成這樣的格式的,再關注下每一行前面的編号,這裡同樣是16進制編碼,如果之前我對<code>IN == inode</code>的猜測是對的話,所有的<code>inode</code>即<code>IN</code>都出現在索引範圍為<code>[0x00000000, 0x00004000)</code>的區間,這個<code>0x00004000</code>的機關是<code>byte</code>轉換過來就是<code>16KB</code>,這個小4M内的所有的<code>inode</code>都儲存在前16KB區間。而檔案出現的第一個索引為<code>0x00004000</code>,第二個在<code>0x00005000</code>,第三個在<code>0x00006000</code>,之間相差了<code>0x1000 bytes</code>也就是<code>4096 bytes == 4KB</code>,還記得<code>mkfs.xfs</code>時的輸出嗎?
也就是這裡的<code>bsize = 4096</code>使得檔案之間間隔了4KB,很抱歉我的<code>xfs</code>知識還是一片空白,是以這裡很多東西都靠猜,等我補完這一課會回來再做更正的。是以這裡我們的結論就是:
RBD其實是一個完完整整的塊裝置,如果把1G的塊想成一個1024層樓的高樓的話,<code>xfs</code>可以想象成住在這個大樓裡的樓管,它隻能在大樓裡面,也就隻能看到這1024層的房子,樓管自然可以安排所有的住戶(檔案or檔案名),住在哪一層哪一間,睡在地闆還是天花闆(檔案偏移量),隔壁的樓管叫做<code>ext4</code>,雖然住在一模一樣的大樓裡(<code>foo</code> or <code>bar</code>),但是它們有着自己的安排政策,這就是檔案系統如果組織檔案的一個比喻了,我們就不做深究,明白到這裡就好了。
然并卵,拆遷大隊長跑來說,我不管你們(<code>xfs</code>or<code>ext4</code>)是怎麼安排的,蓋這麼高的樓是想上天了?,然後大隊長把這1024層房子,每4層(4MB)砍了一刀,一共砍成了256個四層,然後一起打包帶走了,運到了一個叫做<code>Ceph</code>的小區裡面,放眼望去,這個小區裡面的房子最高也就四層(填滿的),有的才打了地基(還沒寫内容)。
這一節最主要的目的就是說明,在Ceph眼裡,它并不關心這個RBD是做什麼用處的,統統一刀斬成4M大小的對象,而使用這個RBD的使用者(比如<code>xfs</code>),它隻能從RBD裡面操作,它可能将一個大檔案從三樓寫到了五樓,但是Ceph不管,直接從四樓砍一刀,檔案分了就分了,反正每個小四層都有自己的編号(index),不會真的把檔案給丢了。
最後再來個小操作(4214784=0x405000):
需要執行<code>sync && echo 1 > /proc/sys/vm/drop_caches</code>後才能看到修改的效果。
AMAZING!,至少我是這麼覺得的,我們通過檢視<code>index</code>為<code>0x01</code>的小4M檔案,得知了<code>file2.txt</code>這個檔案内容在這個小4M内儲存的位置為<code>0x5000</code>,因為<code>0x01</code>前面還有一個小4M檔案即<code>0x00</code>,那麼這個<code>file2.txt</code>在整個RBD内的偏移量為<code>4MB+0x5000B=0x400,000B+0x5000B=0x405000B=4214784</code>,也就是說儲存在<code>/dev/rbd0</code>的偏移量為<code>0x405000</code>的位置,這時候用<code>dd</code>工具,直接向這個位置寫入一個<code>Ceph</code>,再檢視<code>file2.txt</code>的内容,果然,被修改了!
RBD 組裝
受到這篇文章啟發,我們開始組裝一個RBD,所謂組裝,就是把剛剛的13個小4M碎片從叢集中取出來,然後利用這13個檔案,重建出一個1G的RBD塊,這裡的實驗環境沒有那篇文章裡面那麼苛刻,因為叢集還是能通路的,不像文章裡提到的叢集已死,不能執行ceph指令,需要從OSD裡面把碎片檔案一個個撈出來。
一點思考: 使用<code>find .</code>這種方法太慢了,有一個事實是,可以從任何一個<code>dead OSD</code> 導出這個叢集的crushmap,我有一個為證明的猜想,能否将這個CRUSHMAP注入到一個活的叢集,然後這兩個叢集的<code>ceph osd map <pool> <object></code>的輸出如果一樣的話:
<code>live</code>叢集: <code>ceph osd map <pool> foo.rbd</code>,得到塊名所存儲的位置,前往<code>dead</code>叢集找到之。
<code>dead</code>叢集: 讀取<code>foo.rbd</code>檔案讀取<code>prefix</code>。希望你還能記得這個塊的大小。
<code>live</code>叢集: 做個循環,讀取<code>ceph osd map <pool> prefix+index[0x00,Max_index]</code>的輸出,可以擷取在<code>dead</code>叢集的某個OSD的某個PG下面,這樣就可以直接定位而不需要從<code>find .</code>結果來過濾了。
隻是猜想,未證明,這兩天證明過再回來訂正。
通過這個實驗,可以直接通過<code>osdmap</code>擷取所有的<code>object</code>的存儲位置,不需要平移crush了,十分友善快捷。
我們很快就可以把那13個檔案拿出來了,現在開始組裝:
組裝的基本思想就是,先搭建一個1024層的大樓,然後,把剛剛的13個四層根據它的<code>index</code>,安插到對應的樓層,缺少的樓層就空在那就好了,腳本來自剛剛的文章,我對其進行了一小部分的修改,使之适合我們這個實驗,腳本将輸出一個名為<code>foo</code>的塊:
從上面的腳本就可以看出,這是一個填樓工程,将其填完之後,我們來看得到的<code>foo</code>檔案:
這時候,我們挂載會出現一個小問題,uuid重複:
原因很簡單,還記得我們剛剛操作時的<code>mount /dev/rbd0 /foo</code>嗎? <code>foo</code>塊是<code>/dev/rbd0</code>的克隆,是以<code>foo</code>的<code>UUID</code>是和<code>/dev/rbd0</code>的是一樣的,這時候我們<code>umount /foo</code>即可:
神奇吧,我們用碎片檔案組裝了一個完完整整的RBD塊,能和Ceph裡map出來的RBD一樣使用,并且資料也是一樣的,相信如果了解了前幾節的内容,對這個實驗的結果就不會很意外了。
Format 1 VS Format 2
衆所周知,RBD有兩種格式:
<code>Format 1</code>: <code>Hammer</code>以及<code>Hammer</code>之前預設都是這種格式,并且<code>rbd_default_features = 3</code>.
<code>Format 2</code>: <code>Jewel</code>預設是這種格式,并且<code>rbd_default_features = 61</code>.
關于<code>features</code>的問題将在下節解釋,這裡我們隻讨論這兩種格式的RBD在ceph底層的存儲有什麼差別,首先安裝一個<code>Jewel</code>版本的環境(或者改配置項),方法很簡單:
建立一個<code>foo</code>塊,并觀察叢集多出了哪些檔案:
多出了四個檔案,在關閉<code>object-map</code>屬性後,少了一個<code>rbd_object_map.101474b0dc51</code>檔案,我們檢視剩下的三個檔案内容:
可以發現,<code>rbd_directory</code>不再儲存所有RBD的名稱,相比于<code>format1的 foo.rbd</code>,<code>format2</code>采用<code>rbd_id.rbdName</code>的形式儲存了這個塊的<code>prefix</code>,而另一個檔案<code>rbd_header,xxxxxxxxprefix</code>顯示的儲存了這個<code>prefix</code>,我們再向這個塊寫入點檔案:
可以發現,生成的13個小4M檔案的字尾和<code>format 1</code>的是一模一樣的,隻是命名規則變成了<code>rbd_data.[prefix].+[index]</code>,之是以字尾是一樣的是因為<code>xfs</code>對于1G的塊總是會這樣建構檔案系統,是以對于<code>F1 & F2</code>,這些小4M除了命名規範不一樣外,實際儲存的内容都是一樣的,下面對這兩種格式進行簡要的對比總結:
格式
rbd_diretory
ID
prefix
Data
Format 1
儲存了所有RBD的名稱
foo.rbd
在ID中
形如 rb.0.1014.74b0dc51.000000000001
Format 2
空
rbd_id.foo
在ID中 并rbd_header.prefix顯示輸出
形如 rbd_data.101474b0dc51.0000000000000001
FAQ
1、 rbd feature disable
最常見的RBD使用問題,一般出現于<code>Format 2</code>的<code>rbd map</code>操作,需要關閉如下屬性即可,注意順序:
2、100TB的RBD要删幾個月?
是的,之前看到調試的log發現,<code>rbd rm foo</code>的時候,是從index的0開始一個個得往後面删,是以100TB 的塊有兩千六百萬個4M碎片組成,雖然實際上并沒有用到這麼多,但是一個個删的話,還是相當慢的,如果是<code>Format 1</code>我們可以<code>rados ls|grep block_name_prefix |xargs rados rm</code>這種思路删除,再删掉<code>foo.rbd</code>檔案,再執行<code>rbd rm foo</code>就可以很快删掉了。這裡有一篇很詳盡的文章介紹了删除方法。
3、為什麼總是4M的塊
<code>rbd_default_order = 22</code>這個配置項決定了切塊的大小,預設值為22,參考了這篇文章我得到了<code>order</code>和<code>size</code>的幾個關系:
order
size
23
8M
22
4M
21
2M
20
1M
大概關系就是這樣,暫時沒找更詳細的介紹,<code>man rbd</code>能看到更詳細的資訊。
size = 2 <code>**</code> order bytes 也就是2的order次方。
4、 Features 編号
配置項為<code>rbd_default_features = [3 or 61]</code>,這個值是由幾個屬性加起來的:
only applies to format 2 images
+1 for layering,
+2 for stripingv2,
+4 for exclusive lock,
+8 for object map
+16 for fast-diff,
+32 for deep-flatten,
+64 for journaling
是以<code>61=1+4+8+16+32</code>就是<code>layering | exclusive lock | object map |fast-diff |deep-flatten</code>這些屬性的大合集,需要哪個不需要哪個,做個簡單的加法配置好<code>rbd_default_features</code>就可以了。
總結
這篇文章還是有點殘缺的:
RBD碎片在底層是以<code>rbd\udata.101474b0dc51.0000000000000000__head_DDFD75CF__0</code>這種命名方式儲存的。
<code>xfs</code>的<code>inode</code>猜測為證明。
CRUSHMAP的平移注入是否會産生相同的<code>ceph osd map</code>結果。答案:會産生相同的結果,但是不需要平移,隻需要一個<code>osdmap</code>就好了。
最簡單的話來總結下RBD:Client從RBD内部看,RBD是一個整體,Ceph從RBD外部看,是若幹的碎片。
Enjoy It!