Trees
与元数据一起,OpenTSDB 2.0 引入了“树”的概念,这是一种将时间序列组织为易于导航的结构的分层方法,该结构可以类似于计算机上的文件系统进行浏览。用户可以使用各种规则集定义许多树,这些规则集将 TSMeta 对象组织成树结构。然后,用户可以通过 HTTP API 端点浏览结果树。有关详情,请参见/api/tree。
Tree Terminology
-
分支 -每个分支都是一棵树的一个节点。它包含子分支和叶子的列表以及父分支的列表。
-
叶 -分支的结尾,代表唯一的时间序列。该叶子将包含可用于生成 TSD 查询的 TSUID 值。分支可以并且很可能会有多个叶子
-
根 -根分支是树的起点,所有分支都从该根伸出。深度为 0.
-
深度 -每次将一个分支添加到另一个分支时,深度都会增加
-
严格匹配 -启用后,时间序列必须与规则集中每个级别的规则匹配。如果一个或多个级别不匹配,则时间序列将不包含在树中。
-
路径 -层次结构中当前分支上方的每个分支的名称和级别。
Branch
树的每个节点都记录为分支对象。每个分支都包含以下信息:
-
Branch ID -分支的 ID。这是下面描述的十六进制值。
-
显示名称 -分支的名称,由树规则集从 TSMeta 对象解析。
-
深度 -分支所在层次结构的深度。
-
路径 -每个父分支的深度和名称(包括本地分支)。
-
分支 -子分支比该分支低一个深度级别。
-
叶子 -属于该分支的叶子。
导航树始于 root 分支,该分支的 ID 始终与该分支所属树的 ID 相匹配。根目录应具有一个或多个子分支,可用于向下导航树的一级。每个孩子都可以用来导航到他们的孩子,依此类推。根没有任何父分支,并且深度始终为 0.如果刚刚定义或启用了树,则它可能还没有根分支,并且通过扩展,将不会有任何子分支。
每个分支通常都会有一个子分支列表。但是,如果分支位于路径的末尾,则它可能没有任何子分支,但是应该具有叶子列表。
分支 ID 和路径
分支 ID 是类似于 TSUID 的十六进制编码字节数组,但格式不同。分支 ID 始终以 2 个字节编码的树的 ID 开头。根分支的分支 ID 等于树 ID。因此,树1
的根的分支 ID 为0001
。
每个子分支都有一个DisplayName
值,该值的哈希值用于为该分支生成 32 位整数 ID。使用的哈希函数是 Java java.lang.String
哈希函数。然后,将整数值的 4 个字节编码为 8 个十六进制字符。例如,如果分支的显示名称为sys
,则返回的哈希将为 102093.TSD 会将该值转换为十六进制0001BECD
。
分支 ID 由与当前分支上方的每个父代 ID 串联在一起的树 ID 和与当前分支 ID 串联而成的树 ID 组成。因此,如果子分支sys
是根的子分支,则分支 ID 为00010001BECD
。
假设在sys
子分支中有一个显示名称为cpu
的分支。 cpu
返回 98728 的哈希值,该哈希值转换为十六进制的000181A8
。该孩子的 ID 为00010001BECD000181A8
。
通过分支和叶存储的方法来创建 ID,主要是因为它是从树结构中任何位置的分支中导航回树的方法。如果您知道路径的末端分支并想返回上一层或多层,这将特别有用。不幸的是,一棵深树可以创建很长的分支 ID,但是设计良好的树的深度不应超过 5 到 10 级。在达到 URI 字符约束之前,大多数 URI 请求应该支持多达 100 个级别的分支。
Leaves
唯一的时间序列在树上表示为叶子。叶子可以出现在结构的任何分支上,包括根。但是它们通常会出现在具有一个或多个叶子但没有子分支的分支中一系列分支的末端。每个叶子都包含要在查询中使用的时间序列的 TSUID,以及度量标准和标记名称/值。它还包含从规则集中解析的“显示名称”,但可能与任何度量标准,标记名称或标记值都不相同。
理想情况下,时间序列只会在树上出现一次。但是,如果修改了时间序列的 TSMeta 对象,或度量或标记的 UIDMeta,则可以对其进行第二次处理并添加第二片叶子。在树对度量,标记名或标记值具有* custom 规则且处理了 TSMeta 的情况下,然后用户添加与规则集匹配的定制字段的情况下,尤其可能发生这种情况。在这些情况下,建议在树上启用严格匹配*,以便在添加自定义数据之前不会显示时间序列。
Rules
每棵树都是根据用户定义的一组规则动态构建的。规则集必须至少包含一个规则,并且通常将包含多个规则。每个集合具有确定规则处理 Sequences 的多个“级别”。首先处理位于级别 0 的规则,然后处理处于级别 1 的规则,依此类推,直到将所有规则应用于给定的时间序列。规则集中的每个级别可能具有多个规则,以处理度量标准和标签可能没有提前计划或可能吸收了一些任意数据的情况。如果一个级别中存储了多个规则,则第一个成功的规则匹配将被应用,而其他匹配将被忽略。这些规则也通过* order *字段进行排序,以便首先处理 Sequences 为 0 的规则,然后处理 Sequences 为 1 的规则,依此类推。在日志中以及使用测试端点时,通常会以“ [\ <treeId>:\ <level>:\ <order>:\ <type>]”的格式为规则指定 ID,例如“ [1:0:1:0]”表示针对树 1,在级别 0,类型METRIC
的 Sequences1.
Rule Types
每个规则都作用于时间序列数据的单个组成部分。当前可用的类型包括:
Type | ID | Description |
---|---|---|
METRIC | 0 | 处理与时间序列关联的 Metrics 的名称 |
METRIC_CUSTOM | 1 | 在度量元数据自定义标记列表中搜索给定的辅助名称。如果匹配,则将处理与标签名称关联的值。 |
TAGK | 2 | 在标签列表中搜索给定名称。如果匹配,则将处理与标签名称关联的 tagv 值 |
TAGK_CUSTOM | 3 | 在标签列表中搜索给定名称。如果匹配,则在 tagk 元数据自定义标签列表中搜索给定的辅助名称。如果匹配,则将处理与自定义名称关联的值。 |
TAGV_CUSTOM | 4 | 在 tagvs 列表中搜索给定名称。如果匹配,则在 tagv 元数据自定义标签列表中搜索给定的辅助名称。如果匹配,则将处理与自定义名称关联的值。 |
Rule Config
单个规则可以处理正则表达式,分隔符或不进行任何处理。如果为规则定义了正则表达式和分隔符,则仅会处理正则表达式,而忽略分隔符。
验证对规则的所有更改,以确认是否填写了正确的字段,以便规则可以处理数据。必须为每种规则类型填写以下字段:
Type | field | customField |
---|---|---|
Metric | ||
Metric_Custom | X | X |
TagK | X | |
TagK_Custom | X | X |
TagV_Custom | X | X |
Display Formatter
有时,从标签或 Metrics 中提取的数据可能描述性不强。例如,应用程序可以输出带有标签对的时间序列,例如“ port = 80”或“ port = 443”。使用在 tagk 值“ port”上匹配的标准规则,我们将有两个名称分别为“ 80”和“ 443”的分支。Starters 可能不知道这些数字是什么意思。因此,用户可以定义基于令牌的格式器,该格式器将更改分支的输出以显示有用的信息。例如,我们可以声明一个格式为“{tag_name}:\ {}”的格式,分支现在将显示“ port:80”和“ port:443”。
令牌区分大小写,每个格式化程序只能出现一次。它们还必须完全按照下表中的规定显示:
Token | Description | 适用规则类型 |
---|---|---|
{ovalue} | 规则处理的原始值。例如,如果规则使用正则表达式提取值的一部分,但是您不希望提取值,则可以在此处使用原始值。 | All |
{value} | 处理后的值。如果规则具有提取的正则表达式组,或者该值已由分隔符分割,则表示发生该处理后的值。 | All |
{tag_name} | 与值关联的标签或自定义标签的名称。 | METRIC_CUSTOM,TAGK_CUSTOM,TAGV_CUSTOM,TAGK |
{tsuid} | 时间序列的 TSUID | All |
Regex Rules
在某些情况下,您可能只希望提取 Metrics,标记或自定义值的一部分以用于分组。例如,如果您的计算机在多个数据中心中具有完全合格的域名,且这些域名包含 DC 的名称,但并非所有度量标准都包含 DC 标记,则可以使用正则表达式提取 DC 以进行分组。
必须使用包含一个或多个提取运算符(即括号)的有效正则表达式来设置regex
规则参数。如果正则表达式与提供的值匹配,则提取的数据将用于构建分支或叶子。如果正则表达式中提供了多个提取,则可以使用regex_group_index
参数选择要使用的提取值。索引基于 0,默认为 0,因此,如果您要选择第二次提取的输出,则可以将该索引设置为 1.如果正则表达式与该值不匹配,或者提取无法返回有效的字符串,该规则将被视为不匹配。
例如,如果我们的主机 tagk 的 tagv 为web01.nyc.mysite.com
,则可以使用类似于.*\.(.*)\..*\..*
的正则表达式提取 FQDN 的“ nyc”部分,并将“ nyc”数据中心中的所有服务器分组在“ nyc”下”分支。
Separator Rules
许多系统的 Metrics 通常是带有分隔符(例如句点)的字符串,用于确定 Metrics 的组成部分。例如,“ sys.cpu.0.user”。要构建有用的树,可以使用分隔符规则,该规则将根据字符序列将字符串分开,并根据每个值创建分支或叶子。将分隔符设置为“。”对于前面的示例,将产生三个分支“ sys”,“ cpu”,“ 0”和一个叶子“ user”。
优先 Sequences
每个规则只能处理一个正则表达式,一个分隔符或两者都不处理。如果规则在各自的字段中同时具有“ regex”和“ separator”值,则将仅在时间序列上执行“ regex”。 “分隔符”将被忽略。如果未定义“ regex”或“ separator”,则当匹配规则的“字段”时,该字段的整个值将被处理为分支或叶子。
Tree Building
首先,必须先在 HBase 中创建tsdb-tree
表。如果启用树处理并且表不存在,则 TSD 将不会启动。
可以通过两种方式来构建树。 tsd.core.tree.enable_processing
配置设置启用实时树创建。每当用户创建或编辑新的 TSMeta 对象时,TSMeta 将通过每个已配置并启用的树。结果分支将被记录到存储中。如果发生冲突或 TSUID 在任何规则上均不匹配,将记录警告,并且如果已配置树选项,则可能会记录到存储中。
或者,您可以通过 CLI uid
工具定期同步所有 TSMeta 对象。这将扫描tsdb-uid
表,并将每个发现的 TSMeta 对象传递给已配置和启用的树。有关详情,请参见uid。
Note
对于实时树构建,您还需要启用tsd.core.meta.enable_tracking
设置,以便在接收时间序列时创建 TSMeta 对象。
创建和构建树的一般过程如下:
-
通过 HTTP API 创建新树
-
通过 HTTP API 向树分配一个或多个规则
-
通过 HTTP API 使用某些 TSMeta 对象测试规则
-
分支完全正确显示后,将树的
enable
标志设置为true
-
使用
treesync
子命令运行uid
工具以同步树中现有的 TSMeta 对象
Note
创建新树时,默认情况下将禁用它,因此不会通过规则集处理 TSMeta 对象。这样一来,您就有时间配置规则集并对其进行测试,以验证是否可以按预期构建树。
规则处理 Sequences
为了使生成的树有用,一棵树通常将具有多个规则。如上所述,规则按级别和 Sequences 组织。通过从级别 0 和 Sequences0 开始的规则集来处理 TSMeta。在规则中,处理以递增 Sequences 在级别上进行。在成功匹配 TSMeta 数据的级别上的第一个规则之后,处理将跳至下一个级别。这意味着级别上的规则实际上是``或``ed。如果级别 0 的规则的 Sequences 为 0、1、2 和 3,并且 TSMeta 与该规则的 Sequences 为 1 匹配,则将跳过 Sequences 为 2 和 3 的规则。
编辑规则时,可能会跳过某些级别或订单或将其留空。在这些情况下,处理仅跳过空白位置。您应该尽力使事情井井有条,但是规则处理器有点宽容。
Strict Matching
所有 TSMeta 对象都通过每棵树进行处理。如果只希望一个单一的整体树来组织所有 OpenTSDB 时间序列,那么这不是问题。但是,如果要为特定信息子集创建许多树,则可能要从创建叶子中排除一些时间序列条目。一棵树上的strictMatch
标志有助于过滤掉属于一棵树但不属于另一棵树的时间序列。启用严格匹配后,时间序列必须与规则集中的每个级别(具有一个或多个规则)的规则匹配,以使其包含在树中。如果该 meta 在任何级别上都无法与规则匹配,则它将被记录为不匹配的条目,并且不会生成任何叶子。
默认情况下,禁用严格匹配,以便可以在树中捕获尽可能多的时间序列。如果在树上更改此设置,则可能要删除现有分支并运行重新同步。
Collisions
由于规则集的灵 Active 以及度量标准,标签名称和值命名的多样性,两个不同的 TSMeta 条目将试图在树上创建相同的叶子几乎是不可避免的。每个分支只能有一个具有给定显示名称的叶子。例如,如果一个分支有一个名为user
且_id 为_的叶子,但是该树尝试添加一个名为user
的 tsuid 为020202
的叶子,则该新叶子将不会添加到该树中。相反,将为树记录一个* collision *条目,说 tsuid 0202020
与 tsuid 010101
的现有叶子发生了碰撞。然后,可以使用 HTTP API 来查询冲突列表,以查看由于冲突,树中是否没有出现特定的 TSUID。
Not Matched
为树启用“严格匹配”时,TSMeta 必须在规则集的每个级别上的规则上进行匹配,才能添加到树中。如果一个或多个级别不匹配,则不会添加 TSUID。类似于* collisions *,将为每个未能写入树的 TSUID 记录一个不匹配的条目。该条目将包含 TSUID 以及关于哪个规则和级别不匹配的简短消息。
Examples
假设我们的 TSD 存储了以下时间序列:
TS# | Metric | Tags | TSUID |
---|---|---|---|
1 | cpu.system | dc=dal, host=web01.dal.mysite.com | 0102040101 |
2 | cpu.system | dc=dal, host=web02.dal.mysite.com | 0102040102 |
3 | cpu.system | dc=dal, host=web03.dal.mysite.com | 0102040103 |
4 | app.connections | host=web01.dal.mysite.com | 010101 |
5 | app.errors | host=web01.dal.mysite.com, owner=doe | 0101010306 |
6 | cpu.system | dc=lax, host=web01.lax.mysite.com | 0102050101 |
7 | cpu.system | dc=lax, host=web02.lax.mysite.com | 0102050102 |
8 | cpu.user | dc=dal, host=web01.dal.mysite.com | 0202040101 |
9 | cpu.user | dc=dal, host=web02.dal.mysite.com | 0202040102 |
请注意,在此示例中,我们将不使用任何自定义值规则,因此我们无需显示 TSMeta 对象,而是假定这些值填充了 TSMeta。同样,出于说明目的,每个 UID 的 TSUID 都被截断了 1 个字节。
现在,让我们设置禁用strictMatching
并遵循以下规则的树:
Level | Order | Rule Type | Field (value) | Regex | Separator |
---|---|---|---|---|---|
0 | 0 | TagK | dc | ||
0 | 1 | TagK | host | .*\.(.*)\.mysite\.com | |
1 | 0 | TagK | host | \. | |
2 | 0 | Metric | \. |
这套规则的目标是按照数据中心,主机,度量标准对计时器进行排序。我们公司可能在世界各地有数千台服务器,因此将所有服务器都显示在树的一个分支中是没有意义的,而是我们希望按数据中心对它们进行分组,并让用户根据需要进行向下钻取。
在我们的示例数据中,我们有一些没有dc
标记名称的旧时间序列。但是,host
标签确实具有嵌入了数据中心名称的完全限定域名。因此,我们规则集的第一层有两个规则。第一个将查找dc
标记,如果找到,它将使用该标记的值,第二个规则将被跳过。如果dc
标签不存在,则第二条规则将扫描host
标签的值,并尝试从 FQDN 中提取数据中心名称。第二级有一个规则,该规则用于对host
标签的值进行分组,以便可以将属于该主机的所有度量显示在其下方的分支中。最终级别具有度量标准规则,该度量标准规则包含一个分隔符,用于进一步按包含的数据对时间序列进行分组。由于我们有多个 CPU 和应用程序 Metrics,所有 Metrics 均以句点来定义,因此在此时添加分隔符是有意义的。
Result
生成的树如下所示:
-
dal
-
web01.dal.mysite.com
-
app
-
connections (tsuid=010101)
- errors \(tsuid=0101010306\)
- cpu
-
system (tsuid=0102040101)
- user \(tsuid=0202040101\)
- web02.dal.mysite.com
-
cpu
-
system (tsuid=0102040102)
- user \(tsuid=0202040102\)
- web03.dal.mysite.com
-
cpu
-
system (tsuid=0102040103)
-
lax
-
web01.lax.mysite.com
-
cpu
-
system (tsuid=0102050101)
- web02.lax.mysite.com
-
cpu
-
system (tsuid=0102050102)