Ceph的FileStore代碼閱讀問題整理

1.bufferlist中的_memcopy_count作用是什麼?
bufferlist提供了一個rebuild函數,用來將整個buffterptr鏈表的所有bufferraw都copy到一個新建的bufferptr中,然後清空鏈表並將新建的這個bufferptr插入到鏈表中。_memcopy_count成員記錄了在進行拷貝過程中每個bufferraw的長度之和。這個值用來在進行內存對齊時需要重新計算,然後比較兩者的值,如果不相同就判斷內存對齊成功,否則失敗。

2.bufferlist是不是類似io buffer?

bufferlist是ceph中進行所有內存操作的管理類:
  • bufferraw(buffer::raw):對應一段真實內存,派生了十多種不同分配內存的子類,如raw_malloc,raw_static,raw_mmap_pages,raw_posix_aligned,raw_char,raw_pipe等
  • bufferptr(buffer::ptr):對應ceph實際使用的一段內存,可能多個bufferptr對應於同一個底層bufferraw,其成員off和len標識具體使用的一段內存
  • bufferlist(buffer::list):多個正在使用的bufferptr構成的一個鏈表,對應成員std::list<bufferptr> _buffers,其成員off表示整個鏈表構成的內存相對於起始位置的偏移,p_off表示當前使用的bufferptr的偏移

對於bufferlist來說,通過內部實現的bufferlist::iterator來訪問多個bufferptr,能夠逐個字節的訪問整個內存的內容,不需要關心到底有多少個bufferptr存在,也不用關心bufferptr和底層bufferraw到底是怎麼關聯的,這種設計就是爲了上層使用上的方便,與io buffer的緩存作用並沒有太大關係。

上層用戶可以直接使用write_file,write_fd等方法,將bufferlist的內容寫入文件,可以完全忽略內存的來源、釋放等問題,這些問題統一由bufferraw的派生子類和引用計數進行解決。ceph中有兩個使用bufferlist最廣泛的場景:
  1. 快速encode/decode:messege層收到osd傳遞的消息(爲一個bufferlist),需要加上消息頭和消息尾等操作,這些消息頭和尾本身都encode爲bufferlist,就很容易與消息本身合併(prepend和append操作),而且這些encode、preprend、append操作都是指針操作,不涉及內存拷貝,提示效率。
  2. 減少內存分配次數和碎片:利用bufferptr這個中間層進行內存的多次使用,多個bufferptr可以引用同一段bufferraw的不同區域,這個bufferraw可以預先一次性申請較大一段連續內存,從而避免了多次申請內存以及內存碎片的產生。

3.io 優先級?best effort和 real time區別?

I/O優先級是linux系統爲read和同步write操作提供的一種系統調用,對於異步write不支持,同時glibc沒有提供封裝,需要使用<sys/linux.h>頭文件,使用ioprio_set函數進行設置。共有三個參數
  • which:表明第二個參數的解釋方式,IOPRIO_WHO_PROCESS代表who參數爲一個進行或線程ID;IOPRIO_WHO_PGRP表明who爲同一進程組的所有進程;IOPRIO_WHO_USER爲同一用戶的所有進程
  • who:優先級的指定方式,分爲IOPRIO_PRIO_VALUE/CLASS/DATA三種方式指定優先級
  • ioprio:爲優先級的掩碼值,用來生成最終的優先級,優先級數值越低優先級越高
I/O優先級是通過I/O調度器實現,對於每個設備有一個特殊文件可以查看:/sys/block/<device>/queue/scheduler
目前絕大部分實現都是CFQ I/O調度器(Completely Fair Queuing I/O Scheduler),支持三種類型的I/O調度類別:
  • IOPRIO_CLASS_RT:real time,被CFQ給予最高優先級進行調度,每次都會給予最優先的機會去放問磁盤,這種調度策略可能會將整個磁盤的IO打滿,對應的優先級值可以從最高的0到最低的7進行設置
  • IOPRIO_CLASS_BE:best effort,每個進程默認的優先級,優先級值從最高的0到最低的7進行設置
  • IOPRIO_CLASS_IDLE:idle調度類別最低,只有沒有其他進程使用磁盤時纔會調度這類優先級類別的I/O操作

4.pre_pad作用是什麼?

FileJournal的每一個entry沒有offset字段,利用pre_pad找到data部分的起始位置,然後利用len確定數據部分的長度,接着的post_pad用來找到footer部分,具體結構如下圖:

