很多人對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;
}
}
...
}