作者:小心大巧 | 来源:互联网 | 2024-12-21 18:13
在高并发需求的C++项目中,我们最初选择了JsonCpp进行JSON解析和序列化。然而,在处理大数据量时,JsonCpp频繁抛出异常,尤其是在多线程环境下问题更为突出。通过分析发现,旧版本的JsonCpp存在多线程安全性和性能瓶颈。经过评估,我们最终选择了RapidJSON作为替代方案,并实现了显著的性能提升。
在一个需要高并发处理的C++项目中,我们最初使用了JsonCpp来进行JSON数据的解析和序列化。然而,在处理较大规模的JSON数据时,JsonCpp频繁抛出异常,特别是在多线程环境中,异常发生的概率更高。
通过深入研究,我们发现这是由于使用的JsonCpp版本较老,且该版本不支持多线程安全(参考:相关讨论)。更新到最新版本后,虽然解决了部分问题,但性能仍未达到预期。通过热点分析工具,我们发现大部分时间消耗在JsonCpp的readValue函数上,尤其在处理大量浮点数值时表现不佳。
为了解决这些问题,我们引入了RapidJSON库。根据基准测试结果,RapidJSON在多线程环境下的性能表现优异。实际测试表明,在40线程的压力测试下,RapidJSON相比JsonCpp性能提升了约10倍,特别是在处理包含大量浮点数值的JSON数据时,性能提升尤为明显。
以下是我们在项目中使用RapidJSON的一些关键方法:
- 将通用解析方法封装在
rapidjson_utils.hpp
文件中:
#ifndef RAPIDJSON_UTILS_H__
#define RAPIDJSON_UTILS_H__
#include
#include
#include
#include
#include
#include
namespace utils {
using std::string;
// 获取字符串值
static int get_rapidjson_string(const rapidjson::Value& js_in, const string& key, string& value) {
rapidjson::Pointer pointer(key.c_str());
if (!pointer.IsValid()) return -1;
const rapidjson::Value* pValue = rapidjson::GetValueByPointer(js_in, pointer);
if (!pValue || !pValue->IsString()) return -1;
value = pValue->GetString();
return 0;
}
// 获取整数值
static int get_rapidjson_int(const rapidjson::Value& js_in, const string& key, int& value) {
rapidjson::Pointer pointer(key.c_str());
if (!pointer.IsValid()) return -1;
const rapidjson::Value* pValue = rapidjson::GetValueByPointer(js_in, pointer);
if (!pValue || !pValue->IsNumber()) return -1;
value = pValue->GetInt();
return 0;
}
// 获取浮点数值
static int get_rapidjson_number(const rapidjson::Value& js_in, const string& key, float& value) {
rapidjson::Pointer pointer(key.c_str());
if (!pointer.IsValid()) return -1;
const rapidjson::Value* pValue = rapidjson::GetValueByPointer(js_in, pointer);
if (!pValue || !pValue->IsNumber()) return -1;
value = pValue->GetDouble();
return 0;
}
// 获取布尔值
static int get_rapidjson_bool(const rapidjson::Value& js_in, const string& key, bool& value) {
rapidjson::Pointer pointer(key.c_str());
if (!pointer.IsValid()) return -1;
const rapidjson::Value* pValue = rapidjson::GetValueByPointer(js_in, pointer);
if (!pValue || !pValue->IsBool()) return -1;
value = pValue->GetBool();
return 0;
}
// 添加字符串成员
static void add_member_string(rapidjson::Value& node, const string& key, const string& value, rapidjson::Document::AllocatorType& alloc) {
node.AddMember(rapidjson::Value(key.c_str(), key.length(), alloc).Move(), rapidjson::Value(value.c_str(), value.length(), alloc).Move(), alloc);
}
// 添加整数成员
static void add_member_int(rapidjson::Value& node, const string& key, int value, rapidjson::Document::AllocatorType& alloc) {
node.AddMember(rapidjson::Value(key.c_str(), key.length(), alloc).Move(), rapidjson::Value(value).Move(), alloc);
}
// 添加浮点数成员
static void add_member_float(rapidjson::Value& node, const string& key, double value, rapidjson::Document::AllocatorType& alloc) {
node.AddMember(rapidjson::Value(key.c_str(), key.length(), alloc).Move(), rapidjson::Value(value).Move(), alloc);
}
// 添加布尔成员
static void add_member_bool(rapidjson::Value& node, const string& key, bool value, rapidjson::Document::AllocatorType& alloc) {
node.AddMember(rapidjson::Value(key.c_str(), key.length(), alloc).Move(), rapidjson::Value(value).Move(), alloc);
}
// 将Value转换为字符串
static string to_string(const rapidjson::Value& v) {
rapidjson::StringBuffer sb;
rapidjson::Writer writer(sb);
v.Accept(writer);
return sb.GetString();
}
// 将Value转换为格式化的字符串
static string to_styled_string(const rapidjson::Value& v) {
rapidjson::StringBuffer sb;
rapidjson::PrettyWriter writer(sb);
v.Accept(writer);
return sb.GetString();
}
}
#endif // RAPIDJSON_UTILS_H__
- 测试代码放在
rapidjson_example.cpp
中:
#include
#include
#include
#include
#include "rapidjson_utils.hpp"
using std::string;
int main(int argc, char** argv) {
string data = "{\"user\":\"hello\",\"movies\":[{\"name\":\"TENET\",\"like\":10000}],\"song\":{\"暗里着迷\":\"已经播放了3遍\"},\"ratio\":0.98}";
rapidjson::Document doc;
rapidjson::ParseResult ok = doc.Parse(data.c_str());
if (!ok) {
printf("parse json failure id=%d offset=%d msg=%s\n", doc.GetParseError(), doc.GetErrorOffset(), rapidjson::GetParseError_En(doc.GetParseError()));
return -1;
}
string user;
utils::get_rapidjson_string(doc, "/user", user);
printf("user=%s\n", user.c_str());
// 遍历数组
const rapidjson::Value* pMovies = rapidjson::GetValueByPointer(doc, "/movies");
if (pMovies && pMovies->IsArray() && !pMovies->Empty()) {
for (rapidjson::Value::ConstValueIterator iter = pMovies->Begin(); iter != pMovies->End(); ++iter) {
string name;
int like;
utils::get_rapidjson_string(*iter, "/name", name);
utils::get_rapidjson_int(*iter, "/like", like);
printf("movie=%s like=%d\n", name.c_str(), like);
}
}
// 遍历对象
const rapidjson::Value* pSOng= rapidjson::GetValueByPointer(doc, "/song");
if (pSong && pSong->IsObject() && !pSong->ObjectEmpty()) {
const rapidjson::Value& sOng= *pSong;
for (rapidjson::Value::ConstMemberIterator miter = song.MemberBegin(); miter != song.MemberEnd(); ++miter) {
printf("sOng=%s message=%s\n", miter->name.GetString(), miter->value.GetString());
}
}
float ratio;
utils::get_rapidjson_number(doc, "/ratio", ratio);
printf("ratio=%f\n", ratio);
// 生成新的JSON
rapidjson::Document out(rapidjson::kObjectType);
rapidjson::Document::AllocatorType& a = out.GetAllocator();
out.CopyFrom(doc, a);
rapidjson::Value* pNewMovies = rapidjson::GetValueByPointer(out, "/movies");
rapidjson::Value movie(rapidjson::kObjectType);
utils::add_member_string(movie, "name", "Memento", a);
utils::add_member_int(movie, "like", 12366, a);
utils::add_member_float(movie, "score", 0.95, a);
pNewMovies->PushBack(movie.Move(), a);
// 新增节点
rapidjson::SetValueByPointer(out, "/version", 2.0, a);
string s = utils::to_styled_string(out);
printf("after modify json=%s\n", s.c_str());
return 0;
}
参考资料:
RapidJSON官网
Git仓库
一些JSON解析器的性能对比
关于JsonCpp和RapidJSON的选择