“
应用的稳定性,以及出现问题的时候,怎样的快速定位到真正的原因,对于很多企业来说是都是一直在不断的设计和完善的能力。主要体现在怎样监控系统,怎样从日志上快速的找到错误,怎样快速的知道调用链是不是出现了问题,以及应用的运行时有没有出问题,应用依赖的数据库,中间件等是不是出现问题了。这也是大家经常听到的观测性的大三要素,监控,日志,链路。随着云原生的技术的发展,社区也在重新思考一样这些问题,也在不断的提出新的标准。接下来,我们就来介绍介绍 OpenTelemetry 这个技术。
观测性常见方案主要总结:
日志方案:filebeats->elasticsearch->kibana;filebeats->kafka->logstash->elasticsearch->kibana; fluentd->elasticsearch->kibana;fluentd->kafka->fluentd->elasticsearch->kibana 。
监控方案:比较统一的都在使用 prometheus,prometheus-operator,thanos,grafana。
链路方案:主要方案有开源的 skywalking,jaeger,zipkin,还有商业的方案,如 dynatrace 等。
现阶段,我们又是怎样来统一?
答案是,不是很统一(商业方案不确定)。各个领域专注的点不一样。举例来说,我们可以以关键业务模型的 ID,Name 等作为串起三个技术方便的入口。在日志里注入业务 ID;在监控的数据里,注入业务 ID;在 trace 里通过规范化的方式注入业务 ID。这样在某一个业务对象在系统中出现问题的时候,就可以根据业务 ID,可以相对统一的方式拿到三大观测领域的相关数据。
OpenTelemetry 怎样解决这个问题?
用了 OpenTelemetry,我还需要再规划业务 ID?其实可以不用,三大观测领域中,需要有一个来承担起牵头作用,谁越靠近应用就应该是谁来承担这个责任,答案就是 Trace。在 OpenTelemetry 里串起三大观测能力的核心点就是 trace id 和 span id。了解过Trace,应该知道 trace id 就是代表一次业务处理的全局的可追踪的上下文 ID,所以 OpenTelemetry 的方案就是在 metrics 里,在 log 里增加了 trace id 和span id 字段。这样只需要根据告警,以及告警信息里嵌入的 trace id 和 span id (来源于告警规则里嵌入 metrics 的名为 trace id 的 lable,以及对应的 value),就可以很快的拿到出现问题的那一刻的准确的全部的观测数据。
现阶段,怎样解决的?
怎样知道在排查问题的时候,确定问题发生的事件?即便有告警辅助,业务和观测系统的各自处理部分都是需要时间,怎样就能确定具体时间?正常情况是知道大概是什么时间出现的问题,即便是这样,想要快速定位这个问题,还是需要在精确的时间里整合出三大观测的数据,此时,能做的就是取一个相对时间范围小一点的,如根据告警的左右十分钟,或者左右半小时等等。要通过肉眼去识别最后需要的日志,还是会花时间,这个时间和参与排查问题的人的经验也是相关的,这种情况是比较规范和完美的玩法,对于一些团队没有规划这些方案的团队,那就更难在很短时间排查出问题,最后就是先保存一下日志,先重启服务或者重启机器先解决了再说,这个不是我们期望的解决问题的方式。
OpenTelemetry 怎样解决这个问题?
在同一时刻通过 trace id 和 span id 将三大能力串起来。每一个部分都需要有时间,如监控是时序数据库,模式就带时间字段;如日志,日志框架都有时间这个字段;如链路,链路也都是有时间字段的。因为三者在采集的时候,每个模块的执行时机有先后顺序,但是可以保证的是这个时间范围肯定是在这次请求过程中,这样就可以将时间范围缩短到一个很短的时间范围内,来达到精确的定位问题。
根因分析需要知道真正出现问题的地方,不仅仅是应用本身。在这个过程中,链路分析是最贴近的,因为链路的工具包中有内置应用依赖的基础设施的访问链路的分析。定位到应用依赖的基础设施的位置了,接下来前提条件就是被依赖的基础设施本身有监控和告警的能力。经过关系查找到基础设施的监控数据进行展示和分析。但是基础设施的监控数据也许会比较多,那怎样快速的找到问题,这就需要在建设基础设施的监控和告警能力的时候,规划好最常见问题的监控能力,这种能力的建设也是在关联分析中比较重要也是比较有难度的部分,这些数据的准确程度都是取决经验来建设,减少不必要的监控数据和告警设置,防止有用的告警和监控数据被掩盖。
观测性相关的工具只是提供了一些工具。如果要建设业务观测能力,需要根据需要观测的具体业务来关联观测数据。
和业务最相关的,最贴近的是业务的日志,这种方式也是成本最小的方式。对业务逻辑的程序中需要考虑到业务可运维能力,所以需要在需要观测的业务逻辑的日志里增加业务相关字段,包括当前操作所属于具体的业务,这样可以根据固定的关键业务名称快速定位到相关错误日志,以及配合日志告警,这样可以在出现问题的时候,第一时间通过告警的方式知道,以及再根据 logs 里带有的 trace 的相关信息,快速定位具体业务的链路信息。
建设业务相关的监控能力,针对重要的业务需要程序化的方式提供监控能力,举例如业务 A 的访问频率和错误数,这样可以在出现问题的时候,第一时间通过告警的方式知道,以及再根据 metrics 里带有的 trace 的相关信息,快速定位具体业务的链路信息。
对需要支持观测能力的重要业务,进行业务名称的定义和管理,映射业务名称到日志的查询语句,以及映射业务名称到 metrics 的指标查询语句或对应指标的快速查看入口。
OpenTelemetry 是以解决观测性为初衷的项目,观测性包含链路,监控,日志。主要解决的问题是观测性领域模型的统一,观测性数据收集的统一,观测性数据输出的统一。这些统一性主要体现在 API 规范,SDK 实现规范,接口规范等。OpenTelemetry 不会负责观测数据的存储,需要存储这些观测数据的 backend。OpenTelemetry 定义来数据输出的规范,由各大厂商自行完成数据的持久化。
OpenTelemetry 也提供了很多开发语言的 SDK,开发者需要使用自己熟悉的语言的 SDK 完成应用程序的观测性能力。目前不同的开发语言的支持程度不一样。
主要组成:
Specification:这个组件是语言无关的,主要是定义了规范,如 API 的规范,SDK 的开发规范,数据规范。使用不同开发源于开发具体 SDK 的时候会按照标准开发,保证了规范性。
Proto:这个组件是语言无关的,主要是定义了 OpenTelemetry 的 OTLP 协议定义,OTLP 协议是 OpenTelemetry 中数据传输的重要部分。如 SDK 到Collector,Collector 到 Collector,Collector 到 Backend这些过程的数据传输都是遵循了 OTLP 协议。
Instrumentation Libraries:是根据 SDK 的开发规范开发的支持不同语言的 SDK,如 java,golang,c 等语言的 SDK。客户在构建观测性的时候,可以直接使用社区提供的已经开发好的 SDK 来构建观测能力。社区也在此基础上提供了一些工具,这些工具已经集成了常见软件的对接。
Collector:负责收集观测数据,处理观测数据,导出观测数据。
架构介绍:
Application: 一般的应用程序,同时使用了 OpenTelemetry 的 Library (实现了 API 的 SDK)。
OTel Libraty:也称为 SDK,负责在客户端程序里采集观测数据,包括 metrics,traces,logs,对观测数据进行处理,之后观测数据按照 exporter 的不同方式,通过 OTLP 方式发送到 Collector 或者直接发送到 Backend 中。
OTel Collector:负责根据 OpenTelemetry 的协议收集数据的组件,以及将观测数据导出到外部系统。这里的协议指的是 OTLP (OpenTelemetry Protocol)。不同的提供商要想能让观测数据持久化到自己的产品里,需要按照 OpenTelemetry 的标准 exporter 的协议接受数据和存储数据。同时社区已经提供了常见开源软件的输出能力,如 Prometheus,Jaeger,Kafka,zipkin 等。图中看到的不同的颜色的 Collector,Agent Collector 是单机的部署方式,每一个机器或者容器使用一个,避免大规模的 Application 直接连接 Service Collector;Service Collector 是可以多副本部署的,可以根据负载进行扩容。
Backend: 负责持久化观测数据,Collector 本身不会去负责持久化观测数据,需要外部系统提供,在 Collector 的 exporter 部分,需要将 OTLP 的数据格式转换成 Backend 能识别的数据格式。目前社区的已经集成的厂商非常多,除了上述的开源的,常见的厂商包括 AWS,阿里,Azure,Datadog,Dynatrace,Google,Splunk,VMWare 等都实现了 Collector 的 exporter 能力。
由于 Trace 是 OpenTelemetry 中,比较成熟的部分,下面以 opentelemetry-go 这个 SDK,来分析一下 Trace 部分的基本工作原理。对 Trace 本身的数据结构和知识点不在本篇文章中详解。
1. 开始一个 span。
2. 当客户的 App 要想记录一次 trace,需要在 code 里 call 这个 Start 方法,这个方法最后会返回一个 span 出来,后续 span 可以调用 span 的 End 接收这次的 trace。
3. 开始真正的初始化 span,保存 span 的 TraceID,SpanID,以及 span 的attributes,events,links,以及处理 span 限制的 spanLimits,以及 span 的 startTime,span的parent span,span 的 n ame 等一个 span 需要的相关属性。
4. 初始化处理 span 的所有类型的 processors,根据定义的 processor 去处理 span,目前只支持 simple 和 batch 两种,simple 是不推荐生产,因为是同步的 call,而且是一条一条的生产上推荐使用 batch,性能好。
5. 根据配置的所有 span 的 processors,使用每一个 processor 的 OnStart 的去处理 span,这里的 OnStart 其实是空实现。不管是 simple 类型的,还是 batch 类型的,目前都是一个是个空的方法,这个方法的定义的作用就是在开始处理 span 的时候,做一些处理。最后返回一个初始化后的 span 给到客户 (注意,要想让这个 span 真正的进入到整个 trace 里,还要调用 span 对象的 End 方)。
6. 由 tracer 返回开始的 span。
7. 当 call span 的 End 方法时候就会对 span 做一下快照,这里的快照只是将可读写的 span,复制到只读的 ReadOnlySpan 对象,通过 processor 的 OnEnd 方法放到 processor 的 queue 中,可见放到 queue 中等待发送出去的 span 都是只读的 sp.sp.OnEnd(s.snapshot()))。
8. 从 queue 获取 span 放到 batch 中,当 batch 的数量满了,或者到达了固定时间,都会将已经处于 end 状态的 span 通过 exporter 发送出去。
9. 从 queue 获取 span 放到 batch 中,当 batch 的数量满了,或者到达了固定时间,都会将已经处于 end 状态的 span 通过 processor 的 exportSpans 方法发送出去。
10. exportSpans 方法处理,只会去拿 processor 的 batch 字段的数据去处理,每次 exportSpans 时都是一次处理一个 batch 的数据。
11. 使用一个 batch 里的数据,进行转换成 collector 能接受的 trace 数据。
12. 使用 exporter 的 client,upload trace 数据到 collector。
13. 循环所有的 trace 数据,封装成 tracepb.Span, 由 Spans 方法再封装成 tracepb.ResourceSpans。
14. 定义一个 traces 发送的请求。
15. 进行发送 traces。
16. 在发送 traces 的时候,在 send 中 call singleSend 去发送数据,会有失败重试。
17. 使用 http 或者 grpc 的client,发送 tracepb.ResourceSpans 数据到 collector。
18. 将 batch 的数据处理完之后,清空一下 batch 字段,用于继续从 queue里面拿 span,用于下一个发送时机要去处理的 span 数据。
以 golang 的 SDK 的案例代码
Metrics 是业务系统监控所必须的工具。从使用者的角度,更加关心有哪些监控数据采集类型和场景。以下介绍 OpenTelemetry 中规范的几种类型以及每种类型的一些特性,来帮助理解。(Metrics 在 OpenTelemetry 中的实现部分是三大块中最复杂,篇幅有限,原理部分不在当前这篇文章中详细介绍)
Counter :递增的计数器。
UpDownCounter:可增可减的计数器。
Histogram:统计一组数据,如直方图。
CounterObserver:异步方式的递增计数器。
UpDownCounterObserver:异步方式的可增可减计数器。
GaugeObserver:异步的方式观测最新数据的计量器。
6.2. 聚合器类型总结
Sum:类型匹配到 Counter,UpDownCounter,CounterObserver,UpDownCounterObserver。这类聚合器用于对数据做加法。
LastValue:类型匹配到 GaugeObserver。这类聚合器用于根据数据的时间,计算出最新时间点的数据。
Histogram:类型匹配到 Histogram。这类聚合器用于计算一组 Points 中 sum,count,buckets。
MinMaxSumCount:类型匹配到 Histogram。这类聚合器用于计算 sum,count,min,max。
Exact:类型匹配到 Histogram。这类聚合器虽然处理的也是一组 Points,但是不做任何计算,只是保存这些 Points。
备注:有三个聚合器都对应到了 Histogram Instrument,根据期望的分布统计选择对应的策略。NewWithHistogramDistribution 对应选择 Histogram 聚合器作为 Histogram Instrument 的聚合器 (默认推荐);NewWithExactDistribution 对应选择 Exact 聚合器作为 Histogram Instrument 的聚合器;NewWithInexpensiveDistribution 对应选择MinMaxSumCount 聚合器作为 Histogram Instrument 的聚合器;
6.3. 同步/异步的区别
同步:类型匹配到 Counter,UpDownCounter,Histogram。由应用程序每次主动指定 Instrument 的监控数据,随着每次程序的执行而执行。如一次请求处理,在请求处理中设置请求次数。
异步:类型匹配到 CounterObserver,UpDownCounterObserver,GaugeObserver。由 SDK 每次的收集周期执行一次异步类型 Instrument 设置的回掉函数,在回掉函数中设置监控数据。如每次收集间隔是,统计一下程序的运行时间。
6.4. Adding 和 Grouping 的区别
Adding:类型匹配到 Counter,UpDownCounter,CounterObserver,UpDownCounterObserver。每次收集周期,Instrument 对应的聚合器关注于对同一个 Instrument 做加法。
Grouping:类型匹配到 Histogram,GaugeObserver。关注于一组数据的处理。如 GaugeObserver 对应的聚合器会保存 Points,Points 中保存着同一个 Instrument 在不同时间点采集的数据 (Point);Histogram 会计算一组Points 中的 sum,count,min,max,buckets,Points。
6.5. 对应的Prometheus类型
Counter :Counter
UpDownCounter:Gauge
Histogram:Histogram
CounterObserver:Counter
UpDownCounterObserver:Gauge
GaugeObserver:Gauge
6.6 代码案例
Log4j2 支持 Trace,引用 OpenTelemetry 提供的 jar 包,以及修改配置文件。
Logback 支持 Trace,引用 OpenTelemetry 提供的 jar 包,以及修改配置文件。
Golang logrus 支持 Trace,在 code 里手动注入 trace 的相关信息。
OpenTelemetry 自己的 log sdk,在 code 里手动注入 trace 的相关信息。
这里先引入一个概念,Exemplars。Exemplars 的作用是连接 metrics 和 trace 的关键,也就是在 metrics 的数据里注入 trace id 和 span id。这种方式目前已经实现的开源软件有 Prometheus,OpenMetrics。OpenTelemetry 引用 OpenMetrics 的设计,也使用一样的概念完成在 OpenTelemetry 中注入 trace id 和 span id 到 metrics,区别是 OpenMetrics只实现 Histogram Instrument 类型的 Exemplars,OpenTelemetry 将会实现所有 Instrument 类型的 Exemplars 能力。
opentelemetry-java 最新版本是 1.7.0,已经支持了默认支持了 Exemplars,但还是 alpha 版本。OTEL_METRICS_EXEMPLAR_FILTER 这个环境变量默认值是 WITH_SAMPLED_TRACE 作,会自动将需要 sampled (trace中需要被采样的span) 的 trace id 以及 span id 和 metrics 关联起来。
样例:
request_duration{path="/test",le="0.004"} 4.0 #{trace_id="043cd631811e373e4188e",span_id="cd122d2ca5b0"} 0.0033 1618261159.027
opentelemetry-go 目前版本暂时还没有实现这部分功能。
在使用工具的时候,都希望这个工具简单而且强大。
OpenTelemetry 的 java sdk 中,应用可以无需修改 code,只需要引用对应的 jar 包就可以完成对很多常见观测能力的集成。目前支持的集成已经比较广泛,包含常见的软件集成。主要包含,akka,camel,dubbo,httpclient,cassandra,couchbase,elasticsearch,finatra,geode,grails,grizzly,grpc,guava,get,hibernate,hystrix,jaxrs,jaxws,jdbc,jedis,jetty,jms,jsf,jsp,kafka,kubernetes,log4j,logback,mongo,netty,quartz,rabbitmq,reactor,redisson,rmi,rocketmq,scala,servlet,spark,spymemcached,struts,spring,tomcat,vertx等。使用了 OpenTelemetry 的 java 的 SDK,就可以自带这些软件的调用分析能力。
OpenTelemetry 的 golang sdk 中开始支持 http,grpc,beego,mongo,sarama (kakfa),host (程序所在主机的内存,网络,CPU使用),runtime (程序本身的资源使用,如 heap memory 的使用,gc 的情况),gocql,mux,gin,go-kit,go-restful,gomemcache 等,使用了 OpenTelemetry 的 go 的 sdk,就可以自带这些软件的调用分析能力。
collector 的 contrib,是各大厂商参与社区的主要方向之一, 其中对 receiver,processor,exporter 都进行了大量的扩展。
各种语言的 SDK 的 auto instrumentation,主要分布在各种语言 SDK 的 contrib 仓库中。使用了这些工具之后,可以具备更多自动化的能力,而不需要自己再基于 SDK 的基础能力去开发这些能力。举例,可以自动获取当前进程的运行状态等等。所以此类的能力也是社区参与度比较高的部分。
java 的 SDK 的版本是比较高的,因为目前企业还是 java 的系统偏多,其它版本低一点,但是 trace 部分也都发布了 1.0 以上的版本。
trace 的成熟度最高,基本可以商用。metrics 还在 alpha 阶段,最近还在变动 Instrument Kind,举例 ValueRecorder 改成了 Histogram。log 也是在 alpha 阶段,比 metrics 稍微落后一点,java 的 SDK 已经有 alpha 版本,但是 golang 的 SDK 中还没有任何 log 相关的实现。
OpenTelemetry 的出现,将会改变目前现有的观测能力的方案方向。重新定义云原生观测性能力。从目前的社区状态看,还有一些部分还不是很成熟,还需要一些时间。随着社区的发展,会有越来越多的 vendor 去适配,会有更多的观测能力方案和存储观测数据的方案集成到 OpenTelemetry 中。
最后,云原生交流群,欢迎加入参与交流互动: