OSD的狀態轉化


簡介

本文基於Luminous版本,分析一下OSD各狀態的含義和狀態轉化關系。OSD的狀態類型定義在osd_state_t,共有如下幾種狀態:

typedef enum {
    STATE_INITIALIZING = 1,
    STATE_PREBOOT,
    STATE_BOOTING,
    STATE_ACTIVE,
    STATE_STOPPING,
    STATE_WAITING_FOR_HEALTHY
} osd_state_t;

對於這些狀態,其意義和出現的位置見下表,后文將對每個狀態進行詳細分析。

狀態 意義 位置
STATE_INITIALIZING OSD初始狀態;新建OSD對象后,state的默認值。 class OSD
STATE_PREBOOT OSD准備初始化;在OSD::start_boot中發送get_version消息之前設置。 OSD::start_boot
STATE_BOOTING OSD正在初始化,在get_version流程結束后,調用回調在OSD::_send_boot中,發送MOSDBoot消息之后設置。 OSD::_send_boot
STATE_ACTIVE OSD變為active狀態。 OSD::_committed_osd_maps
STATE_STOPPING OSD開始關閉。 OSD::shutdown
STATE_WAITING_FOR_HEALTHY OSD等待心跳健康。 OSD::start_waiting_for_healthy

STATE_PREBOOT

STATE_INITIALIZING作為新建OSD對象后的初試狀態,STATE_PREBOOT才是真正意義上的初試狀態。當執行OSD::start_boot時將OSD狀態設置為STATE_PREBOOT。首先梳理一下OSD::start_boot在哪些地方調用:

  • OSD::init
  • OSD::tick :tick線程對應了OSD中的SafeTimer tick_timer
  • OSD::ms_handle_connect
  • OSD::_committed_osd_maps

首先分析一下OSD::start_boot的流程,然后再分析一下調用OSD::start_boot的邏輯。

OSD::start_boot

當OSD初始化時,調用OSD::start_boot進入boot流程。函數具體流程為:

  • 處理如果OSD不處於健康的狀態的情況,詳情參考后文STATE_WAITING_FOR_HEALTHY
  • 通過monclient向mon發送get_version的消息,獲取OSDMap的版本信息,完成后執行回調函數OSD::_got_mon_epochs
  • OSD::_got_mon_epochs調用了OSD::_preboot
    • 調用OSD::heartbeat ,目的是確認容量狀態,不會讓一個已經標記了full狀態的down osd進入boot流程。關於心跳檢測可以參考我另外一片blog。​

    • OSDMap相關的檢測:

      • epoch是否為0
      • OSD是否有CEPH_OSDMAP_NOUP標記,有該標記的OSD不能進入up狀態。
      • 版本相關信息
      • 判斷full信息是否需要更新(通過實際狀態和OSDMap中的記錄對比),需要的話想mon發送MOSDFull消息。
      • 版本檢查通過進入OSD::_send_boot
    • 如果沒有成功進入OSD::_send_boot,調用osdmap_subscribe對OSDMap進行更新,之前只獲取了版本號相關的信息。

Q:更新了OSDMap之后呢?
A:在OSD::tick線程中會重新調用OSD::start_boot

image

OSD::init中調用

OSD::init是OSD啟動流程中最主要的函數,在末尾部分會調用**OSD::start_boot**,可以參考我的另一篇blog,這里不做贅述。

OSD::tick中調用

OSD::tick函數的具體流程為:

  • 如果是STATE_ACTIVE或者STATE_WAITING_FOR_HEALTHY,調用OSD::maybe_update_heartbeat_peers更新heartbeat peer。
  • 如果是STATE_PREBOOT或者STATE_WAITING_FOR_HEALTHY調用**OSD::start_boot**
  • 調用OSD::do_waitersfinished中的op進行dispatch。相關內容可以參考我的另一篇blog。

OSD::ms_handle_connect中調用

OSD::ms_handle_connect作為一個繼承Dispatcher需要復寫的函數,調用時機為:

  • 連接剛建立
  • 連接重新連接

即別的通信組件和OSD剛建立連接或者重連的時候,這兩種情況也需要調用**OSD::start_boot**,使OSD進入Boot流程。具體的邏輯為:

  • 如果OSD處於STATE_PREBOOT狀態將調用**OSD::start_boot**
  • 如果OSD處於STATE_BOOTING狀態說明正在boot的過程中,此時調用OSD::_send_boot

OSD::_committed_osd_maps中調用

OSD::_dispatch中收到OSDMap類型的消息時調用OSD::handle_osd_map,將OSDMap本地化事物生成后會注冊兩個回調:

store->queue_transaction(
    service.meta_osr.get(),
    std::move(t),
    new C_OnMapApply(&service, pinned_maps, last),
    new C_OnMapCommit(this, start, last, m), 0);

queue_transaction完成后會調用這兩個回調類中的finish函數:

  • 前者調用OSDService::clear_map_bl_cache_pins清理map_bl_inc_cachemap_bl_cache的緩存。關於這兩個緩存可以查看另一篇blog關於OSDMap處理部分。
  • 后者調用OSD::_committed_osd_maps做新OSDMap相關的處理。

