Monitor的Paxos實現主要包括Leveldb、Paxos和PaxosService三層,其中Leveldb負責底層存儲,同時也負責PaxosService給Paxos傳遞數據。Paxos負責Paxos算法的具體實現,PaxosService則是基於Paxos提供的服務,包括OSDMap、MonitorMap、PGMap、CRUSHMap。
下面以MonitorMap爲例,分析PaxosService和Paxos是如何工作的。利用ceph mon add添加monitor節點,則monitor判斷命令前綴是"mon add",會把新節點加入到pending_map中,如下
if (!prepare_update(op)) // 調用bool MonmapMonitor::prepare_command(MonOpRequestRef op)
if (prefix == "mon add")
pending_map.add(name, addr);
pending_map.last_changed = ceph_clock_now();
propose = true; //說明要propose給其它節點
else if (prefix == "mon remove" || prefix == "mon rm")
...
mon->reply_command(op, err, rs, get_last_committed());
return propose;
return true;
// 無論是立即propose還是延遲propose,都會調用這個函數
propose_pending();
MonitorDBStore::TransactionRef t = paxos->get_pending_transaction();
return pending_proposal;
encode_pending(t);
// 編碼pending_map
pending_map.encode(bl, mon->get_quorum_con_features());
put_version(t, pending_map.epoch, bl);
t->put(get_service_name(), ver, bl); // MonmapMonitor的service_name 是monmap
put_last_committed(t, pending_map.epoch);
t->put(get_service_name(), last_committed_name, ver);
have_pending = false;
proposing = true;
paxos->queue_pending_finisher(new C_Committed(this)); //具有回調函數finish,但不會立馬調用
paxos->trigger_propose();
if (is_active()) // return state == STATE_ACTIVE;
propose_pending();
pending_proposal->encode(bl);
committing_finishers.swap(pending_finishers);
state = STATE_UPDATING;
begin(bl);
return true;
else
return false;
可以看到,當pending_map改變時,會將新的<“monmap”+pendin_map.epoch,pending_map>和<“monmap”+“last_committed”,pending_map.epoch>存儲到MonitorDBStore緩存中。
Paxos實現
Monitor節點分爲一個leader節點和多個peon節點,且只有leader節點可以發起提議,下面以monmap更新分析Paxos實現。
leader向各個peon節點發送OP_BEGIN消息
begin(new_value);
accepted.insert(mon->rank);
auto t(std::make_shared<MonitorDBStore::Transaction>());
t->put(get_name(), last_committed+1, new_value);
t->put(get_name(), "pending_v", last_committed + 1);
t->put(get_name(), "pending_pn", accepted_pn);
get_store()->apply_transaction(t);
// 給peon發送OP_BEGIN消息
for (set<int>::const_iterator p = mon->get_quorum().begin(); p != mon->get_quorum().end(); ++p)
if (*p == mon->rank)
continue;
MMonPaxos *begin = new MMonPaxos(mon->get_epoch(), MMonPaxos::OP_BEGIN, ceph_clock_now());
begin->values[last_committed+1] = new_value;
begin->last_committed = last_committed;
begin->pn = accepted_pn;
mon->messenger->send_message(begin, mon->monmap->get_inst(*p));
last_committed爲最新確認的消息,所以下一個要確認的消息的版本號就是last_committed+1,每次選舉成功後都會生成一個accepted_pn,並且直到下一次選舉,該accepted_pb都不會變。
從節點受到OP_BEGIN消息後,處理如下
void Paxos::handle_begin(MonOpRequestRef op)
MMonPaxos *begin = static_cast<MMonPaxos*>(op->get_req());
// 如果對方的accepted_pn小於當前的accepted_pn,就說明是一箇舊老的leader節點發的提議
if (begin->pn < accepted_pn)
op->mark_paxos_event("have higher pn, ignore");
return ;
state = STATE_UPDATING;
//未處理的版本號只比最新確認的版本號多1,說明一次只能提確認一個消息
version_t v = last_committed+1;
auto t(std::make_shared<MonitorDBStore::Transaction>());
t->put(get_name(), v, begin->values[v]);
t->put(get_name(), "pending_v", v);
t->put(get_name(), "pending_pn", accepted_pn);
get_store()->apply_transaction(t);
// 迴應接受消息
MMonPaxos *accept = new MMonPaxos(mon->get_epoch(), MMonPaxos::OP_ACCEPT,
ceph_clock_now());
accept->pn = accepted_pn;
accept->last_committed = last_committed;
begin->get_connection()->send_message(accept);
主節點收到OP_ACCEPT消息後
void Paxos::handle_accept(MonOpRequestRef op)
// 對方的last_committed太小
if (last_committed > 0 && accept->last_committed < last_committed-1)
return;
// 記錄收到了哪些節點的迴應消息
accepted.insert(from);
// 如果收到全部節點的迴應消息
if (accepted == mon->get_quorum())
commit_start();
auto t(std::make_shared<MonitorDBStore::Transaction>());
t->put(get_name(), "last_committed", last_committed + 1);
decode_append_transaction(t, new_value);
get_store()->queue_transaction(t, new C_Committed(this)); //會立馬調用回調函數
paxos->commit_finish();
last_committed++;
first_committed = get_store()->get(get_name(), "first_committed");
for (set<int>::const_iterator p = mon->get_quorum().begin(); p != mon->get_quorum().end(); ++p)
if (*p == mon->rank) continue;
// 發送OP_COMMIT消息
MMonPaxos *commit = new MMonPaxos(mon->get_epoch(), MMonPaxos::OP_COMMIT, ceph_clock_now());
commit->values[last_committed] = new_value;
commit->pn = accepted_pn;
commit->last_committed = last_committed;
mon->messenger->send_message(commit, mon->monmap->get_inst(*p));
new_value.clear();
state = STATE_REFRESH;
do_refresh()
mon->refresh_from_paxos(&need_bootstrap);
for (int i = 0; i < PAXOS_NUM; ++i)
// 以MonmapMonitor爲例分析refresh函數
paxos_service[i]->refresh(need_bootstrap);
cached_first_committed = mon->store->get(get_service_name(), first_committed_name);
cached_last_committed = mon->store->get(get_service_name(), last_committed_name);
update_from_paxos(need_bootstrap);
version_t version = get_last_committed();
return cached_last_committed;
if (version <= mon->monmap->get_epoch())
return;
// MonmapMonitor的cached_last_committed就是pending_map的版本號,
// 如果和當前map的版本號不同,就得重新選舉
if (need_bootstrap && version != mon->monmap->get_epoch())
*need_boostrap = true;
// 依據pending_map的版本號,獲取pending_map內容
get_version(version, monmap_bl);
return mon->store->get(get_service_name(), ver, bl);
// 將最新pending_map解碼到monmap中
mon->monmap->decode(monmap_bl);
if (need_bootstrap)
mon->bootstrap();
return false;
return true;
從節點收到OP_COMMIT消息
void Paxos::handle_commit(MonOpRequestRef op)
store_state(commit);
auto t(std::make_shared<MonitorDBStore::Transaction>());
map<version_t,bufferlist>::iterator start = m->values.begin();
map<version_t,bufferlist>::iterator end = start;
while (end != m->values.end() && end->first <= m->last_committed)
last_committed = end->first;
++end;
if (start != end)
// 更新最新的last_committed
t->put(get_name(), "last_committed", last_committed);
map<version_t,bufferlist>::iterator it;
for (it = start; it != end; ++it)
t->put(get_name(), it->first, it->second);
decode_append_transaction(t, it->second);
get_store()->apply_transaction(t);
(void)do_refresh();
mon->refresh_from_paxos(&need_bootstrap);
for (int i = 0; i < PAXOS_NUM; ++i)
paxos_service[i]->refresh(need_bootstrap);
cached_first_committed = mon->store->get(get_service_name(), first_committed_name);
cached_last_committed = mon->store->get(get_service_name(), last_committed_name);
update_from_paxos(need_bootstrap);
version_t version = get_last_committed();
return cached_last_committed;
if (version <= mon->monmap->get_epoch())
return;
// 如果monmap發生變化,就得重新選舉
if (need_bootstrap && version != mon->monmap->get_epoch())
*need_boostrap = true;
get_version(version, monmap_bl);
return mon->store->get(get_service_name(), ver, bl);
mon->monmap->decode(monmap_bl);
if (need_bootstrap)
mon->bootstrap();
return false;
return true;
通過上面的代碼分析,可以知道ceph monitor正常模式下,monmap更新的流程如下圖所示:
(1) 主monitor收到客戶端修改monmap請求,monitor節點更新暫時記錄在pending_map中。
(2) 將pending_map編碼(bl),並將<“monmap”+pending_map.epoch, bl>和<“monmap”+“last_committed”,pending_map.epoch >(下面的說明中,省去"monmap"前綴)添加到pending_proposal所代表的MonitorDBStore緩存中,pending_proposal就代表了需要在monitor集羣間同步的數據。
(3) 主monitor調用paxos層的begin函數開啓paxos算法的第一階段,begin記錄<last_committed+1, pending_proposal>、<“pending_v”, last_committed+1>、<“pending_pn”, accepted_pn>到RocksDB數據庫,這三個鍵值對分別表明了下一個要commit的版本號和提議內容、下一個要commit的版本號,本節點的提議號,主節點的accepted_pn表示本次選舉後的提議號,每次選舉後都會生成一個遞增的提議號,從節點的表示接受到的最大提議號,(當節點故障恢復後,可以從RocksDB獲得這上鍵值對,繼續工作)。然後將pending_proposal中的內容分發到quorum裏的其它monitor從節點,這部分消息叫OP_BEGIN。
(4) 從節點收到OP_BEGIN消息後,如果對方的提議號小於自己接受過的最大提議號,則不作任何迴應,否則和主節點一樣,記錄<last_committed+1, pending_proposal>、<“pending_v”, last_committed+1>、<“pending_pn”, accepted_pn>到RocksDB數據庫。並給主節點回應OP_ACCEPT消息。
(5) 如果主節點收到了全部從節點的OP_ACCEPT消息,就記錄<“last_committed”, last_committed+1>鍵值對到數據庫,並給從節點發送OP_COMMIT消息。自己則從數據庫中讀取最新版本的monmap。
(6) 從節點收到OP_COMMIT消息後,將最新commit的內容記錄到RocksDB數據庫,並更新monmap。