簡介
OSD作為Ceph的核心組件,承擔着數據的存儲,復制,恢復等功能。本文基於Luminous版本通淺析OSD的初始化流程,了解OSD的主要功能、主要數據結構和其他組件的配合情況等。OSD守護進程的啟動流程在ceph_osd.cc中的主函數中,具體和OSD相關的初始化主要集中在下面幾個函數:
- int OSD::pre_init()
- int OSD::init()
- void OSD::final_init()
后文對這三個函數的主要流程進行分析。
pre_init
pre_init在OSD實例被創建后調用,主要的目的有兩個:
- 通過
test_mount_in_use檢查OSD路徑是否已經被使用。如果已經被使用,返回-EBUSY。 - 如果組件需要使用動態參數變更的機制,需要繼承
md_config_t,並通過add_observer加入被觀察的key,當這個key的value發生改變時,可以及時觀測到。
cct->_conf->add_observer(this);
init
在pre_init后進入init流程,init作為初始化的主要函數,涉及點比較多。主要從如下三個方面入手:
- 主要流程
- 對OSDMap的處理
- 對pg的處理
主要流程
- 對OSD和OSDServer的
SafeTimer進行初始化,SafeTimer主要用於周期性執行tick線程;后面通過add_event_after啟動tick線程。
tick_timer.init();
tick_timer_without_osd_lock.init();
service.recovery_request_timer.init();
service.recovery_sleep_timer.init();
- 進行store層的
mount接口進行掛載。 - 通過
getloadavg獲取負載信息,在scrub中需要檢測是否負載超出限制。 - 對長對象名稱的處理,通過構造name和key達到上限值的對象,調用
validate_hobject_key進行測試。 - 通過
OSD::read_superblock讀取元數據,decode到osd對應的superblock成員。 - 通過
osd_compat進行一些特性方面的檢查。 - 確認
snap mapper對象是否存在,不存在的話新建。snap mapper對象保存了對象和對象快照信息。** - 確認
disk perf和net perf對象的存在,不存在的話新建。disk perf和net perf對象保存了磁盤和網絡相關的信息。 - 初始化
ClassHandler,用來管理動態鏈接庫。 - 通過
get_map獲取superblock記錄的epoch對應的OSDMap。具體分析見后文【對OSDMap的處理】。 - 通過
OSD::check_osdmap_features檢查獲取到的OSDMap的特性。 - 通過
OSD::create_recoverystate_perf創建recovery的pref,然后加入perfcounters_collection中,用來追蹤recovery階段的性能。 - 通過
OSD::clear_temp_objects遍歷所有pg的object,清除OSD down之前的曾經的臨時對象。 - 在
sharded wq中初始化OSDMap的引用。sharded wq是線程池osd_op_tp對應的工作隊列,內含多個shard對應一組線程負責一個pg。 - 加載OSD上已有的pg,具體分析見后文【對pg的處理】。
- pref相關
OSD::create_logger。 - 將OSD加入
client_messenger和cluster_messenger。前者負責集群外通信,后者負責集群內通信。任何組件想要通訊都需要繼承Dispatcher,加入messenger中並復寫相關函數。
client_messenger->add_dispatcher_head(this);
cluster_messenger->add_dispatcher_head(this);
- 將心跳
Dispatcher加入到心跳messenger中,這些messenger對應的群內外的前后心跳。
hb_front_client_messenger->add_dispatcher_head(&heartbeat_dispatcher);
hb_back_client_messenger->add_dispatcher_head(&heartbeat_dispatcher);
hb_front_server_messenger->add_dispatcher_head(&heartbeat_dispatcher);
hb_back_server_messenger->add_dispatcher_head(&heartbeat_dispatcher);
- 將
objecter加入到objecter_messenger。 - 通過
MonClient::init初始化monclient,任何和monitor的通訊需要monclient。 - 初始化
mgrclient,並加入client_messenger中。
mgrc.init();
client_messenger->add_dispatcher_head(&mgrc);
- 設置
logclient,logclient和monitor交互,保證了節點間日志的一致性。
monc->set_log_client(&log_client);
update_log_config();
- 初始化
OSDService,設置OSDMap和superblock等成員。在OSDservice的初始化過程中,初始化或開啟了一些timer和finisher。
service.init();
service.publish_map(osdmap);
service.publish_superblock(superblock);
service.max_oldest_map = superblock.oldest_map;
- 開啟一些線程池,並通過
OSD::set_disk_tp_priority設置線程池優先級。
peering_tp.start();
osd_op_tp.start();
remove_tp.start();
recovery_tp.start();
command_tp.start();
set_disk_tp_priority();
- 通過
heartbeat_thread.create()開啟心跳。 - 通過調用
MonClient::authenticate進行monclient的鑒權。 - 在OSD啟動后,之前的
crush可能需要更新。- 通過
OSD::update_crush_device_class更新設備類型,該功能在Luminous中引入,可以區分osd是hdd/ssd。讀取osd掛載目錄的crush_device_class來決定設備類型,沒有讀取到讀默認值,調用mon命令進行應用。 - 通過
OSD::update_crush_location()更新crush。更新OSD的weight和location,調用mon命令來創建或者移動bucket。
- 通過
- 通過調用
OSDService::final_init()開啟objecter。 - 調用
OSD::consume_map()。距離分析可以參考另外一篇blog。 - 發送一些
MMonSubscribe類型的消息。
monc->sub_want("osd_pg_creates", last_pg_create_epoch, 0);
monc->sub_want("mgrmap", 0, 0);
monc->renew_subs();
- 調用
OSD::start_boot進入boot流程,關於OSD的boot和狀態轉化,可以參考我的另一篇blog。
對OSDMap的處理
在初始化過程中,有兩個地方進行了OSDMap的獲取:
- 獲取
superblock記錄的epoch對應的OSDMap。 - 加載pg時獲取對應的OSDMap。
獲取的調用鏈為:
OSD::get_map-->OSDService::get_map-->OSDService::try_get_map
OSDMapRef OSDService::try_get_map(epoch_t epoch)
{
Mutex::Locker l(map_cache_lock);
OSDMapRef retval = map_cache.lookup(epoch);
if (retval) {
dout(30) << "get_map " << epoch << " -cached" << dendl;
if (logger) {
logger->inc(l_osd_map_cache_hit);
}
return retval;
}
...
OSDMap *map = new OSDMap;
if (epoch > 0) {
dout(20) << "get_map " << epoch << " - loading and decoding " << map << dendl;
bufferlist bl;
if (!_get_map_bl(epoch, bl) || bl.length() == 0) {
derr << "failed to load OSD map for epoch " << epoch << ", got " << bl.length() << " bytes" << dendl;
delete map;
return OSDMapRef();
}
map->decode(bl);
} else {
dout(20) << "get_map " << epoch << " - return initial " << map << dendl;
}
// 加入map_cache緩存
return _add_map(map);
}
- 在
try_get_map中使用了map_cache_lock保護,該所用於保護從cache中獲取map的一致性。 - 首先從
map_cache中查找,如果未找到再通過OSDService::_get_map_bl將map從map_bl_cache(和前面的MapCache不同)中讀取或從磁盤中讀取並加入到緩存中。
Q:這幾種cache是在什么時候加入的?
A:在OSD處理OSDMap消息時(handle_osd_map)中,將map和增量map加入map_bl_cache和map_bl_inc_cache。map_cahche由_add_map添加。
bool OSDService::_get_map_bl(epoch_t e, bufferlist& bl)
{
// * 先檢查一下cache中有沒有
bool found = map_bl_cache.lookup(e, &bl);
if (found) {
if (logger)
logger->inc(l_osd_map_bl_cache_hit);
return true;
}
if (logger)
logger->inc(l_osd_map_bl_cache_miss);
found = store->read(coll_t::meta(),
OSD::get_osdmap_pobject_name(e), 0, 0, bl,
CEPH_OSD_OP_FLAG_FADVISE_WILLNEED) >= 0;
// * 加入map_cache_bl緩存
if (found) {
_add_map_bl(e, bl);
}
return found;
}
對pg的處理
前文提到。調用OSD::load_pgs對OSD上已有的pg進行加載:
- 通過store層的
list_collections從硬盤中讀取PG(current目錄下),並遍歷。 - 在
**OSD::load_pgs**中有一個優化點,可以通過多線程來加速加載。
for (vector<coll_t>::iterator it = ls.begin();
it != ls.end();
++it) {
spg_t pgid;
//對PGTemp和需要清理的pg進行清理
//recursive_remove_collection函數主要進行了一下幾個刪除步驟
// 1. 遍歷PG對應的Objects,刪除對應的Snap
// 2. 遍歷PG對應的Objects,刪除Object
// 3. 刪除PG對應的coll_t
if (it->is_temp(&pgid) ||
(it->is_pg(&pgid) && PG::_has_removal_flag(store, pgid))) {
dout(10) << "load_pgs " << *it << " clearing temp" << dendl;
recursive_remove_collection(cct, store, pgid, *it);
continue;
}
...
// 獲取OSD Down前最后的pg對應的OSDMap epoch
epoch_t map_epoch = 0;
// 從Omap對象中獲取
int r = PG::peek_map_epoch(store, pgid, &map_epoch);
...
if (map_epoch > 0) {
OSDMapRef pgosdmap = service.try_get_map(map_epoch);
...
//如果獲取到了PG對應的OSMap
pg = _open_lock_pg(pgosdmap, pgid);
} else {
//如果沒有,就用之前獲取的OSDMap
pg = _open_lock_pg(osdmap, pgid);
}
...
//讀取pg狀態和pg log
pg->read_state(store);
//pg不存在?判斷依據是info的history中created_epoch為0
if (pg->dne()) {
// 刪除pg相關操作
...
}
...
PG::RecoveryCtx rctx(0, 0, 0, 0, 0, 0);
// 進入Reset狀態
pg->handle_loaded(&rctx);
}
Q:PG為什么會不存在?
A:可能是在加載的過程中防止PG被移除
- 上述代碼中,獲取了OSD上pg對應的OSDMap后執行了
_open_lock_pg,這一步獲取了PG對象且對對象進行了加鎖,下面來分析一下代碼。
PG *OSD::_open_lock_pg(
OSDMapRef createmap,
spg_t pgid, bool no_lockdep_check)
{
assert(osd_lock.is_locked());
//構造PG
PG* pg = _make_pg(createmap, pgid);
{
//讀取PGMap的寫鎖,因為要修改PGMap
RWLock::WLocker l(pg_map_lock);
// PG上鎖
pg->lock(no_lockdep_check);
pg_map[pgid] = pg;
// PG的引用計數+1
pg->get("PGMap"); // because it's in pg_map
// 維護pg_epochs和pg_epoch結構
service.pg_add_epoch(pg->info.pgid, createmap->get_epoch());
}
return pg;
}
Q:在OSD啟動的過程中,已經通過superblock對應的epoch嘗試獲取了OSDMap,為什么還需要在加載OSD的PG時,獲取PG對應的OSDMap?
Q:什么時候應該上PG鎖?
A:這里需要拷貝復制,為了保證前后一致性,需要上鎖
Q:pg的引用計數什么時候增加?
A:類似只能指針的原理,pg作為等號右邊的值,給別的變量拷貝賦值了,引用計數+1
- 分析一下
PG::read_state,主要功能是讀取pg log和pg state
void PG::read_state(ObjectStore *store, bufferlist &bl)
{
// 通過PG::read_info讀取PG狀態
// PG的元數據信息保存在一個object的omap中
// 具體分析過程
int r = read_info(store, pg_id, coll, bl, info, past_intervals,
info_struct_v);
assert(r >= 0);
...
ostringstream oss;
pg_log.read_log_and_missing(
store,
coll,
info_struct_v < 8 ? coll_t::meta() : coll,
ghobject_t(info_struct_v < 8 ? OSD::make_pg_log_oid(pg_id) : pgmeta_oid),
info,
force_rebuild_missing,
oss,
cct->_conf->osd_ignore_stale_divergent_priors,
cct->_conf->osd_debug_verify_missing_on_start);
if (oss.tellp())
osd->clog->error() << oss.str();
if (force_rebuild_missing) {
dout(10) << __func__ << " forced rebuild of missing got "
<< pg_log.get_missing()
<< dendl;
}
// log any weirdness
log_weirdness();
}
final_init
在OSD::final_init中注冊admin socket命令,這些命令格式為ceph daemon osd.X xxx,比如:
ceph daemon osd.0 dump_disk_perf
總結
了解OSD的啟動流程,對理解整個OSD模塊很有幫助。在啟動流程中基本涵蓋了OSD工作流程中的各種組件和結構,因為篇幅所限很多地方沒有展開,有些地方也存在一些疑問。希望后續能對內容繼續深入,逐個擊破。
