OpenTelemetry Tracing 思维导图,收藏

Flashcat 2024年2月26日

相较于传统的单体应用,以及过去相对静态化的基础设施,现代的应用架构,是一种松耦合的、动态变化的、数量巨大的微服务构成的网络。为了看清楚网络中众多不同的服务之间的依赖关系,以及看清楚一次请求经过的路径上各个节点之间的耗时等信息,传统监控,已经无力应对了。这个网络的每个节点,都有可能是出问题的风险点,tracing 能够追踪每个请求在全生命周期过程中所经过的每个节点的信息,成为了云原生时代和微服务架构下构建可观测体系的关键一环。

Distributed Tracing 中最核心的数据结构是 Span,下面是一些 OTel 中关于 tracing span 相关的关键概念和字段:

  1. span name:典型的比如 RPC method、function name、子任务的名称等等,一个好的span name,既要有一定的表意性、可读性,也要注意颗粒度,比如 /account/12345 作为span name,颗粒度会太细,对后端的trace系统会造成压力,也使得聚合分析比较困难;相应的 /account/ 或者 /account/{account_id} 就是一个合适的颗粒度。
  2. span context:由 TraceId、SpanId、TraceFlags、TraceState、IsRemote 构成。
    • TraceId:全局唯一的ID,用于唯一标识某个请求,TraceId 一般由 root span 来生成,然后该 ID 会在透传到该请求所经历的每个span,16个字节长度。
    • SpanId:代表 traces 中的一个单个操作(Operation),SpanId 用于唯一标识某个 Span,8个字节长度。
    • TraceFlags:目前主要是用于设置和控制采样率和采样方法。
    • TraceState:key-value 对,记录span的一些额外状态信息。
    • IsRemote:bool值,用于标识该span context 是从上游传递过来的,还是本地生成的。
  3. parent span:span 与 span 之间构成了一个有向无环图,每一个 span 都有一个或者零个 parent span(注意 root span 的父节点为空),一个 span 可以拥有零个或者多个 child span。通过 span 间的父子关系,最终构成了完整的 trace。
  4. spanKind:用于描述span和span之间的关系,比如是否是remote,还是synchronous,还是asynchronous。SpanKind的值为:CLIENT、SERVER、PRODUCER、CONSUMER、INTERNAL。
  5. start timestamp:每一个 span 都有一个时间戳,来标识该 span 开始的时间。
  6. end timestamp:每一个 span 都有一个字段记录该 span 结束的时间。
  7. attributes:key-value 对。
  8. events 列表:在span 中可以记录一些有用的事件,比如记录某个Exception。
  9. status:由 StatusCode 和 Description 构成,StatusCode可选值为:Unset、Ok、Error。

注意,OTel tracing spec 中定义的 API 数据结构和开源 tracing 工具 Jaeger 的数据结构,有一定的差异,下面是相关的映射表:

OTel tracing api vs jaeger

关于如何在span中记录系统抛出的异常,OTel 中也有相关的规范,首先,Exception 是被作为一种典型的事件Event 来对待的。在对 Span 的定义中,OTel 要求Span需要实现一个RecordException()的函数,并添加到Span.Events中。

Tracing 不仅仅可以追踪请求经过每一次rpc调用或者本地函数调用时的现场情况,对于函数内部的“热点代码块”(比如典型的JSON序列化和反序列化),也推荐创建对应的Span来记录该代码块的执行情况,可以大大方便分析代码性能瓶颈。

对每一个请求进行tracing会带来计算、存储上的开销,同时对生产环境也会造成额外的压力,推荐开启“降采样”策略,可选的策略有:固定比率的采样策略、自适应动态采样策略、基于特征的多采样策略等等。“降采样”的动作,可以作用于trace生命周期的不同阶段,比如可以在trace生成的时候,直接降采样,也可以在trace被发送到后端的存储系统前降采样。

OTel 中,定义了两个字段用于控制“降采样(Sampling)”,分别是 span 的 IsRecording 属性和 SpanContext 的 TraceFlags 属性。这两个属性共同作用,控制一个 trace 或者 span 的降采样动作和时机:

OTel 中,定义了几种内建的采样方法:

其中 JaegerRemoteSampler 有个很有用的特性,支持在中心端配置“降采样”策略,SDK动态感知和生效,能够大大提高采样策略管理上的灵活性。此外,设置“降采样”策略,可以对某个service设定全局统一的策略,也可以针对service的不同的方法(即不同的span name)设置个性化的策略。

Tracing的构建需要涉及到对代码做侵入性的改动,比如需要埋点相关的SDK,这个投入比较大,周期也比较久。我们可以基于公司已有的Logging技术栈实现一个tracing的过渡方案。首先将非结构化的纯文本日志格式,改造为结构化的日志(可以是JSON结构),其次在日志中添加实现tracing功能必须的字段,包括(trace_id、span_id、parent_id、timestamp、duration),最后对采集到的日志,进行实时的分析和统计,以trace_id 和 span_id 重建整个调用链路的拓扑图。

结合日志中记录的trace_id,就可以实现从 tracing 到 logging 的串联打通。从 metrics 打通 tracing,一般是通过 service、operation 这两个维度来打通,即:metrics 数据里面要包含 service 和 operation 这两个标签,然后去 trace 系统里面,按照 service + operation 组合来搜索匹配对应的 traces。

OTel Logs 思维导图整体如下,供参考:

关于快猫星云和夜莺

夜莺 (Nightingale) 是一款开源云原生监控工具,是中国计算机学会接受捐赠并托管的第一个开源项目,在GitHub上有8000颗星,有数千家企业用户使用。快猫星云以开源夜莺为内核打造的“Flashcat平台”,是国内顶级互联⽹公司可观测性实践的产品化落地,我们致力于让可观测性技术更好的落地和发挥价值。

你可以通过Flashcat平台,有效改善以下问题:

  1. 希望整个公司统一用一个工具,就可以支持指标、日志、链路追踪数据的采集、可视化、告警,免去搭建和维护多套 Prometheus、Zabbix、Grafana、ELK、Jaeger 的工作量。
  2. 如果有在用多云,并且在多个公有云监控控制台来回切换不方便,希望监控数据、监控视图都是统一的,有更一致的用户体验,同时降低给所有的工程师开通公有云控制台权限带来的安全隐患。
  3. 告警太多,工作老被打断, 可以利用我们提供的 OnCall 值班平台(类似于 PagerDuty),支持告警聚合、降噪、认领、升级、排班,可以在飞书、钉钉、企微中接收和处理告警。
开源版
Flashcat
Flashduty