尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Grafana Tempo TraceQL 语言设计解析:Spanset、管道与结构运算符

Grafana Tempo TraceQL 语言设计解析:Spanset、管道与结构运算符 Grafana Tempo TraceQL 语言设计解析Spanset、管道与结构运算符【免费下载链接】tempoGrafana Tempo is a high volume, minimal dependency distributed tracing backend.项目地址: https://gitcode.com/GitHub_Trending/tempo1/tempoTraceQL 是 Grafana Tempo 为选择 trace而设计的专用查询语言。本文以仓库中的设计文档 2022-04 TraceQL Concepts 为核心骨架结合 2023-11 TraceQL Extensions 与 pkg/traceql 下的引擎源码系统讲解 TraceQL 的核心概念spanset 选择、intrinsic 与 attribute 字段、逻辑/结构运算符、聚合器、管道与分组。读完本文你将理解 TraceQL 查询的求值模型逐 trace 求值、spanset 缩减掌握从选单个 span到对 span 集合做聚合与分组的完整语法并能在 Tempo 中直接写出可用的 TraceQL 查询。说明本文对应的设计文档是 Tempo 2.0 引入 TraceQL 前的概念提案文档中的示例大量使用当时尚未加作用域前缀的遗留语法如.http.status、duration。这些写法在语言演进中保留为向后兼容的 legacy 形式本文在保留原文档示例的同时会给出当前仓库文档所用的等价写法方便你在真实环境中验证。一、TraceQL 的能力边界能查什么设计文档在 Capabilities 一节明确给出 TraceQL 查询可以基于三类信息选择 tracespan 属性attributes、时间与持续时间timing and durationspan 之间的结构关系structural relationships对单个 trace 内 span 集合的聚合数据aggregated data。设计文档同时声明了一个重要原则TraceQL 在设计上尽可能复用 PromQL 与 LogQL 的语法和语义因为二者的用户基数大、心智模型成熟但由于 trace 数据是树状结构有根、分支、叶子任意位置的键值对以及时间戳TraceQL 的语法与语义必然因查询 trace这一专门需求而有所不同。当前官方文档 TraceQL 总览 也确认了这一点TraceQL uses similar syntax and semantics as PromQL and LogQL, where possible.需要强调的是这份设计文档不是完整的语言规范而是向社区征求意见的概念框架。后续的语言细化工作由 2023-11 TraceQL Extensions 等提案接力完成本文末尾会作补充介绍。二、查询结构一次只求值一个 trace 的管道表达式设计文档给出 TraceQL 最核心的结构定义A query is an expression that is evaluated on one trace at a time. The query is structured as a set of chained expressions (a pipeline).即一条查询是对单个 trace 依次求值的表达式整体被组织为一系列链式表达式管道 pipeline。每个管道表达式都会从结果集中选择或丢弃 spanset。文档给出的原型示例为{ .http.status 200 } | by(.namespace) | count() 3求值语义可以概括为花括号{}从当前 trace 中选出一组 spanspanset管道符|把上一级产生的 spanset 送入下一级表达式by(.namespace)做分组、count() 3做聚合过滤如果某个 trace 经过整条管道求值后产出了一个 spanset那么这个 spanset连同其所在的 trace就进入查询的结果集否则该 trace 被丢弃。从当前源码看这一管道模型被原样保留并实现。pkg/traceql/ast.go 中定义了PipelineElement接口任何管道元素都必须实现两个方法type PipelineElement interface { Element extractConditions(request *FetchSpansRequest) evaluate([]*Spanset) ([]*Spanset, error) }extractConditions把查询中可下推的条件拍平成存储层可执行的扁平条件fetch spans requestevaluate则在内存中对 spanset 做真正的求值——这正是 architecture 文档 所描述的引擎职责Parses incoming requests and extract flattened conditions the storage layer can work with; Pulls spansets from the storage layer and revalidates that the query matches each span.注意该文档已被标记为 out of date 并隐藏仅供理解引擎的分工。三、选择 span花括号与条件在 TraceQL 中花括号{}永远表示从当前 trace 选择一组 span通常与一个条件配对使用来缩减传入的 span{ .http.status 200 }这条最简单的查询会对每个 trace 的每个 span 逐一求值如果被求值的 trace 中没有任何 span 带有值为200的http.status属性则没有 span 被选中该 trace 不会出现在结果集中如果 trace 中确实存在这样的 span则只有这些匹配的 span 会被返回即 trace 被缩减为满足花括号内条件的 span 子集结果集只包含这个子集。四、字段类型Intrinsic 字段与 Attribute 字段设计文档将 span 上可引用的字段分为两大类这一划分在当前的 construct-traceql-queries 官方指南中仍然是基础概念。4.1 Intrinsic 字段固有字段每个 span 都有一些与生俱来的固有字段设计文档给出如下表格字段名说明durationspan 的结束时间减开始时间end - startname操作名或 span 名operation or span namestatus状态值取值 error、ok 或 unsetparent当前 span 的父 span示例{ duration 2s } // 查找包含持续时间超过 2 秒的 span 的 trace { name HTTP POST } // 查找包含名为 HTTP POST 的 span 的 trace需要说明的是设计文档中的duration、name、status属于 legacy遗留写法在 TraceQL Extensions 设计文档 中被称为 Legacy Intrinsics官方承诺当前没有移除计划但所有新 intrinsic 只提供带作用域前缀的形式。等价的作用域写法是span:duration、span:name、span:status例如{ span:duration 2s } { span:name HTTP POST }4.2 Attribute 字段动态属性除了 intrinsic还可以引用 span 或 span 所属 resource 上的动态属性即通常所说的 tag。设计文档示例{ .http.method GET } // 查找使用 GET HTTP 方法的 trace { .namespace prod } // 查找经过 prod 命名空间的 trace { parent.service.name ! .service.name } // 查找跨服务边界的 trace其中第三条示例parent.service.name ! .service.name展示了 attribute 与parentintrinsic 的组合用法当某个 span 的服务名与其父 span 的服务名不同就说明 trace 在这里跨越了服务边界。同样.前缀是遗留写法默认指 span 属性当前推荐使用显式作用域见下一节。4.3 作用域属性Scoped attribute fields设计文档特别强调属性可以被显式限定为span作用域或resource作用域这样做可以带来显著的性能收益result in significant performance benefits因为 Tempo 只需要扫描你关心的那部分数据{ span.http.status 200 } { resource.namespace prod }官方文档 construct-traceql-queries 对此给出了更完整的落地方案attribute 以.分隔作用域与字段名span.http、resource.namespace、event.exception.message、link.opentracing.ref_type、instrumentation.language而 intrinsic 用:分隔span:name。当前仓库支持的五种 attribute 作用域为span、resource、event、link、instrumentation scope。4.4 字段表达式Field expressions字段之间还可以按预期方式组合成表达式。设计文档给出两个示例{ parent.duration - duration 500ms } // 父子 span 持续时间之差超过 500ms { .http.status 200 .http.status 300 } // 成功的 HTTP 状态码设计文档对第二条注释了一句关键语义花括号内的整个表达式必须在单个 span 上求值为真该 span 才会进入结果集——两个条件必须同时命中同一个 span而不是分散在不同 span 上。这一同 span语义是理解 TraceQL 与后面组合 spanset跨 span 逻辑之间差异的基石。五、组合 Spanset逻辑运算符与结构运算符5.1 逻辑运算符设计文档指出逻辑运算符用于组合多个 span 集合。例如要找到一条同时经过两个特定 region 的 trace{ .region eu-west-0 } { .region eu-west-1 }注意它与下面这条的本质区别{ .region eu-west-0 .region eu-west-1 }第二条不会返回任何 trace——因为单个 span 不可能同时把region属性既设为eu-west-0又设为eu-west-1。前者是两个 spanset 之间的与trace 中至少有一个 span 匹配左边、且至少有一个 span 匹配右边后者是单 span 内的与。当前仓库文档对与||的表述为{condA} {condB}检查两个条件都找到匹配{condA} || {condB}作为并集OR检查任一条件找到匹配。5.2 结构运算符Structural Operators结构运算符基于 span 树中的 span 关系来评估 trace这是 TraceQL 区别于 PromQL/LogQL 的核心特色。设计文档定义了三个原型运算符运算符名称语义{ } { }后代运算符descendant返回匹配右侧条件的 span且这些 span 是匹配左侧条件 span 的后代{ } { }子运算符child返回匹配右侧条件的 span且这些 span 是匹配左侧条件 span 的直接子节点{ } ~ { }兄弟运算符sibling返回匹配右侧条件的 span且这些 span 与匹配左侧条件的 span 互为兄弟当前仓库在 pkg/traceql 的运算符枚举与 官方文档 中已把结构运算符扩展到完整矩阵后代、祖先、子、父、兄弟~以及对应的否定形式!、!、!、!、!~标记为 experimental可能产生误报和并集形式、、、、~同时返回两侧匹配的 span。官方文档同时给出一个重要补充规则结构运算符总是返回运算符右侧匹配的 span。设计文档中关于结构运算符的示例没有给出具体查询但当前官方文档提供了可直接验证的实例{ resource.service.namefrontend } { status error } // frontend 或其后代服务中出现错误 { resource.service.name productcatalogservice } ~ { resource.service.namefrontend } // 两服务互为兄弟 { } ! { resource.service.name foo } // foo 服务中的叶子 span六、聚合器Aggregators前面所有表达式都在回答单个 span的问题当需要针对一组 span提问时就要使用聚合函数。设计文档给出两个核心示例count() 10 // 查找 span 总数大于 10 的 trace avg(duration) 1s // 查找平均持续时间大于 1 秒的 trace当前仓库的官方文档把聚合器扩充为五类countspanset 中的 span 数、avg数值型属性或 intrinsic 的平均值、max最大值、min最小值、sum总和。一个官方文档示例count() 10 avg(span:duration) 20ms { } | sum(span.bytesProcessed) 1000000000 // 假设的 bytesProcessed 属性总和超过 1GB七、表达式管道Expression Pipelining管道|允许把一个表达式产生的 span 集合灌入下一个表达式这在希望对 trace 的某个子集做聚合时特别有用。设计文档的经典示例{ .http.status 200 } | count() 3含义先筛选出所有http.status 200的 span再统计这批 span 的数量——查找拥有超过 3 个 200 状态 span的 trace。注意如果没有管道count()是作用在整个 trace 的全部 span 上的有了管道聚合就被限制在花括号筛选出的子集内。这是管道与单纯聚合的核心区别。八、分组Grouping分组允许把一条 trace 拆成若干 span 集合交给后续管道条目逐一独立求值。设计文档强调Each set of spans created by the group isindividuallyevaluated by downstream expressions.by(.region) | count() 5含义按region属性把 trace 内的 span 分组然后每个分组各自统计数量——查找在任意一个 region 中都有超过 5 个 span的 trace。官方文档的同类示例单服务出现多个错误{ status error } | by(resource.service.name) | count() 1九、设计文档示例全集设计文档在 Examples 一节给出了十组经过精心设计、由浅入深的完整示例覆盖前面所有概念逐条完整列出如下任何 span 匹配某属性{ .namespace prod }两个属性出现在同一 span 上{ .namespace prod .http.status 200 }两个属性出现在 trace 内任意位置可不同 span{ .namespace prod } { .http.status 200 }任何 span 持续时间超过 1 秒{ duration 1s }trace 整体持续时间超过 1 秒利用聚合表达式max(end) - min(start) 1sspan 平均持续时间超过 1 秒且存在带某属性的 spanavg(duration) 1s { .namespace prod }一条 trace 在任意命名空间内拥有超过 5 个 http.status200 的 span分组管道聚合的组合{ .http.status 200 } | by(.namespace) | count() 5trace 按特定顺序经过两个 region结构运算符{ .region eu-west-0 } { .region eu-west-1 }trace 在任意两个服务之间传递时出现超过 1 秒的网络延迟{ parent.service.name ! .service.name } | max(parent.duration - duration) 1s这组示例的价值在于它们从单 span 过滤逐步过渡到聚合、分组、管道、结构关系恰好构成了 TraceQL 语言能力的完整演示路径。当前官方文档 construct-traceql-queries 还补充了针对具体场景的写法例如按操作名与服务名过滤{resource.service.name frontend name POST /api/orders}以及使用trace:durationintrinsic比聚合表达式更快{ trace:duration 5s }十、从设计到实现引擎源码佐证设计文档提出的概念在当前仓库的 pkg/traceql 包中有着完整对应实现编译入口pkg/traceql/engine.go 的Compile函数依次完成Parse词法/语法解析、expr.validate()语义校验、expr.SinglePipeline()提取单管道、expr.extractConditions(req)抽取可下推条件返回FetchSpansRequest最终得到求值函数p.evaluate。设计文档中把查询翻译成存储层可执行的扁平条件的思想在此落地为FetchSpansRequest。求值执行pkg/traceql/engine.go 的ExecuteSearch接收SearchRequest与SpansetFetcher编译查询后从存储层拉取 spanset 并逐 span 重新校验匹配与设计文档Pulls spansets from the storage layer and revalidates的描述一致。管道元素抽象pkg/traceql/ast.go 的PipelineElement接口统一了所有管道元素的条件抽取 求值两个阶段pkg/traceql/ast.go 的NeedsFullTrace会识别哪些元素如SpansetOperation结构运算符、Aggregate聚合器必须拿到完整 trace 才能求值——这正是结构运算符需要整棵树、而简单属性过滤可以下推存储层的原因。指标类管道pkg/traceql/ast_metrics.go 实现了rate() by (...)、topk()等 trace 指标化管道元素对应设计文档 Summary 中未来将从 trace 推导指标的展望——该展望已由 TraceQL metrics 功能落地。十一、语言演进TraceQL Extensions 补充设计文档开篇就声明这不是完整语言规范后续细化由 2023-11 TraceQL Extensions 承接该提案为语言补充了四类能力可作为理解概念文档的延伸属性名转义允许用双引号包裹属性名以支持空格、数学符号等 Unicode 字符如{ span.attribute with spaces foo }并支持\与\\两种转义序列新增作用域trace{ trace:duration 100ms }、instrumentationscope{ scope:name ~ .*Java.* }、event{ event.exception.message ~ .*Division by zero.* }、link{ link:traceID hex string }其中 event/link 由于一个 span 可有多个事件/链接设计上刻意保守作用域化 intrinsic统一用:分隔作用域与 intrinsic如span:name是 intrinsicspan.name是同名属性并给出完整的 intrinsic 表trace:duration、trace:rootName、span:childCount、event:name、link:traceID、parent:id等设计文档中的 legacy intrinsicduration、name、status继续保留新数据类型数组用[0]访问指定元素、用[]匹配任意元素与 ID 类型如{ span.id 8bf5306cb6a28 }只支持/!比较时忽略前导 0。十二、在哪里运行 TraceQL设计文档只定义语言概念实际使用方式在当前官方文档中有明确说明TraceQL 总览命令行通过 Tempo 的 API 以搜索请求方式执行Grafana在 Tempo 数据源的 Explore 查询编辑器/查询构建器中使用 TraceQL前置条件TraceQL 依赖 Parquet 列式存储格式Tempo 的默认块格式相关说明见 Apache Parquet 后端 文档。结语TraceQL Concepts 设计文档用极简的概念集合spanset、花括号选择、intrinsic/attribute 字段、逻辑与结构运算符、聚合、管道、分组定义了 Tempo 查询语言的全部骨架而 pkg/traceql 的源码与 TraceQL Extensions 提案把这一骨架细化成了可运行、可扩展的完整语言。理解对单个 trace 逐条求值、以 spanset 为单位流转、结构运算符永远返回右侧匹配这三条核心语义就能举一反三地构造出从简单属性过滤到跨服务结构分析再到 trace 指标化的各种查询。【免费下载链接】tempoGrafana Tempo is a high volume, minimal dependency distributed tracing backend.项目地址: https://gitcode.com/GitHub_Trending/tempo1/tempo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表