InfluxDB Java 客户端性能和功能改进

导航至

我们对最新版本的 Influxdb-java 客户端(2.10 版本)的性能和功能进行了重大改进。在这篇博客文章中,我们将更详细地探讨这些改进。

性能

性能是任何关键任务系统(其中包括时间序列数据库)的关键特性,而我们通过最新版本的 InfluxDB Java 客户端(influxdb-java 版本 2.10)实现的最重要的改进之一就是提高了数据摄入率。以下是对版本 2.9 和 2.10 之间摄入率基准测试的剖析报告的总结。

CPU 剖析

剖析环境:Intel Core I7 6700HQ,16GB RAM,Windows 10 企业版

配置 ID 压力工具 参数 Influxdb-java 耗时(秒)
1 inch-java -t 10,10,10,10 2.9 15
2 inch-java -t 10,10,10,10 2.10 7

配置 ID(1)和(2)之间的耗时差异表明,查询时间降低了超过一半。但它也意味着 2.10 版本中的摄入率提高了 2 倍。

为了检查发生了什么,我们使用 YourKit 对(1)和(2)进行了剖析,并发现

  • 在配置 ID(1)中,对 String.replace(CharSequence, CharSequence) 的过度使用是性能问题的原因,来自 Point.concatenatedTags(StringBuilder) 和 Point.concatenatedFields(StringBuilder) 
  • 与配置 ID(2)相比,没有使用 String.replace(CharSequence, CharSequence)

YourKit 截图

以下提交并没有改变行为,而是解决了它的执行速度: 性能:更高效地转义字段和键 #424。此外,相关的拉取请求具体描述了写入性能的改进。

堆剖析

版本 2.10 的开销大约比 2.9 低 40% - 50%,如下两个图所示。

图 1:版本 2.9

图 2:版本 2.10

对象分配

对象分配记录是一个繁重的进程,因此使用了缩小的场景 -t 10,10。在最新构建中,对象分配开销比 2.9 低大约 40%。

版本 2.9

版本 2.10

MessagePack 支持

"MessagePack 是一种高效的二进制序列化格式。它允许您在多种语言之间交换数据,如 JSON,并且更快、更压缩。小整数被编码为一个字节,而典型的短字符串只需要额外一个字节,加上字符串本身。"(《来源https://msgpack.org/)

InfluxDB 从 1.4 版本开始采用 MessagePack 作为标准响应格式。以这种轻量级交换格式查询数据可显著提高性能。MessagePack 被添加到 InfluxDB 中,不仅是为了提高查询性能,还为了解决 JSON 表示整数时的限制。有关更多详细信息,请参阅原始提案

从 2.12 版本开始,InfluxDB Java 客户端支持 MessagePack。以下是对 JSON 和 MessagePack 之间的快速比较。我们收集了一些针对 MessagePack 和 JSON 的性能测量数据(以查询时间为准)。测量是在以下配置上进行的:Intel Core I7 6700HQ,16 GB RAM,Windows 10 Enterprise,InfluxDB OSS 1.6,influxdb-java 客户端 2.12。

测试场景

使用 java-inch 工具 创建基准测试数据。使用以下命令运行 java-inch 工具: param -t 2,20,10 -p 10000 -> 400 系列 * 10000 点 = 4 百万点

  1. 重启服务器
  2. 运行 QueryComparator  对 JSON 进行测试
  3. 重启服务器
  4. 运行 QueryComparator  对 MessagePack 进行测试
  5. 以下表格总结了第 3 步和第 5 步收集的指标。
语句 JSON 查询时间(毫秒) MSGPACK 查询时间(毫秒) 差异
select * from m0 where tag0='value0' 13647 10134 25.74%
select * from m0 29519 19191 34.99%

观察到相对改进约为 25.74%,对于返回 4M 点完整数据集的查询,改进约为 34.99%。我们使用了 Eclipse Memory Analyzer 来分析 QueryComparator 进程的堆转储,并显示了 QueryResult 对象的保留大小差异。以下表格显示了内存堆分析的结果

JSON MessagePack
791,736,832 字节 650,940,032 字节

图 1:JSON 情况

图 2:MessagePack 情况

在这种情况下,MessagePack 使用了 17.7% 的更少堆。原因是 MessagePack 将时间值编码为 Long 值,并且通常使用更少的内存。JSON 将时间格式化为 String,因此与 MessagePack 相比,字符串的转换更资源密集。

功能

我们还添加了一些功能改进,增强了 influxdb-java 客户端的企业就绪性。以下重点介绍客户端的行为方面,例如可靠性、弹性和健壮性。

可靠性和弹性

当InfluxDB服务器超出其限制时,它开始输出各种错误状态。最终客户端可能会挂起,没有响应,直到超时。如果有许多客户端连接到一个无响应的InfluxDB服务器,那么系统可能会消耗所有关键资源,并将导致整个系统崩溃。解决这种情况的常见模式是客户端在服务器恢复正常后回退并重试。这种模式目前在Telegraf中应用以保证可靠性——与以下描述的其他技术一起。

批量写入

数据库访问通常包括向数据库发送某些查询语言语句。每次数据库访问都会在客户端和服务器端消耗一定量的开销。单独发送这些语言语句会导致数据库服务器无谓地重复开销。因此,通常情况下,批量写入是解决数据库优化的方法(因为大多数现代数据库都支持此功能)。InfluxDB和influxdb-java都支持批量写入。对于InfluxDBImpl实例,我们可以通过BatchOptions实例启用批量写入。

BatchOptions options = BatchOptions.DEFAULTS.actions(100).flushDuration(100);
influxDB.enableBatch(options);

在上面的代码片段中,我们使用以下选项启用批量写入

  • actions=100(actions参数表示批量大小。当批次大小达到100时,influxdb-java将刷新批次到数据库服务器——批次中有100个数据点)
  • flushDuration=100(influxdb-java将每隔100毫秒定期刷新批次到数据库服务器)

actionsflushDuration是influxdb-java中批量写入支持的最基本的两个选项。

有关更多详细信息,请参阅org.influxdb.BatchOptions Javadocs。

写入间隔抖动

Telegraf在内部缓冲区上实现了可变刷新间隔,以便在多个客户端同时同步并将批写入InfluxDB时,可以将多个节点的负载分散到一段时间内,而不会出现重复的峰值。此功能在influxdb-java 2.9中实现。

通常,刷新间隔将在flushDuration-jitterDurationflushDuration+jitterDuration之间随机波动。

例如,使用以下选项,下一个实际的flushDuration将在80毫秒到120毫秒之间。

BatchOptions options = BatchOptions.DEFAULTS.actions(100).flushDuration(100).jitterDuration(20);
influxDB.enableBatch(options);

错误时的写入重试

Telegraf会在某些类型的失败上进行写入重试,例如cache-max-memory-size。虽然InfluxDB在输入上维护缓存(由cache-max-memory-siz CMD选项配置),当达到限制时,InfluxDB会拒绝任何进一步的写入,并显示以下消息:("cache-max-memory-size exceeded: (%d/%d)", n, limit)

这是响应的显示——InfluxDB配置参数cache-max-memory-size = 104

HTTP/1.1 500 Internal Server Error
Content-Encoding: gzip
Content-Type: application/json
Request-Id: 582b0222-f064-11e7-801f-000000000000
X-Influxdb-Version: 1.2.2
Date: Wed, 03 Jan 2018 08:59:02 GMT
Content-Length: 87

{"error":"engine: cache-max-memory-size exceeded: (1440/104)"}

当缓存被清空时,重试请求是有意义的。以下是InfluxDB返回的错误列表,以帮助指示不应重试写入。这是由于点已经写入或重复写入会再次失败。

服务器错误 描述
字段类型冲突 冲突的类型将永远卡在缓冲区中。
超出保留策略的点 此错误表示点比保留策略允许的旧,这不是问题所在。除非修改保留策略,否则重试不会有帮助。
无法解析 此错误表示客户端或InfluxDB解析行协议中的错误。重试将不会成功。
提示:手传队列不为空 这只是一个信息性消息。
数据库未找到 数据库未找到。

Telegraf 会持续写入失败的测量值,直到这些值被写入。重试次数没有限制,如第126行所示。Telegraf 会检查 InfluxDB 的特定错误,以确保无法通过重试修复的测量值不会反复写入

失败的写入缓冲区有上限:默认为10000条记录。当缓冲区填满时,额外的失败写入将替换最旧的条目。这个特性已在 influxdb-java 2.9 中实现。客户端维护一个失败写入的缓冲区,以便稍后重试写入,这有助于克服临时网络问题或 InfluxDB 负载峰值。

当缓冲区满载并且有新的数据点写入时,缓冲区中最旧的条目将被删除。实际上,我们通过将 BatchOptions 中的 bufferLimit 设置为一个大于 actions 的值来启用此功能,如下面的示例片段所示

BiConsumer<Iterable<Point>, Throwable> exceptionHandler = (batch, exception) -> {
  //do something
};
BatchOptions options = BatchOptions.DEFAULTS.bufferLimit(500).actions(100).flushDuration(100).jitterDuration(20).exceptionHandler(exceptionHandler);
spy.enableBatch(options);

writeSomePoints();

我们已将重试的 bufferLimit 设置为500个数据点。然后假设 writeSomePoints() 在某些可恢复的错误(如 cache-max-memory-size)上失败,客户端将稍后重试写入失败的数据点,直到它们成功写入。如果缓冲区满载并且有新的数据点到达,则失败的数据点将从重试缓冲区中删除。

结论

这些更改使得 InfluxDB Java 客户端更加出色、更适用于企业级,并且与 Telegraf 客户端的操作行为一致。