OSD啟動流程分析


簡介

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 perfnet perf對象的存在,不存在的話新建。disk perfnet 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_messengercluster_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);
  • 設置logclientlogclientmonitor交互,保證了節點間日志的一致性。
monc->set_log_client(&log_client);
update_log_config();
  • 初始化OSDService,設置OSDMapsuperblock等成員。在OSDservice的初始化過程中,初始化或開啟了一些timerfinisher
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工作流程中的各種組件和結構,因為篇幅所限很多地方沒有展開,有些地方也存在一些疑問。希望后續能對內容繼續深入,逐個擊破。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM