Ceph Monitor Paxos實現

在這裏插入圖片描述
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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章