mongocxx::client并发安全问题:从将json转bson插入mongodb失败说起

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/LuyaoYing001/article/details/80435496

最近,开发的交易系统与mongodb进行交互时遇到一个很诡异的问题,这个坑比较深,搜索引擎上能找到的资料也都刷了一遍,试了无数种可能解决问题的方案。最后,放弃搜索,重新从代码出发。

从最后的出错信息上看,很难定位到问题根源,在clion开发环境运行抛出以下运行时错误。刚开始以为是查询出错了,check了所有与coll.find()有关的代码块,采用注释掉部分程序去定位产生问题的代码片段(笨办法)一步一步去找。

terminate called after throwing an instance of 'mongocxx::v_noabi::query_exception'
  what():  Invalid reply to find command.: generic server error

终于,定位到出问题的代码块如下:

coll.insert_one(bsoncxx::from_json(data).view());

接着尝试使用operation_exception和bsoncxx::excepption去捕获异常,但是都没有成功,判断这应该是一个运行时异常。刚开始以为bsoncxx::from_json解析出现问题,因为系统在并发地调用该代码块,所以怀疑是否bsoncxx处理线程安全的代码有bug,然后使用关键字bsoncxx from_json搜索,又尝试了数种可能的解决方案(重新编译libbson mongoc mongocxx,采用static link替换share link等等),依然无法解决问题。机智地想到用下面代码排除是否bsoncxx的嫌疑:

cout << bsoncxx::to_json(bsoncxx::from_json(depth_market_data).view()) << endl;

代码可以正常运行,所以结论是,bsoncxx::from_json不是问题的“始作俑者”。

持续地观察bug,分析bug,回顾了下bug的几个特征:

  1. 使用全新的mongodb数据库时,运行正常,当使用一段时间(几天或一两周)后,出现上述问题;
  2. 每次运行,insert_one函数也不是完全失败,数据库会插入几条数据,之后程序失败;

从bug现象再出发,bug时有时无,那多半就是线程问题,一种可能性是多线程访问mongodb造成数据库的压力太大,mongodb断开insert_one的连接(数据库本身并没有掉线,依然能够响应外部访问)。继续回到搜索引擎查别人是否遇到过mongodb并发插入出现类似问题,没有相关搜索结果。于是,尝试去配置mongodb连接池以求降低mongodb并发压力,开始去mongocxx官网查看如何配置连接池,此时,转机出现了,意外发现了官网上关于mongocxx并发安全的有关论述:
这里写图片描述

https://mongodb.github.io/mongo-cxx-driver/mongocxx-v3/thread-safety/

In general each mongocxx::client object AND all of its child objects should be used by a single thread at a time. This is true even for clients acquired from a mongocxx::pool.

Even if you create multiple child objects from a single client, and synchronize them individually, that is unsafe as they will concurrently modify internal structures of the client. The same is true if you copy a child object.

黑体字部分明确提到,在并发环境中,每个独立线程不能与其他线程共享同一个mongocxx::client实例,即使很好地用互斥锁进行同步也会由于client内部结构的改变而带来意想不到的情况。

知道问题真正的root cause以后,改起来就容易了,在问题代码处新建一个mongocxx::client,把坑彻底填上:

mongocxx::client db_client{mongocxx::uri{MONGO_URI}};
collection coll= db_client[DB_NAME].collection(COLLECTION_NAME);
coll.insert_one(bsoncxx::from_json(data).view());

填完坑后的总结

以前遇到bug,一般根据出错信息在百度上或者google上面总能很快地找到问题的解决办法,复杂比较难搞的问题也就是多试几次不同的方案总能够较快解决的。而这次的问题,从遇到问题,忽视,取巧地解决,到重视,无法搜索到root cause,静下心来分析bug,跟自己较劲一定要解决问题,经历了一个比较曲折又完整的心路历程,耗费了很长的一段时间,前前后后有两周。

回过头看,总结点经验教训:在搜索引擎没法直接提供答案时,要重新分析bug特征,理清可能的原因,然后一个个去排除,不要像没头苍蝇一样去乱试网上的方案,感觉问题有点类似就试了再说,往往是在浪费时间。

另外,这个问题之所以出现是因为对mongocxx不熟悉,在使用自己没有接触过或没有很多使用经验的技术或工具时,最好还是系统性的去学习一遍,可以提前避免出错,反而节省了项目花的时间,关键是减小运行时系统出错的风险。

猜你喜欢

转载自blog.csdn.net/LuyaoYing001/article/details/80435496