了解了OSD::_committed_osd_maps的調用時機,該函數主要進行了三個判斷:

  • OSD需不需要關閉
  • OSD需不需要重啟
  • 是否有網絡錯誤

這里受限於篇幅原因,只分析和OSD::start_boot相關的內容。

if (do_shutdown) {
    ...
  }
  else if (m->newest_map && m->newest_map > last) {
    ...
  }
  else if (is_preboot()) {
    if (m->get_source().is_mon())
      _preboot(m->oldest_map, m->newest_map);
    else
      start_boot();
  }
  else if (do_restart)
    start_boot();

可以看出

  • 如果OSD不需要關閉且在STATE_PREBOOT狀態。
    • 如果該OSDMap消息是來自mon,則進入OSD::_preboot函數。因為已經有了來自mon的最新OSDMap,無需通過上述的get_version去獲取OSDMap epoch,直接進入OSD::_preboot函數。
    • 如果該OSDMap消息是來自osd,則進入OSD::start_boot
  • 如果OSD需要重啟,也進入OSD::start_boot

STATE_BOOTING

OSD::_send_boot中設置STATE_BOOTING狀態,接上文的OSD::start_boot之后。主要功能為:

  • 獲取各類addr,為下一步做准備。
  • 向mon發送MOSDBoot消息。

STATE_STOPPING

OSD::shutdown中設置STATE_STOPPING狀態,表明OSD處於正在關閉的狀態。
在OSDService中還有幾個和關閉相關的狀態:主要是和OSDService的關閉狀態有關。

enum {
    NOT_STOPPING,
    PREPARING_TO_STOP,
    STOPPING 
};
  • NOT_STOPPING為默認值
  • OSDService::prepare_to_stop向mon發送MOSDMarkMeDown類型的消息(要求ack):
    • 在發送之前設置為PREPARING_TO_STOP狀態。
    • 發送之后且is_stopping_cond Signal后(在OSDService::got_stop_ack中收到ack回復后)設置為STOPPING狀態。

Q:OSDService::prepare_to_stop何時調用?
A:在L版中是在OSD::shutdown中,主要作用是給mon發消息。
Q:在日志中怎么搜索這種情況?
A:搜索【telling mon we are shutting down】
Q:這些狀態和OSD的的STATE_STOPPING有什么關系?
A:這三個狀態主要用來維護OSDService發送消息的流程,和OSD狀態沒有太大關系。
Q:為什么關閉后要給mon發送消息?
A:需要判斷能否mark down,修改OSDMap等工作。詳細可以查看OSDMonitor::preprocess_mark_me_downOSDMonitor::prepare_mark_me_down。關於Mon的消息處理和同步可以參考我另外一篇blog

STATE_ACTIVE

STATE_ACTIVE表明OSD變為active狀態。具體代碼在OSD::_committed_osd_maps中:

epoch_t _bind_epoch = service.get_bind_epoch();
// OSDMap中本OSD為up
  if (osdmap->is_up(whoami) &&
// OSD為新起的OSD
    osdmap->get_addr(whoami) == client_messenger->get_myaddr() &&
    _bind_epoch < osdmap->get_up_from(whoami)) {

  if (is_booting()) {
    dout(1) << "state: booting -> active" << dendl;
      // 設置狀態
    set_state(STATE_ACTIVE);

    // set incarnation so that osd_reqid_t's we generate for our
    // objecter requests are unique across restarts.
    service.objecter->set_client_incarnation(osdmap->get_epoch());
  }
}

STATE_WAITING_FOR_HEALTHY

STATE_WAITING_FOR_HEALTHY狀態的含義為等待心跳健康的階段,在OSD::start_waiting_for_healthy中設置,關於心跳檢測可以參考我的另外一篇blog
如何定義健康?在OSD::start_boot中對是否健康進行了判斷,如果在OSD boot的過程中還處於不健康的狀態,不進行boot后續操作。

if (!_is_healthy()) {
    // if we are not healthy, do not mark ourselves up (yet)
    dout(1) << "not healthy; waiting to boot" << dendl;
    if (!is_waiting_for_healthy())
      start_waiting_for_healthy();
    // send pings sooner rather than later
    heartbeat_kick();
    return;
  }

可以看到判斷的關鍵在OSD::_is_healthy 函數,在該函數中有兩個判斷點:

  • 通過HeartbeatMap::_check檢查所有心跳線程是否超時。
  • 檢查所有的Heartbeat_peers成員是否健康,如果滿足一下任意一個條件則為不健康:
    • 沒有收到心跳前peer或后peer的回復。
    • ping_history不為空(說明不是沒有發送ping消息或者已經接受了所有回復的情況)且現在的時間now已經大於ping_historyoldest_deadline時間。

總結

OSD的狀態變化和PG相比相對來說比較簡單,本文着重分析了流程和主要函數,理解狀態變化對理解OSD是至關重要的。


免責聲明!

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



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