指标、日志和追踪:比它们看起来更相似?
作者:Andrew Lamb / 用例, 开发者
2023 年 5 月 5 日
导航至
本文最初发表于 The New Stack,并经许可在此处转载。
它们需要不同的存储和查询方法,这使得使用单一解决方案成为挑战。但 InfluxDB 正在努力将它们整合到一个解决方案中。
时间序列数据 具有独特的特征,使其与其他类型的数据区分开来。但即使在时间序列数据的范围内,也存在需要不同工作负载的不同类型的数据。指标、追踪和日志都不同,这使得设计一个可以处理所有三种数据类型的单一解决方案成为挑战。
虽然所有三种类型的数据结构通常相同,但每种工作负载的查询模式都不同。旨在存储时间序列数据的系统并非都以相同方式处理这些不同的查询模式。我们在时间序列市场中看到了这种挑战的反映,其中有三类用于指标、追踪和日志的软件。
诸如 InfluxDB、Grafana、Prometheus 等解决方案可以收集、存储、分析和可视化指标数据。Jaeger 可用于端到端分布式追踪,InfluxDB 的最新更新也使其成为可行的选择。对于日志,常见的解决方案是所谓的 ELK 栈,它由 Elasticsearch、Logstash 和 Kibana 组成,但像 Loki 这样的解决方案也可以处理日志。
日志是最具挑战性的时间序列数据类型,因此让我们深入探讨一下为什么会这样。
数据模型
如上所述,所有时间序列数据都可以使用相同的数据模型。InfluxDB 的行协议提供了一个有用的示例。
measurement,tag1=value,tag2=value field1=value,field2=value timestamp
在这里,测量函数,如表名和时间戳,正是如此。标签和字段是键值对,其中标签充当元数据,字段表示您要收集、存储、分析和/或可视化的数据。
系列是测量和标签值的唯一组合,因此您拥有的标签越多,您拥有的唯一系列就越多。我们称之为 基数,当基数过高时,可能会影响性能。这个问题对于所有基于日志结构合并树 (LSM) 的方法都很典型,LSM 是指标系统的常见解决方案。
解析日志数据
假设我们正在使用日志数据来调试应用程序。日志的原始输出可能如下所示
"level=debug msg=\"Not resuming any session\" log.target=rustls::client::hs log.module_path=rustls::client::hs log.file=/usr/local/cargo/registry/src/github.com-1ecc6299db9ec823/rustls-0.20.8/src/client/hs.rs log.line=127 target=\"log\" time=1675710426464848718\n"
如果我们仔细观察,我们可以开始将这些数据解析为键值对,然后我们可以在我们的数据模型中使用这些键值对。
level=debug
log.target=rustls::client::hs
log.module_path=rustls::client::hs log.file=/usr/local/cargo/registry/src/github.com-1ecc6299db9ec823/rustls-0.20.8/src/client/hs.rs
log.line=127
target=\”log\”
msg=”Not resuming any session”
time=1675710426464848718
此时,我们可以开始决定哪些键应该是标签(元数据),哪些是字段。对标签进行查询使开发人员能够几乎沿着任何维度切片和切块数据。但是,存在的标签越多,运行每个查询所需的资源就越多,这最终会影响性能。
如果我们查看另一个日志文件,问题开始变得更加清晰。在这里,我们已经将日志文件解析为键值对。
level=debug
msg=\”Processing request\”
request=\”Request { method: GET, uri: http://10.144.148.50:8080/metrics, version: HTTP/2.0, headers: {\\\”user-agent\\\”: \\\”Prometheus/2.38.0\\\”, \\\”accept\\\”: \\\”application/openmetrics-text;version=1.0.0,application/openmetrics-text;version=0.0.1;q=0.75,text/plain;version=0.0.4;q=0.5,*/*;q=0.1\\\”, \\\”accept-encoding\\\”: \\\”gzip\\\”, \\\”x-prometheus-scrape-timeout-seconds\\\”: \\\”10\\\”, \\\”x-forwarded-proto\\\”: \\\”http\\\”, \\\”x-request-id\\\”: \\\”001052e8-d898-4b4b-9e21-0b0a4918970a\\\”}, body: Body(Streaming) }\”
target=\”ioxd_common::http\”
location=\”ioxd_common/src/http/mod.rs:121\”
time=1675710425927921595
如果我们比较这两个日志文件,我们会看到四个常见的标签:level
、msg
、target
和 time
。还有几个唯一的标签:log.target
、log.module_path
、log.file
、log.line
、request
和 location
。因此,每个日志都是一个系列,因为它包含唯一的标签组合。当我们考虑调试应用程序时存在多少个单独的日志时,很容易看出查询该数据变得多么复杂。
造成这种情况的一个促成因素是应用程序内属性命名缺乏一致性。例如,如果您正在调试应用程序,则每个进程可能需要不同的信息,因此自然而然地,开发人员会为该特定进程创建属性,每个属性都成为键值。在整个应用程序中扩展这种做法会导致数千个不同的键,因为每个进程都在做不同的事情。
更复杂的是,像“error”这样的属性可能具有 e
、error
、err
、err_code
或开发人员可以想出的任何其他描述性排列的标签键。当然,理论上可以清理和标准化像 error 这样的属性,但这也会产生大量工作,并且需要您了解每个排列以确保没有任何遗漏。
简而言之,日志不仅生成大量数据,而且还生成大量唯一数据。这意味着数据库可能需要以不同于其他类型的时间序列数据的方式存储数据,并且查询模式必须考虑日志数据的形状。
比较日志和追踪
追踪 也可能导致基数问题。但是,当我们考虑追踪时,存在一些关键差异。
来自追踪的键的总数往往更一致。这是因为应用程序中点的数量是有限的,并且这些进程的输出包含较少的程序员定义的元素。结果是更结构化的输出。换句话说,标签键更可能是相同的,例如 spanID
,但它们的值是无界的(例如,追踪和 span ID)。
无界标签值也会导致更高的基数。但是,追踪和日志之间的关键区别在于,追踪更可能具有一个无界元素(标签值),而日志具有两个无界元素(标签键和标签值)。相比之下,指标往往具有有界标签键和标签值。这些组合中的每一种都需要不同的存储和查询方法,这就是为什么为所有三种数据类型使用单一解决方案如此具有挑战性的原因。
幸运的是,地平线上有希望。InfluxDB 长期以来一直很好地处理指标,但随着其新数据库核心 InfluxDB IOx 的发布,它现在可以在单一解决方案中管理高基数追踪数据,以及指标和原始事件数据。将三类时间序列应用程序整合为一个应用程序的努力仍在继续,InfluxDB 背后的团队将目光投向了日志。