InfluxDB Java 客户端性能和功能改进
作者:Hoan Le Xuan / 产品,用例,开发者
2018年8月1日
导航至
我们对最新版本的 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 百万点
- 重启服务器
- 运行
QueryComparator
对 JSON 进行测试 - 重启服务器
- 运行
QueryComparator
对 MessagePack 进行测试 - 以下表格总结了第 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毫秒定期刷新批次到数据库服务器)
actions
和flushDuration
是influxdb-java中批量写入支持的最基本的两个选项。
有关更多详细信息,请参阅org.influxdb.BatchOptions
Javadocs。
写入间隔抖动
Telegraf在内部缓冲区上实现了可变刷新间隔,以便在多个客户端同时同步并将批写入InfluxDB时,可以将多个节点的负载分散到一段时间内,而不会出现重复的峰值。此功能在influxdb-java 2.9中实现。
通常,刷新间隔将在flushDuration
-jitterDuration
和flushDuration
+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 客户端的操作行为一致。