在微服务架构中,一次请求往往涉及到多个模块,多个中间件,多台机器的相互协作才能完成。这一系列调用请求中,有些是串行的,有些是并行的,那么如何确定这个请求背后调用了哪些应用,哪些模块,哪些节点及调用的先后顺序?如何定位每个模块的性能问题?本文将为你揭晓答案。
衡量一个接口的性能好坏,一般我们至少会关注以下三个指标
在初期,公司刚起步的时候,可能多会采用如下单体架构,对于单体架构我们该用什么方式来计算以上三个指标呢?
最容易想到的显然是用 AOP:
在单体架构中由于所有的服务,组件都在一台机器上,所以相对来说这些监控指标比较容易实现,不过随着业务的快速发展,单体架构必然会朝微服务架构发展,如下:
如果有用户反馈某个页面很慢,我们知道这个页面的请求调用链是 A -----> C -----> B -----> D,此时如何定位可能是哪个模块引起的问题。每个服务 Service A,B,C,D 都有好几台机器。怎么知道某个请求调用了服务的具体哪台机器呢?
如图所示可发现微服务接口调用存在以下几个问题:
分布式调用链就是为了解决以上几个问题而生,它主要的作用如下:
通过分布式追踪系统能很好地定位如下请求的每条具体请求链路,从而轻易地实现请求链路追踪,每个模块的性能瓶颈定位与分析。
知道了分布式调用链的作用,那我们来看下如何实现分布式调用链的实现及原理, 首先为了解决不同的分布式追踪系统 API 不兼容的问题,诞生了 OpenTracing 规范,OpenTracing 是一个轻量级的标准化层,它位于应用程序/类库和追踪或日志分析程序之间。
这样 OpenTracing 通过提供平台无关,厂商无关的 API,使得开发人员能够方便地添加追踪系统的实现。
说到这大家是否想过 Java 中类似的实现?还记得 JDBC 吧,通过提供一套标准的接口让各个厂商去实现,程序员即可面对接口编程,不用关心具体的实现。这里的接口其实就是标准,所以制定一套标准非常重要,可以实现组件的可插拔。
接下来我们来看 OpenTracing 的数据模型,主要有以下三个
理解这三个概念非常重要,为了让大家更好地理解这三个概念,我特意画了一张图。
如图所示,一次下单的完整请求完整就是一个 Trace, 显然对于这个请求来说,必须要有一个全局标识来标识这一个请求,每一次调用就称为一个 Span,每一次调用都要带上全局的 TraceId, 这样才可把全局 TraceId 与每个调用关联起来,这个 TraceId 就是通过 SpanContext 传输的,既然要传输显然都要遵循协议来调用。如图示,我们把传输协议比作车,把 SpanContext 比作货,把 Span 比作路应该会更好理解一些。
理解了这三个概念,接下来我看看分布式追踪系统如何采集统一图中的微服务调用链。
我们可以看到底层有一个 Collector 一直在默默无闻地收集数据,那么每一次调用 Collector 会收集哪些信息呢。
有了这些信息,Collector 收集的每次调用的信息如下
于是一个完整的分布式追踪系统就实现了。
以上实现看起来确实简单,但有以下几个问题需要我们仔细思考一下
接下我来看看 SkyWalking 是如何解决以上四个问题的
目前市场上比较常见的链路追踪方案如下:
类别 | Zipkin | Pinpoint | skywalking | CAT | jaeger |
---|---|---|---|---|---|
实现方式 | 拦截请求,发送(http,mq)数据到zipkin服务 | java探针,字节码增强 | java探针,字节码增强 | 代码埋点(拦截器,注解,过滤器) | 拦截请求 |
主要支持语言 | Java | Java | Java/c/c++/python/GO/Node/PHP/.NET/nginx | Java/c/c++/python/GO/node | go |
接入方式 | 基于linkerd或者sleuth方式,引入配置即可 | java agent字节码 | java agent字节码 | 代码侵入 | 拦截侵入 |
agent到collector的协议 | http,mq | thrift | gRPC | http/tcp | udp/http |
openTracing | √ | × | √ | × | √ |
颗粒度 | 接口级别 | 方法级别 | 方法级别 | 代码级别 | 接口级别 |
全局调用统计 | × | √ | √ | √ | × |
traceId查询 | √ | × | √ | × | √ |
报警 | × | √ | √ | √ | × |
JVM监控 | × | × | √ | √ | × |
健壮度 | ** | ***** | **** | ***** | **** |
数据存储 | ES,mysql,Cassandra,内存 | Hbase | ES,H2 | mysql,hdfs | ES,kafka,cassandra,内存 |
社区活跃度 | 15.2k | 12.4k | 20.2k | 17.2k | 16.3k |
PinPoint和SkyWalking支持的插件对比
类别 | Pinpoint | Skywalking |
---|---|---|
web容器 | Tomcat6/7/8,Resin,Jetty,JBoss,Websphere | Tomcat7/8/9,Resin,Jetty |
JDBC | Oracle,mysql | Oracle,mysql,Sharding-JDBC |
消息中间件 | ActiveMQ,RabbitMQ | RocketMQ 4.x,Kafka |
日志 | log4j, Logback | log4j,log4j2, Logback |
HTTP库 | Apache HTTP Client,GoogleHttpClient,OkHttpClient | Apache HTTP Client, OkHttpClient,Feign |
Spring体系 | spring,springboot | spring,springboot,eureka,hystrix |
RPC框架 | Dubbo,Thrift | Dubbo,Motan,gRPC,ServiceComb |
NOSQL | Memcached, Redis, CASSANDRA | Memcached,Redis |
节点数据的定时采样,采样后将数据定时上报,将其存储到 ES, MySQL 等持久化层,有了数据自然而然可根据数据做可视化分析。
skywalking的工作机制,需要三块协同。工作原理图大致如下:
SkyWalking采用组件式开发,易于扩展,主要组件作用如下:
Skywalking Agent:链路数据采集tracing(调用链数据)和metric(指标)信息并上报,上报通过HTTP或者gRPC方式发送数据到Skywalking Collector
Skywalking Collector : 链路数据收集器,对agent传过来的tracing和metric数据进行整合分析通过Analysis Core模块处理并落入相关的数据存储中,同时会通过Query Core模块进行二次统计和监控告警
Storage: Skywalking的存储,支持以ElasticSearch、Mysql、TiDB、H2等主流存储作为存储介质进行数据存储,H2仅作为临时演示单机用。
SkyWalking UI: Web可视化平台,用来展示落地的数据,目前官方采纳了RocketBot作为SkyWalking的主UI
SkyWalking 采用了插件化 + javaagent 的形式来实现了 span 数据的自动采集,这样可以做到对代码的 无侵入性,插件化意味着可插拔,扩展性好。
我们知道数据一般分为 header 和 body, 就像 http 有 header 和 body, RocketMQ 也有 MessageHeader,Message Body, body 一般放着业务数据,所以不宜在 body 中传递 context,应该在 header 中传递 context,如图示:
dubbo 中的 attachment 就相当于 header ,所以我们把 context 放在 attachment 中,这样就解决了 context 的传递问题。
要保证全局唯一 ,我们可以采用分布式或者本地生成的 ID,使用分布式话需要有一个发号器,每次请求都要先请求一下发号器,会有一次网络调用的开销,所以 SkyWalking 最终采用了本地生成 ID 的方式,它采用了大名鼎鼎的 snowflow 算法,性能很高。
不过 snowflake 算法有一个众所周知的问题:时间回拨,这个问题可能会导致生成的 id 重复。那么 SkyWalking 是如何解决时间回拨问题的呢?
每生成一个 id,都会记录一下生成 id 的时间(lastTimestamp),如果发现当前时间比上一次生成 id 的时间(lastTimestamp)还小,那说明发生了时间回拨,此时会生成一个随机数来作为 traceId。
如果对每个请求调用都采集,那毫无疑问数据量会非常大,但反过来想一下,是否真的有必要对每个请求都采集呢,其实没有必要,我们可以设置采样频率,只采样部分数据,SkyWalking 默认设置了 3 秒采样 3 次,其余请求不采样,如图示:
这样的采样频率其实足够我们分析组件的性能了,按 3 秒采样 3 次这样的频率来采样数据会有啥问题呢。理想情况下,每个服务调用都在同一个时间点(如下图示)这样的话每次都在同一时间点采样确实没问题。
但在生产上,每次服务调用基本不可能都在同一时间点调用,因为期间有网络调用延时等,实际调用情况很可能是下图这样:
这样的话就会导致某些调用在服务 A 上被采样了,在服务 B,C 上不被采样,也就没法分析调用链的性能,那么 SkyWalking 是如何解决的呢。
它是这样解决的:如果上游有携带 Context 过来(说明上游采样了),则下游强制采集数据。这样可以保证链路完整。
Skywalking已经支持从6个可视化维度剖析分布式系统的运行情况。
具体参考即可:https://blog.csdn.net/weixin_42906244/article/details/125638730
参考文献:
https://blog.csdn.net/A123638/article/details/123117142
https://blog.csdn.net/weixin_38004638/article/details/115975798
https://blog.csdn.net/weixin_39866487/article/details/111581322
https://blog.csdn.net/weixin_39595621/article/details/111574769