Ceph OSD寫操作失效如何處理

很多人對Ceph寫操作的異常處理的過程還不是特別的清楚。本文就介紹Ceph如何處理異常處理的。

首先需要明確的是,Ceph的讀寫操作並沒有超時機制。 rbd_write並沒有超時機制。所有經常看到,用ceph -s 命令查看,有些 slow request請求會顯示延遲 30s甚至更長的時間。

當然,osd有一個機制,會檢查某個線程執行時間,如果執行時間過長,osd就會觸發自殺機制(osd suicide timeout)。這個機制默認關閉。

下面就研究一下ceph如何處理寫操作的異常。

正常的寫流程

在OSD端正常的寫操作流程中,在函數ReplicatedBackend::submit_transaction把請求加入到in_progress_ops 隊列中

map<ceph_tid_t, InProgressOp> in_progress_ops;

該map保存了所有正在處理的請求。一個請求必須等到所有的up的osd的請求都返回才能返回給客戶端。

例如
pg 1.1(osd1,osd2,osd4)
client的請求先發起給osd1,osd1把請求發送給osd2和osd4,等到osd2和osd4上的請求返回給osd1,osd1才能給客戶端應答。

那麼,當osd2或者osd4出現crash的情況下,該請求如何處理? 甚至當osd1出現crash的情況該如何處理呢? 實際上,客戶端和OSD端都根據接收到的OSDMap的變化來做相應的處理。

OSD端的處理

當一個osd發送crash時(無論何種原因),通過Heartbeat機制,Monitor會檢查到該osd的狀態,並把該狀態通過OSDMap推送給集羣中其它的節點。相應的PG在peering的過程中,會首先丟棄正在處理請求。

例如上述的例子:

當一個osd2或者osd4發生crash時,通過hearbeat等機制,該osd的狀態會上報給monitor,當monitor判斷該osd處於down狀態,會把最新的osdmap推送給各個osd。當osd1收到該osdmap後,pg1.1發現自己的osd 列表發生了變化,就會重新發起peering過程。其會調用函數:PG::start_peering_interval, 該函數調用了ReplicatedBackend::on_change(), 該函數會把正在處理的請求從in_progress_ops中刪除。

void PG::start_peering_interval(
{
     on_change(t);
}

客戶端的處理

當客戶端接收到osdmap的變化時,會檢查所有正在進行的請求。如果該請求受osdmap的變化的影響,就會在客戶端重發請求。

void Objecter::handle_osd_map(MOSDMap *m)
{
  ...
  // 掃描需要重發的請求 
    _scan_requests(s, false, false, NULL, need_resend,
             need_resend_linger, need_resend_command, sul);
 ...
    // resend requests 重發請求 
  for (map<ceph_tid_t, Op*>::iterator p = need_resend.begin();
       p != need_resend.end(); ++p) {
    Op *op = p->second;
    OSDSession *s = op->session;
    bool mapped_session = false;
    if (!s) {
      int r = _map_session(&op->target, &s, sul);
      assert(r == 0);
      mapped_session = true;
    } else {
      get_session(s);
    }
    OSDSession::unique_lock sl(s->lock);
    if (mapped_session) {
      _session_op_assign(s, op);
    }
    if (op->should_resend) {
      if (!op->session->is_homeless() && !op->target.paused) {
    logger->inc(l_osdc_op_resend);
    _send_op(op);
      }
    } else {
      _op_cancel_map_check(op);
      _cancel_linger_op(op);
    }
    sl.unlock();
    put_session(s);
  ...
}
void Objecter::_scan_requests(OSDSession *s,
                  bool force_resend,
                  bool cluster_full,
                  map<int64_t, bool> *pool_full_map,
                  map<ceph_tid_t, Op*>& need_resend,
                  list<LingerOp*>& need_resend_linger,
                  map<ceph_tid_t, CommandOp*>& need_resend_command,
                  shunique_lock& sul)
{
    ...

  // check for changed request mappings
  map<ceph_tid_t,Op*>::iterator p = s->ops.begin();
  while (p != s->ops.end()) {
    Op *op = p->second;
    ++p;   // check_op_pool_dne() may touch ops; prevent iterator invalidation
    ldout(cct, 10) << " checking op " << op->tid << dendl;
    bool force_resend_writes = cluster_full;
    if (pool_full_map)
      force_resend_writes = force_resend_writes ||
    (*pool_full_map)[op->target.base_oloc.pool];
    int r = _calc_target(&op->target, &op->last_force_resend);
    switch (r) {
    case RECALC_OP_TARGET_NO_ACTION:
      if (!force_resend &&
      (!force_resend_writes || !(op->target.flags & CEPH_OSD_FLAG_WRITE)))
    break;
      // -- fall-thru --
    case RECALC_OP_TARGET_NEED_RESEND:
      if (op->session) {
    _session_op_remove(op->session, op);
      }
      need_resend[op->tid] = op;
      _op_cancel_map_check(op);
      break;
    case RECALC_OP_TARGET_POOL_DNE:
      _check_op_pool_dne(op, sl);
      break;
    }
  }
    ...
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章