Actor模式封裝MongoDB C驅動-多連接併發與自動重試

原文轉自:http://www.tanjp.com/archives/192 (即時修正和更新)

 

Actor模式封裝異步的MongoDB C驅動

MongoDB C驅動(MongoDB C Driver http://mongoc.org/),提供了同步的DB訪問存儲接口,在高併發的業務系統,同步操作會阻塞業務邏輯,是高併發的一大障礙。所以需要設計異步的接口來滿足業務系統的需求。

利用Actor的特性,同一個Actor裏面的行爲,不需要加鎖,也就是說Actor可以很方便地封裝同步操作。

1、實現一個同步的MongoDB數據存取類型 SyncModb。

2、用Actor包裝SyncModb,得到類型ActorMongodb。

3、實現ActorModb類型,該類型包含了單獨的調度器(Scheduler)和舞臺(Stage),和管理所有ActorMongodb。

4、ActoModb類型接收信件,創建N個ActorMongodb對象,每個ActorMongodb對象負責一個單獨mongoc驅動連接,進行數據收發。

這樣就實現了基於Actor模式實現可多連接併發異步接口。ActoModb對象的設計如下圖:

圖文代碼請參照以上筆記

首先,實現一個同步的MongoDB數據存取類型 SyncModb,也就是說,這些接口如果直接調用會阻塞業務層邏輯。

下面只粘貼部分代碼演示使用方法:

class SyncModb
{
public:
    	SyncModb();
    	~SyncModb();
    	// 配置連接地址
    	// @ps_uri, mongod的URI,格式: mongodb://username:[email protected]:27017/?authSource=collectionname
    	// @return 執行結果集合。
    	ModbResult config(const std::string & ps_uri);
	// 插入一條數據
	// @ps_dbname, 數據庫名稱。
	// @ps_colnname, 集合名稱。
	// @ps_json, 待插入數據,json格式。
	// @ps_opts, 選項。默認爲空,格式 { writeConcern : { w: <uint32>, j: <boolean>, wtimeout: <uint32> } }]
	// @pn_retry, 失敗時自動重試的次數(每次重試耗時不少於0.1秒),0表示不自動重試。
	// @return 執行結果集合。
	ModbResult insert_one(const std::string & ps_dbname, const std::string & ps_colnname,
		const std::string & ps_json, const std::string & ps_opts = "");  
	// 關閉 mongodb 連接。
	void close();
	// @return mongodb已經建立連接返回true,如果未建立連接將嘗試重連,重連失敗返回false。
	bool connected();
	// @po_res, 根據結果集來判斷網絡是否有問題。
	// @return 返回true表示網絡有問題,否則返回false其他問題或沒問題。
	bool check_network_problem(const ModbResult & po_res);
	// ping測試Mongodb連接是否可用,每0.1秒嘗試一次。
	// @ps_dbname, 數據庫名稱。
	// @pn_try_times, 嘗試次數,取值大於零。
	// @return 0表示重試多次後還是失敗,成功返回非0表示重試了多少次後成功。
	uint32 ping(const std::string & ps_dbname, uint32 pn_try_times);
private:
	mongoc_client_t	* mp_client;
	std::string ms_mongo_uri;
	uint32 mn_ping_times; //ping次數,失敗時每次不少於0.1秒,默認值600,不少於1分鐘。
};

然後,用Actor包裝同步的MongoDB數據存取類型 SyncModb,得到類型ActorMongodb。代碼如下:

class ActorMongodb : public Actor
{
public:
    	explicit ActorMongodb(ActorModb * pp_parent, Stage * pp_stage, uint32 pn_actorid, uint32 pn_retry_times_on_disconnected = 1, uint32 pn_ping_times = 600);
    	~ActorMongodb();
    	void init() override;

private:
    	void sync_ping(const std::string & ps_dbname, uint32 pn_qid, uint32 pn_cur_try_times);
	void op_config(Mail * pp_mail);
	void op_insert_one(Mail * pp_mail);
	void op_close(Mail * pp_mail);
private:
	const uint32 kRetryTimes;
	const uint32 kPingTimes;
	ActorModb * mp_parent;
	SyncModb * mp_modb;
};
ActorMongodb::ActorMongodb(ActorModb * pp_parent, Stage * pp_stage, uint32 pn_actorid, uint32 pn_retry_times_on_disconnected, uint32 pn_ping_times)
	: Actor(pp_stage, pn_actorid), kRetryTimes(pn_retry_times_on_disconnected), kPingTimes(pn_ping_times)
	, mp_parent(pp_parent), mp_modb(new SyncModb()){}
ActorMongodb::~ActorMongodb()
{
	SAFE_DELETE(mp_modb);
}
void ActorMongodb::init()
{
	set_operation(ActorModb::kMSIdConfig, std::bind(&ActorMongodb::op_config, this, std::placeholders::_1));
	set_operation(ActorModb::kMSIdInsertOne, std::bind(&ActorMongodb::op_insert_one, this, std::placeholders::_1));
	set_operation(ActorModb::kMSIdClose, std::bind(&ActorMongodb::op_close, this, std::placeholders::_1));
}
void ActorMongodb::sync_ping(const std::string & ps_dbname, uint32 pn_qid, uint32 pn_cur_try_times)
{
	uint32 zn_ping_times = mp_modb->ping(ps_dbname, kPingTimes);
	if (zn_ping_times > 0){
		ExINFO(TI_LIBMODB, TI_NETWORK, "ActorMongodb::op_insert_many -> ping success, network reconnected, qid="
			<< pn_qid << ", cur_times=" << pn_cur_try_times << ", ping times=" << zn_ping_times);
	}else{
		ExERROR(TI_LIBMODB, TI_NETWORK, "ActorMongodb::op_insert_many -> ping failed, network disconnected, qid="
			<< pn_qid << ", cur_times=" << pn_cur_try_times << ", ping times=" << kPingTimes);
	}
}
void ActorMongodb::op_config(Mail * pp_mail)
{
	modb::ConfigAttachment * zp_attachment = static_cast<modb::ConfigAttachment*>(pp_mail->attachment);
	modb::ConfigAttachment & zo_attach = *zp_attachment;
	uint32 zn_qid = std::get<0>(zo_attach);
	std::string & zs_uri = std::get<1>(zo_attach);
	ModbResult zo_res = mp_modb->config(zs_uri);
	zo_res.qid = zn_qid;
	if (!zo_res.ok)	{
		ExERROR(TI_LIBMODB, TI_ARGUMENTS, "ActorMongodb::op_config -> sync modb config error, uri=" << zs_uri 
			<< ", result={" << zo_res.tostring()
			<< "}, actor={" << tostring() << "}");
	}
	uint32 zn_receiver = pp_mail->content;
	ModbResult * zp_res = modb::apply_result();
	zp_res->operator=(std::move(zo_res));	
	bool zb_ok = mp_parent->post(zn_receiver, ActorModb::kMSIdResult, 0, zp_res);
	if (!zb_ok)
	{
		modb::revert_result(zp_res); //歸還附件
		ExERROR(TI_LIBMODB, TI_SYSTEM_ERR, "ActorMongodb::op_config -> modb actor post error uri="	<< zs_uri
			<< ", receiver=" << zn_receiver
			<< ", result={" << zp_res->tostring()
			<< ", actor={" << tostring() << "}");
	}
	SAFE_DELETE(zp_attachment); //銷燬附件
}
void ActorMongodb::op_insert_one(Mail * pp_mail)
{
	modb::InsertOneAttachment * zp_attachment = static_cast<modb::InsertOneAttachment*>(pp_mail->attachment);
	modb::InsertOneAttachment & zo_attach = *zp_attachment;
	uint32 zn_qid = std::get<0>(zo_attach);
	std::string & zs_dbname = std::get<1>(zo_attach);
	std::string & zs_colnname = std::get<2>(zo_attach);
	std::string & zs_json = std::get<3>(zo_attach);
	std::string & zs_opts = std::get<4>(zo_attach);
	ModbResult zo_res;
	uint32 zn_cur_try_times = 0;	
	while (zn_cur_try_times <= kRetryTimes)
	{
		++zn_cur_try_times;
		zo_res = std::move(mp_modb->insert_one(zs_dbname, zs_colnname, zs_json, zs_opts));
		if (!mp_modb->check_network_problem(zo_res))
		{
			break; //成功或不是網絡問題
		}
		//網絡有問題
		if (kRetryTimes <= 0)
		{
			break; //不重試
		}		
		ExERROR(TI_LIBMODB, TI_NETWORK, "ActorMongodb::op_insert_one -> network problem sync modb insert_one error, " 
			<< ", qid=" << zn_qid << ", cur_times=" << zn_cur_try_times << ", dbname=" << zs_dbname 
			<< ", colnname=" << zs_colnname << ", json=" << zs_json << ", opts=" << zs_opts	
			<< ", result={" << zo_res.tostring() << "}, actor={" << tostring() << "}");
		sync_ping(zs_dbname, zn_qid, zn_cur_try_times);
	}
	zo_res.qid = zn_qid;
	if (!zo_res.ok)
	{
		ExERROR(TI_LIBMODB, TI_LOGIC_ERR, "ActorMongodb::op_insert_one -> sync modb insert_one error, dbname=" << zs_dbname
			<< ", colnname=" << zs_colnname << ", json=" << zs_json << ", opts=" << zs_opts
			<< ", result={" << zo_res.tostring() << "}, actor={" << tostring() << "}");
	}
	uint32 zn_receiver = pp_mail->content;
	ModbResult * zp_res = modb::apply_result();
	zp_res->operator=(std::move(zo_res));
	bool zb_ok = mp_parent->post(zn_receiver, ActorModb::kMSIdResult, 0, zp_res);
	if (!zb_ok)
	{
		modb::revert_result(zp_res); //歸還附件
		ExERROR(TI_LIBMODB, TI_SYSTEM_ERR, "ActorMongodb::op_insert_one -> modb actor post error, receiver=" 
			<< zn_receiver << ", result={" << zp_res->tostring() << ", actor={" << tostring() << "}");
	}
	modb::revert_insert_one(zp_attachment); //歸還附件
}
void ActorMongodb::op_close(Mail * pp_mail)
{
	this->close();
	mp_modb->close();
}

最後,實現ActorModb類型,該類型包含了單獨的調度器(Scheduler)和舞臺(Stage),和管理所有ActorMongodb。

struct ModbSpecialData
{
    	explicit ModbSpecialData(uint32 pn_core_num = 2, uint32 pn_retry_times = 5, uint32 pn_ping_times = 600)
		: core_num(pn_core_num), retry_times(pn_retry_times), ping_times(pn_ping_times)	{}
    	uint32 core_num; //分配給連接的線程數
    	uint32 retry_times; //重試次數
    	uint32 ping_times; //檢查網絡ping的次數(每次不少於0.1秒)
};
class ActorModb : public Actor
{
private:
	friend class ActorMongodb;
	static const uint32 kMSIdConfig = MODB_MAIL_SUBJECT(1);
	static const uint32 kMSIdInsertOne = MODB_MAIL_SUBJECT(2);
	static const uint32 kMSIdClose = MODB_MAIL_SUBJECT(9);
public:
	static const uint32 kMSIdResult = MODB_MAIL_SUBJECT(0);
	static const uint32 kActorId = MODB_ACTOR_ID;
	static bool post_config(ActorPtr & pp_sender, uint32 pn_id, uint32 pn_qid, const std::string & ps_uri);	
	static bool post_insert_one(ActorPtr & pp_sender, uint32 pn_id, uint32 pn_qid, 
		const std::string & ps_dbname, const std::string & ps_colname, const std::string & ps_json, 
		const std::string & ps_opts = "");
	static bool post_close(ActorPtr & pp_sender);
public:
	explicit ActorModb(Stage * pp_stage);
	~ActorModb();
	void init() override;
	bool set_special(void * pp_data) override;
	bool set_special(ModbSpecialData * pp_data);
private:
	void op_config(Mail * pp_mail);
	void op_insert_one(Mail * pp_mail);
	void op_close(Mail * pp_mail);
private:
	Scheduler * mp_schd;
	Stage * mp_stage;
	ModbSpecialData mo_data;
};
bool ActorModb::post_config(ActorPtr & pp_sender, uint32 pn_id, uint32 pn_qid, const std::string & ps_uri)
{
	modb::ConfigAttachment * zp_attachment = new modb::ConfigAttachment(pn_qid, ps_uri);
	bool zb_ok = pp_sender->post(kActorId, kMSIdConfig, pn_id, zp_attachment);
	if (!zb_ok){SAFE_DELETE(zp_attachment); /*銷燬附件*/	}
	return zb_ok;
}
bool ActorModb::post_insert_one(ActorPtr & pp_sender, uint32 pn_id, uint32 pn_qid,
	const std::string & ps_dbname, const std::string & ps_colname, const std::string & ps_json,
	const std::string & ps_opts)
{
	modb::InsertOneAttachment * zp_attachment = modb::apply_insert_one();
	std::get<0>(*zp_attachment) = pn_qid;
	std::get<1>(*zp_attachment) = ps_dbname;
	std::get<2>(*zp_attachment) = ps_colname;
	std::get<3>(*zp_attachment) = ps_json;
	std::get<4>(*zp_attachment) = ps_opts;
	bool zb_ok = pp_sender->post(kActorId, kMSIdInsertOne, pn_id, zp_attachment);
	if (!zb_ok)
	{
		modb::revert_insert_one(zp_attachment); //歸還附件
	}
	return zb_ok;
}
bool ActorModb::post_close(ActorPtr & pp_sender)
{
	bool zb_ok = pp_sender->post(kActorId, kMSIdClose, 0, 0);
	return zb_ok;
}
ActorModb::ActorModb(Stage * pp_stage)
	: Actor(pp_stage, MODB_ACTOR_ID), mp_schd(0), mp_stage(0), mo_data(2, 5, 600){}
ActorModb::~ActorModb()
{
	SAFE_DELETE(mp_stage);
	SAFE_DELETE(mp_schd);
}
void ActorModb::init()
{
	set_operation(kMSIdConfig, std::bind(&ActorModb::op_config, this, std::placeholders::_1));
	set_operation(kMSIdInsertOne, std::bind(&ActorModb::op_insert_one, this, std::placeholders::_1));
	set_operation(kMSIdClose, std::bind(&ActorModb::op_close, this, std::placeholders::_1));
}
bool ActorModb::set_special(void * pp_data)
{
	ModbSpecialData * zp_data = static_cast<ModbSpecialData*>(pp_data);
	return set_special(zp_data);
}
bool ActorModb::set_special(ModbSpecialData * pp_data)
{
	if (mp_schd || mp_stage)
	{
		ExERROR(TI_LIBMODB, TI_LOGIC_ERR, "ActorModb::set_special -> can not repeat set_special.");
		return false;
	}
	mo_data.core_num = pp_data->core_num;
	mo_data.retry_times = pp_data->retry_times;
	mo_data.ping_times = pp_data->ping_times;
	//實時性要求不高,這種方式可以通過條件變量和互斥鎖在空閒時掛起,儘量少侵佔系統資源
	mp_schd = new Scheduler(mo_data.core_num, enST_PREEMPTIVE, false);
	bool zb_ok = mp_schd->start();
	if (!zb_ok)
	{
		ExERROR(TI_LIBMODB, TI_LOGIC_ERR, "ActorModb::set_special -> scheduler start failed, core_num=" << mo_data.core_num);
		SAFE_DELETE(mp_schd);
		return false;
	}
	mp_stage = new Stage(mp_schd);
	return zb_ok;
}
void ActorModb::op_config(Mail * pp_mail)
{
	modb::ConfigAttachment * zp_attachment = static_cast<modb::ConfigAttachment*>(pp_mail->attachment);
	if (!mp_schd || !mp_stage)
	{
		SAFE_DELETE(zp_attachment); //銷燬附件
		ExERROR(TI_LIBMODB, TI_LOGIC_ERR, "ActorModb::op_config -> please call function set_special after init.");
		return;
	}
	uint32 zn_id = pp_mail->content;
	ActorPtr zp_actor = ActorPtr(new ActorMongodb(this, mp_stage, zn_id, mo_data.retry_times, mo_data.ping_times));
	zp_actor->init();
	bool zb_ok = mp_stage->add_actor(zp_actor);
	if (!zb_ok)
	{
		SAFE_DELETE(zp_attachment); //銷燬附件
		ExERROR(TI_LIBMODB, TI_LOGIC_ERR, "ActorModb::op_config -> add actor failed, maybe repeat mongodb actorid="
			<< zn_id << ", actor = {" << zp_actor->tostring() << "}, mail={" << pp_mail->tostring() << "}");
		return;
	}
	zb_ok = zp_actor->post(zn_id, pp_mail->subject, pp_mail->sender(), zp_attachment);
	if (!zb_ok)
	{
		SAFE_DELETE(zp_attachment); //銷燬附件
		ExERROR(TI_LIBMODB, TI_LOGIC_ERR, "ActorModb::op_config -> actor post self actor failed, mongodb actorid=" << zn_id
			<<", parent actor = {"	<< tostring() << "}, mail={" << pp_mail->tostring() << "}");
	}
}
void ActorModb::op_insert_one(Mail * pp_mail)
{
	modb::InsertOneAttachment * zp_attachment = static_cast<modb::InsertOneAttachment*>(pp_mail->attachment);
	if (!mp_schd || !mp_stage)
	{
		modb::revert_insert_one(zp_attachment); //歸還附件
		ExERROR(TI_LIBMODB, TI_LOGIC_ERR, "ActorModb::op_insert_one -> please call function set_special after init.");
		return;
	}
	uint32 zn_id = pp_mail->content;
	ActorPtr & zp_actor = mp_stage->get(zn_id);
	if (!zp_actor)
	{
		modb::revert_insert_one(zp_attachment); //歸還附件
		ExERROR(TI_LIBMODB, TI_LOGIC_ERR, "ActorModb::op_insert_one -> get actor failed, not found mongodb actorid="
			<< zn_id << ", actor = {" << tostring() << "}, mail={" << pp_mail->tostring() << "}");
		return;
	}
	bool zb_ok = zp_actor->post(zn_id, pp_mail->subject, pp_mail->sender(), zp_attachment);
	if (!zb_ok)
	{
		modb::revert_insert_one(zp_attachment); //歸還附件
		ExERROR(TI_LIBMODB, TI_LOGIC_ERR, "ActorModb::op_insert_one -> actor post self actor failed, mongodb actorid=" << zn_id
			<< ", parent actor = {"	<< tostring() << "}, mail={" << pp_mail->tostring() << "}");
	}
}
void ActorModb::op_close(Mail * pp_mail)
{
	if (!mp_schd || !mp_stage)
	{
		ExERROR(TI_LIBMODB, TI_LOGIC_ERR, "ActorModb::op_close -> please call function set_special after init.");
		return;
	}
	this->close();
	std::vector<uint32> zc_ids = mp_stage->get_ids();
	uint32 zn_actorid = 0;
	for (auto it = zc_ids.begin(); it != zc_ids.end(); ++it)
	{
		zn_actorid = *it;
		ActorPtr & zp_actor = mp_stage->get(zn_actorid);
		zp_actor->post(zn_actorid, kMSIdClose, 0, 0);
	}
	mp_stage->wait_for_destroyed();
	mp_schd->stop();
}

測試示例

創建一個測試ActorTest對象,用於返回的結果。

class ActorTest : public Actor
{
    	static const uint32 kMSIdClose = 101;
public:
    	static bool post_close(ActorPtr & pp_sender)
    	{
    		bool zb_ok = pp_sender->post(kActorId, kMSIdClose, 0, 0);
    		return zb_ok;
    	}
	static const uint32 kActorId = 100;
	ActorTest(Stage * pp_stage)	: Actor(pp_stage, kActorId){}
	void init() override
	{
		set_operation(ActorModb::kMSIdResult, std::bind(&ActorTest::op_modb_result, this, std::placeholders::_1));
		set_operation(kMSIdClose, std::bind(&ActorTest::op_close, this, std::placeholders::_1));
	}
private:
	void op_modb_result(Mail * pp_mail)
	{
		modb::ResultAttachment * zp_attachment = static_cast<modb::ResultAttachment *>(pp_mail->attachment);
		std::cout << "ActorTest::op_modb_result uid=" << pp_mail->uid()
			<< ", result={" << zp_attachment->tostring() << "}"
			<< std::endl;
		modb::revert_result(zp_attachment);
	}
	void op_close(Mail * pp_mail)
	{
		this->close();
	}
};
void main(int argc, char* argv[])
{
	modb::init();
	std::cout << std::endl << "begin" << std::endl;
	uint32 zn_qid = 0;
	Scheduler schd(0, enST_STEALING, true);
	schd.start();
	{
		Stage stage(&schd);
		{	//創建Actor
			ActorPtr zp_actor = ActorPtr(new ActorModb(&stage));
			zp_actor->init();
			uint32 zn_core_num = 3;
			ModbSpecialData sd(3, 10, 600);
			zp_actor->set_special(&sd);
			stage.add_actor(zp_actor);
			ActorPtr zp_test = ActorPtr(new ActorTest(&stage));
			zp_test->init();
			stage.add_actor(zp_test);
		}{	// 開始投遞信件
			ActorPtr zp_actor = stage.get(ActorTest::kActorId);
			for (uint32 i = 0; i < mongodb_col_len; ++i)
			{
				ActorModb::post_config(zp_actor, mongodb_col_indexs[i], ++zn_qid, mongodb_uri);
			}
		} {	//insert_one
            ActorPtr zp_actor = stage.get(ActorTest::kActorId);
            auto f = [&](uint32 n) {
            	std::string s1("{\"insert_one_key\":\"abc ");
            	if (n > 0)
            	{
            		s1.append((n % 100) + 10, 'A' + (n % 26));
            	}
            	s1.append("\"}");
            	for (uint32 i = 0; i < mongodb_col_len; ++i)
            	{
            		std::stringstream ss;
            		ss << mongodb_colname << "_" << mongodb_col_indexs[i];
            		ActorModb::post_insert_one(zp_actor, mongodb_col_indexs[i], ++zn_qid, mongodb_dbname, ss.str(), s1, "");
            	}
            };
            for (uint32 i = 0; i < 2; ++i)
            {
            	f(i);
            }
        }{	// 投遞關閉信件
			ActorPtr zp_actor = stage.get(ActorModb::kActorId);
			ActorModb::post_close(zp_actor);
			THIS_SLEEP_SECONDS(13);
			ActorPtr zp_test = stage.get(ActorTest::kActorId);
			ActorTest::post_close(zp_test);
		}
		// 等待所有操作完成並銷燬
		stage.wait_for_destroyed();
	}
	schd.stop();
	modb::release();
}

 

 

 

 

 

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