InfluxDB Java 客户端性能和功能改进
By Hoan Le Xuan / 产品, 用例, 开发者
Aug 01, 2018
导航至
我们在最新版本的 Influxdb-java 客户端的性能和功能方面做了一些重大改进。在这篇博文中,让我们更仔细地看看这些改进。
性能
性能是任何关键任务系统(包括时间序列数据库)的关键特性,而我们在最新 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) 中,过度使用来自
Point.concatenatedTags(StringBuilder)
和Point.concatenatedFields(StringBuilder)
的String.replace(CharSequence, CharSequence)
是性能问题的原因 - 与配置 ID (2) 相比,
no String.replace(CharSequence, CharSequence)
YourKit 截图
以下提交没有改变行为,而是解决了执行速度问题: Performance: Escape fields and keys more efficiently #424。此外, 相应的 pull request 具体描述了写入性能的改进。
堆性能分析
版本 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)之间交换数据,并且速度更快、压缩率更高。小整数被编码成单个字节,典型的短字符串除了字符串本身之外只需要一个额外的字节。” (来源)
InfluxDB 从 1.4 版本开始采用 MessagePack 作为标准响应格式。以这种轻量级交换格式查询数据可提供显著更好的性能。在 InfluxDB 中添加 MessagePack 不仅是为了提高查询性能,也是为了解决 JSON 表示整数的限制。有关更多详细信息,请参阅 原始提案。
InfluxDB Java 客户端从 2.12 版本开始提供 MessagePack 支持。以下是 JSON 和 MessagePack 之间的快速比较。我们收集了一些 MessagePack 和 JSON 的性能测量数据(以查询时间衡量)。测量是在以下配置下进行的:Intel Core I7 6700HQ, 16 GB RAM, Windows 10 企业版, InfluxDB OSS 1.6, influxdb-java 客户端 2.12。
测试场景
java-inch 工具用于创建基准测试数据。使用 param -t 2,20,10 -p 10000 -> 400 series * 10000 points = 4 mil points
参数运行 java-inch 工具
- 重启服务器
- 运行
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% |
对于返回 4M 点完整数据集的查询,观察到相对改进约为 25.74%,对于完整数据集的查询,则为 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 解析行协议时存在错误。重试不会成功。 |
hinted handoff 队列不为空 | 这只是一个信息性消息。 |
数据库未找到 | 数据库未找到。 |
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 客户端的行为保持一致。