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: , j: , wtimeout: } }]
// @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(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(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(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(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(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 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(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();
}