加入收藏 | 设为首页 | 会员中心 | 我要投稿 常州站长网 (https://www.0519zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 站长学院 > MySql教程 > 正文

从零写一个时间序列数据库

发布时间:2019-06-13 10:43:47 所属栏目:MySql教程 来源:Fabian Reinartz
导读:副标题#e# 编者按:Prometheus 是 CNCF 旗下的开源监控告警解决方案,它已经成为 Kubernetes 生态圈中的核心监控系统。本文作者Fabian Reinartz 是Prometheus 的核心开发者,这篇文章是其于 2017 年写的一篇关于Prometheus 中的时间序列数据库的设计思考,
副标题[/!--empirenews.page--]

从零写一个时间序列数据库

编者按:Prometheus 是 CNCF 旗下的开源监控告警解决方案,它已经成为 Kubernetes 生态圈中的核心监控系统。本文作者 Fabian Reinartz 是 Prometheus 的核心开发者,这篇文章是其于 2017 年写的一篇关于 Prometheus 中的时间序列数据库的设计思考,虽然写作时间有点久了,但是其中的考虑和思路非常值得参考。长文预警,请坐下来慢慢品味。


我从事监控工作。特别是在 Prometheus 上,监控系统包含一个自定义的时间序列数据库,并且集成在 Kubernetes 上。

在许多方面上 Kubernetes 展现出了 Prometheus 所有的设计用途。它使得持续部署continuous deployments弹性伸缩auto scaling和其他高动态环境highly dynamic environments下的功能可以轻易地访问。查询语句和操作模型以及其它概念决策使得 Prometheus 特别适合这种环境。但是,如果监控的工作负载动态程度显著地增加,这就会给监控系统本身带来新的压力。考虑到这一点,我们就可以特别致力于在高动态或瞬态服务transient services环境下提升它的表现,而不是回过头来解决 Prometheus 已经解决的很好的问题。

Prometheus 的存储层在历史以来都展现出卓越的性能,单一服务器就能够以每秒数百万个时间序列的速度摄入多达一百万个样本,同时只占用了很少的磁盘空间。尽管当前的存储做的很好,但我依旧提出一个新设计的存储子系统,它可以修正现存解决方案的缺点,并具备处理更大规模数据的能力。

备注:我没有数据库方面的背景。我说的东西可能是错的并让你误入歧途。你可以在 Freenode 的 #prometheus 频道上对我(fabxc)提出你的批评。

问题,难题,问题域

首先,快速地概览一下我们要完成的东西和它的关键难题。我们可以先看一下 Prometheus 当前的做法 ,它为什么做的这么好,以及我们打算用新设计解决哪些问题。

时间序列数据

我们有一个收集一段时间数据的系统。

  1. identifier -> (t0, v0), (t1, v1), (t2, v2), (t3, v3), ....

每个数据点是一个时间戳和值的元组。在监控中,时间戳是一个整数,值可以是任意数字。64 位浮点数对于计数器和测量值来说是一个好的表示方法,因此我们将会使用它。一系列严格单调递增的时间戳数据点是一个序列,它由标识符所引用。我们的标识符是一个带有标签维度label dimensions字典的度量名称。标签维度划分了单一指标的测量空间。每一个指标名称加上一个唯一标签集就成了它自己的时间序列,它有一个与之关联的数据流value stream

这是一个典型的序列标识符series identifier集,它是统计请求指标的一部分:

  1. requests_total{path="/status", method="GET", instance=”10.0.0.1:80”}
  2. requests_total{path="/status", method="POST", instance=”10.0.0.3:80”}
  3. requests_total{path="/", method="GET", instance=”10.0.0.2:80”}

让我们简化一下表示方法:度量名称可以当作另一个维度标签,在我们的例子中是 __name__。对于查询语句,可以对它进行特殊处理,但与我们存储的方式无关,我们后面也会见到。

  1. {__name__="requests_total", path="/status", method="GET", instance=”10.0.0.1:80”}
  2. {__name__="requests_total", path="/status", method="POST", instance=”10.0.0.3:80”}
  3. {__name__="requests_total", path="/", method="GET", instance=”10.0.0.2:80”}

我们想通过标签来查询时间序列数据。在最简单的情况下,使用 {__name__="requests_total"} 选择所有属于 requests_total 指标的数据。对于所有选择的序列,我们在给定的时间窗口内获取数据点。

在更复杂的语句中,我们或许想一次性选择满足多个标签的序列,并且表示比相等条件更复杂的情况。例如,非语句(method!="GET")或正则表达式匹配(method=~"PUT|POST")。

这些在很大程度上定义了存储的数据和它的获取方式。

纵与横

在简化的视图中,所有的数据点可以分布在二维平面上。水平维度代表着时间,序列标识符域经纵轴展开。

  1. series
  2. ^
  3. | . . . . . . . . . . . . . . . . . . . . . . {__name__="request_total", method="GET"}
  4. | . . . . . . . . . . . . . . . . . . . . . . {__name__="request_total", method="POST"}
  5. | . . . . . . .
  6. | . . . . . . . . . . . . . . . . . . . ...
  7. | . . . . . . . . . . . . . . . . . . . . .
  8. | . . . . . . . . . . . . . . . . . . . . . {__name__="errors_total", method="POST"}
  9. | . . . . . . . . . . . . . . . . . {__name__="errors_total", method="GET"}
  10. | . . . . . . . . . . . . . .
  11. | . . . . . . . . . . . . . . . . . . . ...
  12. | . . . . . . . . . . . . . . . . . . . .
  13. v
  14. <-------------------- time --------------------->

Prometheus 通过定期地抓取一组时间序列的当前值来获取数据点。我们从中获取到的实体称为目标。因此,写入模式完全地垂直且高度并发,因为来自每个目标的样本是独立摄入的。

这里提供一些测量的规模:单一 Prometheus 实例从数万个目标中收集数据点,每个数据点都暴露在数百到数千个不同的时间序列中。

在每秒采集数百万数据点这种规模下,批量写入是一个不能妥协的性能要求。在磁盘上分散地写入单个数据点会相当地缓慢。因此,我们想要按顺序写入更大的数据块。

(编辑:常州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读