度量、日志和跟踪:它们看起来比实际上更相似吗?

导航至

本文最初发表在The New Stack上,并经许可在此重新发布。

由于需要不同的存储和查询方法,因此使用单一解决方案具有挑战性。但InfluxDB正在努力将它们整合到一个系统中。

时序数据具有独特的特征,使其与其他类型的数据区别开来。但在时序数据的范围内,也存在不同类型的数据,它们需要不同的工作负载。度量、跟踪和日志各不相同,因此设计一个可以处理所有三种数据类型的单一解决方案具有挑战性。

尽管所有三种类型的数据结构通常相同,但每种工作负载的查询模式都不同。设计用于存储时序数据的不同系统并不都处理这些不同的查询模式。我们在时序数据市场看到了这一挑战的反映,其中存在度量、跟踪和日志的三个软件类别。

例如InfluxDBGrafanaPrometheus等解决方案可以收集、存储、分析和可视化度量数据。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

如果我们比较两个日志文件,我们会看到四个常见的标签:levelmsgtargettime。还有几个独特的标签:log.targetlog.module_pathlog.filelog.linerequestlocation。因此,每个日志都是一系列,因为它包含独特的标签组合。当我们考虑在调试应用程序时存在的单个日志数量时,很容易看出查询这些数据是多么复杂。

造成这种情况的一个原因是应用程序内属性命名的不一致性。例如,如果您正在调试应用程序,每个进程可能需要不同的信息,因此开发人员自然会为该特定进程创建属性,每个属性都成为键值。在整个应用程序中扩展这种做法会导致成千上万的键,因为每个进程都在做不同的事情。

更糟糕的是,像“错误”这样的属性可能有eerrorerrerr_code或其他开发人员能想到的任何描述性排列的标签键。当然,在理论上可以清理并标准化像错误这样的属性,但这也会产生大量工作,并要求您了解每个排列以确保没有遗漏。

简而言之,日志不仅生成大量数据,而且生成大量独特数据。这意味着数据库可能需要以与其他类型的时间序列数据不同的方式存储数据,并且查询模式必须考虑到日志数据的形式。

日志和跟踪的比较

跟踪也可以创建基数问题。然而,当我们思考跟踪时,有一些关键的区别。

跟踪的键总数通常更一致。这是因为应用程序中有有限数量的点,这些过程的输出包含更少的程序员定义的元素。结果是更结构化的输出。换句话说,标签键更有可能是相同的,例如spanID,但它们的值是无限的(例如,跟踪和跨度ID)。

无限的标签值也导致了更高的基数。然而,跟踪和日志之间的关键区别在于,跟踪更有可能有一个无限元素(标签值),而日志有两个无限元素(标签键和标签值)。相比之下,度量值通常既有有限的标签键也有无限的标签值。每种组合都需要不同的存储和查询方法,这就是为什么使用单个解决方案来解决所有三种数据类型如此具有挑战性的原因。

幸运的是,希望就在眼前。InfluxDB长期以来一直处理度量值很好,但随着其新数据库核心InfluxDB IOx的发布,它现在可以管理高基数跟踪数据,以及度量值和原始事件数据,在一个解决方案中。将三类时间序列应用程序合并为一个的尝试仍在继续,InfluxDB团队的目标是日志。