共計 6319 個字符,預計需要花費 16 分鐘才能閱讀完成。
這篇文章主要介紹 Ceph 中容量計算與管理的示例分析,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
Ceph 中的容量計算與管理
在部署完 Ceph 集群之后,一般地我們可以通過 Ceph df 這個命令來查看集群的容量狀態,但是 Ceph 是如何計算和管理的呢?相信大家都比較好奇。因為用過 ceph df 這個命令的人都會有這個疑問,它的輸出到底是怎么計算的呢?為什么所有 pool 的可用空間有時候等于 GLOBAL 中的可用空間,有時候不等呢?帶著這些疑問我們可以通過分析 ceph df 的實現,來看看 Ceph 是如何計算容量和管理容量的。
一般情況下 ceph df 的輸出如下所示:
ceph-df
[root@study-1 ~]# ceph df
GLOBAL:
SIZE AVAIL RAW USED %RAW USED
196G 99350M 91706M 45.55
POOLS:
NAME ID USED %USED MAX AVAIL OBJECTS
rbd 1 20480k 0.02 49675M 11
x 2 522 0 49675M 11
GLOBAL 維度中有 SIZE,AVAIL,RAW USED,%RAW USED。從上面的輸出可以看到,ceph 對容量的計算其實是分為兩個維度的。一個是 GLOBAL 維度,一個是 POOLS 的維度。
POOLS 維度中有 USED,%USED,MAX AVAIL,OBJECTS。
我們這里先把注意力放在 RAW USED,和 AVAIL 上。這個兩個分析清楚之后,其它的也就迎刃而解了。
這里我們粗略算一下 GLOBAL 中的 RAW USED 為 91706M,明顯大于下面 pool 中 USED 20480k*3 + 522bytes* 3 啊。而且各個 pool 的 MAX AVAIL 相加并不等于 GLOBAL 中的 AVAIL。我們需要深入代碼分析一下為什么。
分析
Ceph 命令基本上都是首先到 Montior 這里,如何 Monitor 能處理請求,就直接處理,不能就轉發。
我們看看 Monitor 是如何處理 ceph df 這個命令的。Monitor 處理命令主要是在 Monitor::hanlde_command 函數里。
handle_command
else if (prefix == df) {
bool verbose = (detail == detail
if (f)
f- open_object_section( stats
pgmon()- dump_fs_stats(ds, f.get(), verbose);
if (!f)
ds \n
pgmon()- dump_pool_stats(ds, f.get(), verbose);
if (f) { f- close_section();
f- flush(ds);
ds \n
}
}
dump_fs_stats 對應 GLOBAL 這個維度。dump_pool_stats 對應 POOLS 這個維度。從上面的代碼可以知道,主要是兩個函數完成了 df 命令的輸出。一個是 pgmon()- dump_fs_stats,另一個是 pgmon()- dump_pool_stats。
GLOBAL 維度
從 PGMonitor::dump_fs_stats 開始:
dump_fs_stats
void PGMonitor::dump_fs_stats(stringstream ss, Formatter *f, bool verbose) const
if (f) {
f- open_object_section( stats
f- dump_int(total_bytes , pg_map.osd_sum.kb * 1024ull);
f- dump_int(total_used_bytes , pg_map.osd_sum.kb_used * 1024ull);
f- dump_int(total_avail_bytes , pg_map.osd_sum.kb_avail * 1024ull);
if (verbose) { f- dump_int( total_objects , pg_map.pg_sum.stats.sum.num_objects);
}
f- close_section();
}
stat_pg_update
void OSDService::update_osd_stat(vector int hb_peers)
Mutex::Locker lock(stat_lock);
osd_stat.hb_in.swap(hb_peers);
osd_stat.hb_out.clear();
osd- op_tracker.get_age_ms_histogram(osd_stat.op_queue_age_hist);
// fill in osd stats too
struct statfs stbuf;
int r = osd- store- statfs(stbuf);
if (r 0) { derr statfs() failed: cpp_strerror(r) dendl;
return;
}
uint64_t bytes = stbuf.f_blocks * stbuf.f_bsize;
uint64_t used = (stbuf.f_blocks - stbuf.f_bfree) * stbuf.f_bsize;
uint64_t avail = stbuf.f_bavail * stbuf.f_bsize;
osd_stat.kb = bytes 10;
osd_stat.kb_used = used 10;
osd_stat.kb_avail = avail 10;
osd- logger- set(l_osd_stat_bytes, bytes);
osd- logger- set(l_osd_stat_bytes_used, used);
osd- logger- set(l_osd_stat_bytes_avail, avail);
check_nearfull_warning(osd_stat);
dout(20) update_osd_stat osd_stat dendl;
可以看到相關字段數值的輸出主要依賴 pg_map.osd_sum 的值,而 osd_sum 是各個 osd_stat 的總和。所以我們需要知道單個 osd 的 osd_stat_t 是如何計算的。
FIleStore::statfs 從上面我們可以看到 update_osd_stat 主要是通過 osd- store- statfs(stbuf),來更新 osd_stat 的。因為這里使用的是 Filestore,所以需要進入 FileStore 看其是如何 statfs 的。
int FileStore::statfs(struct statfs *buf)
if (::statfs(basedir.c_str(), buf) 0) {
int r = -errno;
assert(!m_filestore_fail_eio || r != -EIO);
assert(r != -ENOENT);
return r;
}
return 0;
}
可以看到上面 FileStore 主要是通過::statfs() 這個系統調用來獲取信息的。這里的 basedir.c_str() 就是 data 目錄。所以 osd_sum 計算的就是將所有 osd 數據目錄的磁盤使用量加起來。回到上面的輸出,因為我使用的是一個磁盤上的目錄,所以在 statfs 的時候,會把該磁盤上的其它目錄也算到 Raw Used 中?;氐缴厦娴妮敵?,因為使用兩個 OSD,且每個 OSD 都在同一個磁盤下,所以 GLOBAL 是這么算的
同上,就知道 Ceph 如何算 Raw Used,AVAIL 的。
POOLS 維度
從 PGMonitor::dump_pool_stats() 來看,該函數以 pool 為粒度進行循環,通過 pg_map.pg_pool_sum 來獲取 pool 的信息。其中 USED,%USED,OBJECTS 是根據 pg_pool_sum 的信息算出來的。而 MAX AVAIL 是單獨算出來的。
這里有一張圖,可以幫助同學們梳理整個的流程。中間僅取了一些關鍵節點。有一些省略,如想知道全貌,可以在 PGMonitor::dump_pool_stats 查閱。
通過分析代碼我們知道,pool 的使用空間(USED)是通過 osd 來更新的,因為有 update(write,truncate,delete 等)操作的的時候,會更新 ctx- delta_stats,具體請見 ReplicatedPG::do_osd_ops。舉例的話,可以從處理 WRITE 的 op 為入手點,當處理 CEPH_OSD_OP_WRITE 類型的 op 的時候,會調用 write_update_size_and_usage()。里面會更新 ctx- delta_stats。當 IO 處理完,也就是 applied 和 commited 之后,會 publish_stats_to_osd()。
這里會將變化的 pg 的 stat_queue_item 入隊到 pg_stat_queue 中。然后設置 osd_stat_updated 為 True。入隊之后,由 tick_timer 在 C_Tick_WithoutOSDLock 這個 ctx 中通過 send_pg_stats() 將 PG 的狀態發送給 Monitor。這樣 Monitor 就可以知道 pg 的的變化了。
可用空間,即 MAX AVAIL 的值,計算稍微有點復雜。Ceph 是先計算 Available 的值,然后根據副本策略再計算 MAX AVAIL 的值。Available 的值是在 get_rule_avail() 中計算的。在該函數中通過 get_rule_weight_osd_map() 算出來一個有 weight 的 osd 列表。
注意這里的 weight 一般是小于 1 的,因為它除以了 sum。而 sum 就是 pool 中所有 osd weight 的總和。在拿到 weight 列表后,就會根據 pg_map.osd_stat 中 kb_avail 的值進行除以 weight,選出其中最小的,作為 Available 的值。
這么描述有些抽象了,具體舉一個例子。比如這里我們的 pool 中有三個 osd,假設 kb_avail 都是 400G
即,
{osd_0: 0.9, osd_1, 0.8, osd_2: 0.7}。計算出來的 weight 值是 {osd_0: 0.9/2.4,osd_1: 0.8/2.4,osd_2: 0.7/2.4}
這樣后面用 osd 的 available 空間除以這里的 weight 值,這里的 Available 的值就是 400G*0.7/2.4。這里附上一個公式,可能更直觀一些。
然后根據你的 POOL 的副本策略不同,POOL 的 AVAL 計算方式也不同。如果是 REP 模式,就是直接除以副本數。如果是 EC 模式,則 POOL 的 AVAL 是 Available * k / (m + k)。
所以一般情況下,各個 POOL 的 MAX AVAIL 之和與 GLOBAL 的 AVAIL 是不相等的,但是可以很接近(相差在 G 級別可以忽略為接近)。
總結
分析到這里,我們知道 CEPH 中容量的計算是分維度的,如果是 GLOBAL 維度的話,因為使用的是 osd 的所在磁盤的 statfs 來計算所以還是比較準確的。而另一個維度 POOLS
由于需要考慮到 POOL 的副本策略,CRUSH RULE,OSD WEIGHT,計算起來還是比較復雜的。容量的管理主要是在 OSD 端,且 OSD 會把信息傳遞給 MON,讓 MON 來維護。
計算 osd weight 值比較復雜,這里附上算 weight 的函數,添加了一些注釋,有助于感興趣的同學一起分析。
int CrushWrapper::get_rule_weight_osd_map(unsigned ruleno, map int,float *pmap)
if (ruleno = crush- max_rules)
return -ENOENT;
if (crush- rules[ruleno] == NULL)
return -ENOENT;
crush_rule *rule = crush- rules[ruleno];
// build a weight map for each TAKE in the rule, and then merge them
for (unsigned i=0; i rule- ++i) {
map int,float m;
float sum = 0;
if (rule- steps[i].op == CRUSH_RULE_TAKE) {// 如果是 take 的話, 則進入
int n = rule- steps[i].arg1;
if (n = 0) { // n 如果大于等于 0 的話是 osd,否則是 buckets
m[n] = 1.0; // 如果是 osd 的話,因為這里是直接 take osd,所有有沒有權重已經不重要了
sum = 1.0;
} else { // 不是 osd,是 buckets 的話
list int q;
q.push_back(n); // buckets 的 id 入隊
//breadth first iterate the OSD tree
while (!q.empty()) { int bno = q.front(); // 取出 buckets 的 id
q.pop_front(); // 出隊
crush_bucket *b = crush- buckets[-1-bno]; // 根據序號拿到 buckets
assert(b); // 這個 buckets 必須是存在的
for (unsigned j=0; j b- size; ++j) { // 從 buckets 的 items 數組中拿相應的 bucket
int item_id = b- items[j];
if (item_id = 0) { //it s an OSD
float w = crush_get_bucket_item_weight(b, j); // 拿出該 osd 的 weight
m[item_id] = w; // m 入隊
sum += w; // weight 加和
} else { //not an OSD, expand the child later
q.push_back(item_id); // 如果不是 osd,則添加其 item_id, 所以這里是一個樹的深度遍歷
}
}
}
}
}
for (map int,float ::iterator p = m.begin(); p != m.end(); ++p) { map int,float ::iterator q = pmap- find(p- first);
// 因為我們這里傳入的 pmap 是沒有數據的
// 所以第一次必中, if (q == pmap- end()) { (*pmap)[p- first] = p- second / sum;
} else {
// 這里還需要考慮 osd 在不同的 buckets 里的情況
q- second += p- second / sum;
}
}
}
return 0;
}
以上是“Ceph 中容量計算與管理的示例分析”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注丸趣 TV 行業資訊頻道!