目录
背景
metricserver2 (以下简称Agent)是与字节内场时序数据库 ByteTSD 配套使用的用户指标打点 Agent,用于在物理机粒度收集用户的指标打点数据,在字节内几乎所有的服务节点上均有部署集成,装机量达到百万以上。此外Agent需要负责打点数据的解析、聚合、压缩、协议转换和发送,属于CPU和Mem密集的服务。两者结合,使得Agent在监控全链路服务成本中占比达到70%以上,对Agent进行性能优化,降本增效是刻不容缓的命题。本文将介绍我们在Agent性能优化上的探索和实践。
基本架构
-
Receiver 监听socket、UDP端口,接收SDK发出的metrics数据
-
Msg-Parser对数据包进行反序列化,丢掉不符合规范的打点,然后将数据点暂存在Storage中
-
Storage支持7种类型的metircs指标存储
-
Flusher在每个发送周期的整时刻,触发任务获取Storage的快照,并对其存储的metrics数据进行聚合,将聚合后的数据按照发送要求进行编码
-
Compress对编码的数据包进行压缩
-
Sender支持HTTP和TCP方式,将数据发给后端服务
我们将按照数据接收、数据处理、数据发送三个部分来分析Agent优化的性能热点。
数据接收
Case 1
Agent与用户SDK通信的时候,使用 msgpack 对数据进行序列化。它的数据格式与json类似,但在存储时对数字、多字节字符、数组等都做了优化,减少了无用的字符,下图是其与json的简单对比:
Agent在获得数据后,需要通过msgpack.unpack
进行反序列化,然后把数据重新组织成 std::vector。这个过程中,有两步复制的操作,分别是:从上游数据反序列为 msgpack::object 和 msgpack::object 转换 std::vector。
{ // Process Function
msgpack::unpacked msg;
msgpack::unpack(&msg, buffer.data(), buffer.size());
msgpack::object obj = msg.get();
std::vector<std::vector<std::string>> vecs;
if (obj.via.array.ptr[0].type == 5) {
std::vector<std::string> vec;
obj.convert(&vec);
vecs.push_back(vec);
} else if (obj.via.array.ptr[0].type == 6) {
obj.convert(&vecs);
} else {
++fail_count;
return result;
}
// Some more process steps
}
但实际上,整个数据的处理都在处理函数中。这意味着传过来的数据在整个处理周期都是存在的,因此这两步复制可以视为额外的开销。
msgpack协议在对数据进行反序列化解析的时候,其内存管理的基本逻辑如下:
为了避免复制 string,bin 这些类型的数据,msgpack 支持在解析的时候传入一个函数,用来决定这些类型的数据是否需要进行复制:
因此在第二步,对 msgpack::object 进行转换的时候,我们不再转换为 string,而是使用 string_view,可以优化掉 string 的复制和内存分配等:
// Define string_view convert struct.
template <>
struct msgpack::adaptor::convert<std::string_view> {
msgpack::object const& operator()(msgpack::object const& o, std::string_view& v) const {
switch (o.type) {
case msgpack::type::BIN:
v = std::string_view(o.via.bin.ptr, o.via.bin.size);
break;
case msgpack::type::STR:
v = std::string_view(o.via.str.ptr, o.via.str.size);
break;
default:
throw msgpack::type_error();
break;
}
return o;
}
};
static bool string_refe