關(guān)于Ceph中Bufferlist的設(shè)計與使用
如果非要在整個Ceph中,找出一個類最重要,我覺得非Bufferlist莫屬了,原因很簡單,因?yàn)锽ufferlist負(fù)責(zé)管理Ceph中所有的內(nèi)存。整個Ceph中所有涉及到內(nèi)存的操作,無論是msg分配內(nèi)存接收消息,還是OSD構(gòu)造各類數(shù)據(jù)結(jié)構(gòu)的持久化表示(encode/decode),再到實(shí)際磁盤操作,都將bufferlist作為基礎(chǔ)。
Ceph中bufferlist的設(shè)計還是有些復(fù)雜的,其中包含三個主要的內(nèi)buffer::raw(bufferraw)、 buffer::ptr(bufferptr)和buffer::list(bufferlist)。這三個類都定義在common/buffer.h 中,都是buffer類的內(nèi)部類,而buffer類本身沒有任何內(nèi)容,只起到了一個命名空間的作用。
這三個類的職責(zé)各有不同:
buffer::raw:對應(yīng)一段真實(shí)的物理內(nèi)存,負(fù)責(zé)維護(hù)這段物理內(nèi)存的引用計數(shù)nref和釋放操作。
buffer::ptr:對應(yīng)Ceph中的一段被使用的內(nèi)存,也就是某個bufferraw的一部分或者全部。
buffer::list:表示一個ptr的列表(std::list),相當(dāng)于將N個ptr構(gòu)成一個更大的虛擬的連續(xù)內(nèi)存。
buffer這三個類的相互關(guān)系可以用下面這個圖來表示:
圖中藍(lán)色的表示bufferlist,橙色表示bufferptr,綠色表示bufferraw。
在這個圖中,實(shí)際占用的系統(tǒng)內(nèi)存一共就三段,分別是raw0,raw1和raw2代表的三段內(nèi)存。其中:
raw0被ptr0,ptr1,ptr2使用
raw1被ptr3,ptr4,ptr6使用
raw2被ptr5,ptr7使用
而list0是由ptr0-5組成的,list1是由ptr6和ptr7組成的。
從這張圖上我們就可以看出bufferlist的設(shè)計思路了: 對于bufferlist來說,僅關(guān)心一個個ptr。bufferlist將ptr連在一起,當(dāng)做是一段連續(xù)的內(nèi)存使用。因此,可以通過 bufferlist::iterator一個字節(jié)一個字節(jié)的迭代整個bufferlist中的所有內(nèi)容,而不需要關(guān)心到底有幾個ptr,更不用關(guān)心這些 ptr到底和系統(tǒng)內(nèi)存是怎么對應(yīng)的;也可以通過bufferlist::write_file方法直接將bufferlist中的內(nèi)容出到一個文件中;或者通過bufferlist::write_fd方法將bufferlist中的內(nèi)容寫入到某個fd中。
與bufferlist相對的是負(fù)責(zé)管理系統(tǒng)內(nèi)存的bufferraw。bufferraw只關(guān)心一件事:維護(hù)其所管理的系統(tǒng)內(nèi)存的引用計數(shù),并且在引用計數(shù)減為0時——即沒有ptr再使用這塊內(nèi)存時,釋放這塊內(nèi)存。
連接bufferlist和bufferraw的是bufferptr。bufferptr關(guān)心的是如何使用內(nèi)存。每一個bufferptr一定有一個bufferraw為其提供系統(tǒng)內(nèi)存,然后ptr決定使用這塊內(nèi)存的哪一部分。bufferlist只用通過ptr才能對應(yīng)到系統(tǒng)內(nèi)存中,而 bufferptr而可以獨(dú)立存在,只是大部分ptr還是為bufferlist服務(wù)的,獨(dú)立的ptr使用的場景并不是很多。
通過引入ptr這樣一個中間層次,bufferlist使用內(nèi)存的方式可以非常靈活,這里可以舉兩個場景:
1. 快速encode/decode
在Ceph中經(jīng)常需要將一個bufferlist編碼(encode)到另一個bufferlist中,例如在msg發(fā)送消息的時候,通常msg拿到的 osd等邏輯層傳遞給它的bufferlist,然后msg還需要給這個bufferlist加上消息頭和消息尾,而消息頭和消息尾也是用 bufferlist表示的。這時候,msg通常會構(gòu)造一個空的bufferlist,然后將消息頭、消息尾、內(nèi)容都encode到這個空的 bufferlist。而bufferlist之間的encode實(shí)際只需要做ptr的copy,而不涉及到系統(tǒng)內(nèi)存的申請和Copy,效率較高。
2. 一次分配,多次使用
我們都知道,調(diào)用malloc之類的函數(shù)申請內(nèi)存是非常重量級的操作。利用ptr這個中間層可以緩解這個問題,即我們可以一次性申請一塊較大的內(nèi)存,也就是一個較大的bufferraw,然后每次需要內(nèi)存的時候,構(gòu)造一個bufferptr,指向這個bufferraw的不同部分。這樣就不再需要向系統(tǒng)申請內(nèi)存了。***將這些ptr都加入到一個bufferlist中,就可以形成一個虛擬的連續(xù)內(nèi)存。
關(guān)于作者:袁冬博士,UnitedStack產(chǎn)品副總裁,負(fù)責(zé)UnitedStack產(chǎn)品、售前和對外合作工作;云計算專家,在云計算、虛擬化、分布式系統(tǒng)和企業(yè)級應(yīng)用等方面有豐富的經(jīng)驗(yàn);對分布式存儲、非結(jié)構(gòu)數(shù)據(jù)存儲和存儲虛擬化有深刻地理解,在云存儲和企業(yè)級存儲領(lǐng)域有豐富的研發(fā)與實(shí)踐經(jīng)驗(yàn);Ceph等開源存儲項(xiàng)目的核心代碼貢獻(xiàn)者。