代碼中的實現部分如下,可以看出footer和header部分都是寫入了一個entry_header:
int FileJournal::prepare_entry(vector<ObjectStore::Transaction>& tls, bufferlist* tbl) {
...
entry_header_t h;
unsigned head_size = sizeof(entry_header_t);
off64_t base_size = 2*head_size + bl.length();
memset(&h, 0, sizeof(h));
if (data_align >= 0)
h.pre_pad = ((unsigned int)data_align - (unsigned int)head_size) & ~CEPH_PAGE_MASK;
off64_t size = ROUND_UP_TO(base_size + h.pre_pad, header.alignment);
unsigned post_pad = size - base_size - h.pre_pad;
h.len = bl.length();
h.post_pad = post_pad;
h.crc32c = bl.crc32c(0);
...
bufferlist ebl;
// header
ebl.append((const char*)&h, sizeof(h));
if (h.pre_pad) {
ebl.push_back(buffer::create_static(h.pre_pad, zero_buf));
}
// payload
ebl.claim_append(bl, buffer::list::CLAIM_ALLOW_NONSHAREABLE); // potential zero-copy
if (h.post_pad) {
ebl.push_back(buffer::create_static(h.post_pad, zero_buf));
}
// footer
ebl.append((const char*)&h, sizeof(h));
...
}

5.magic number作用是什麼?

從第4個問題的圖示中可以看出magic number是進行數據校驗的,magic1是記錄的當前entry寫入的相對於整個journal文件開頭的絕對位置,也就是最終調用文件系統的lseek操作的位置;magic2就是fsid、seq、len三者的異或值,用於進一步校驗。

6.aio是否落盤還是cache就返回?

FileJournal打開fd進行寫入的時候設置的flag爲O_RDWR,如果使用了directio(通過配置項設置journal dio = true),則會爲flag增加O_DIRECT | O_DSYNC,因此配置dio爲true時就會直接寫入磁盤。

7.讀數據多個線程?

讀取數據的操作都會解析爲lfn_open打開文件,調用safe_pread進行讀取,最後lfn_close關閉文件,而safe_pread是封裝了pread的一個公共函數:
ssize_t safe_pread(int fd, void *buf, size_t count, off_t offset)
{
size_t cnt = 0;
char *b = (char*)buf;
while (cnt < count) {
ssize_t r = pread(fd, b + cnt, count - cnt, offset + cnt);
if (r <= 0) {
if (r == 0) {
// EOF
return cnt;
}
if (errno == EINTR)
continue;
return -errno;
}
cnt += r;
}
return cnt;
}
因此使用方併發調用讀取操作是支持的。

8.配journal限制的目的是什麼?

Journal Throttle一方面爲了控制寫入速度過快導致所有journal可寫空間都佔滿,造成大量修改操作並沒有commit到底層存儲中,另一方面是可以控制底層存儲的隊列中的長度,可以爲實時性要求高的業務場景提供可配。

9.split和merge後怎麼維持hash關係?

每個object都有一個對其hash值進行nibble  reverse之後的字段:nibblewise_key_cache,每次進行split的時候會基於這個對象重新創建一個新的匹配的路徑,見下面的get_path_component函數,這個函數最終會獲取這個對象的nibblewise_key_cache字段的值,然後每次都會按照半字節翻轉一次,得到的新路徑會重新創建或者直接移動到相應目錄(已存在的話),然後刪除現有object,最終設置新目錄的xattr,裏面包含了這個目錄下有多少個object和子目錄等信息。當進行merge的時候與這個過程類似。
int HashIndex::complete_split(const vector<string> &path, subdir_info_s info) {
int level = info.hash_level;
map<string, ghobject_t> objects;
vector<string> dst = path;
int r;
dst.push_back("");
r = list_objects(path, 0, 0, &objects);
...
map<string, map<string, ghobject_t> > mapped;
...
for (map<string, ghobject_t>::iterator i = objects.begin();
i != objects.end();
++i) {
vector<string> new_path;
get_path_components(i->second, &new_path);
mapped[new_path[level]][i->first] = i->second;
}
...
r = remove_objects(path, moved, &objects);
...
set_info(path, info);
...
}
void HashIndex::get_path_components(const ghobject_t &oid, vector<string> *path) {
char buf[MAX_HASH_LEVEL + 1];
snprintf(buf, sizeof(buf), "%.*X", MAX_HASH_LEVEL, (uint32_t)oid.hobj.get_nibblewise_key());
// Path components are the hex characters of oid.hobj.hash, least
// significant first
for (int i = 0; i < MAX_HASH_LEVEL; ++i) {
path->push_back(string(&buf[i], 1));
}
}

10.多級目錄的目的是什麼?

多級目錄是爲了防止同一個目錄下保存的文件數目過多導致查詢時效率過低,通過多級目錄的切分,能夠增加讀取目錄項和文件的效率。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章