skywalking研究

一、架构

    整个架构可分为五个模块:

1、agent
    各种语言、和框架适配的无缝接入探针,以及部分语言本身没有探针特性,也会提供集成的 SDK 支持手动集成
2、recelver
    数据收集器,agent 探针获取的调用链上下文数据会通过 grpc 或者 kafka 投递到数据收集器,支持各种协议的 trace 数据(包括 trace 规范协议、以及主流 trace 系统客户端 sdk 数据),metrics 数据收集,以及针对 service mesh 场景有单独实现收集器
3、aggregator
    多个跨进程系统来源数据聚合、分析、关联
4、storage
    最终落地持久化,落地数据库可选 es、tidb、influxdb、mysql、h2、shardingsphere
5、web ui
    采用 vue 实现的 前后端分离的 ui,界面美观

二、架构图

在这里插入图片描述

三、功能特性

1、trace 数据协议支持丰富
    在 trace 数据收集方面,skywalking 支持了如下的 Trace 协议数据格式上报分析:

opentracing
Jaeger
zipkin
skywalking协议

2、开发语言支持丰富
    虽然 skywalking 是 java 语言实现的链路追踪项目,但是在客户端 sdk 集成方面,几乎覆盖了主流开发语言。java 等部分支持动态织入的应用可以通过 agent 探针技术无感集成,其他语言也均有完善的 sdk 支持

java:Java agent
php :SkyAPM PHP SDK
C++: cpp2sky
go:SkyAPM GO2Sky
lua:LUA agent
node.js:Node.js agent
.net:SkyAPM .NET Core agent
python:Python Agent
service mesh (服务网格实现):SkyWalking on Istio 、Envoy Proxy

3、探针性能优异
    从测验结果看,增加链路追踪,几乎不影响业务服务的吞吐,只会增加一点资源消耗

4、ui 完善美观
5、架构灵活、不侵入业务
    skywalking 在架构设计上,oapServer 是无状态的支持横向扩展,超大规模流量下,只要后端存储模块 (elasticsearch) 能够 hold 住,支撑 100% 的 trace 采样率没啥问题。而且,大部分语言的集成实现,支持无缝集成,不侵入业务,探针的可定制化程度高,trace 追踪力度可配置追踪每个内部 service 方法级别。支持 opentracing 协议以及自定义埋点的数据收集。经初步测验,oapServer 在处理能力不足、或者直接宕机的情况下,均不影响业务服务
6、社区活跃,企业用户多,对这个项目足够了解
    作者吴晟早期在华为从事 apm 相关的工作,skywalking 最早是他个人主导的开源项目,随后捐献给 Apache 开源组织,并从 Apache 成功孵化毕业,最后成为 Apache 基金会顶级项目。截至目前已有三百多个代码贡献者,迭代到了 8.x 的版本。国内有很多公司采用 skywalking 搭建性能分析 、链路追踪系统

四、OpenTracing
1、重要概念

1、微服务架构普及,分布式追踪系统大量涌现,但API互不兼容,难以整合和切换,因此OpenTracing提出了统一的平台无关、厂商无关的API,不同的分布式追踪系统去实现。这种作用与“JDBC”类似。

OpenTracing是一个轻量级的标准化层,位于“应用程序/类库”和“日志/追踪程序”之间。
应用程序/类库层示例:开发者在开发应用代码想要加入追踪数据、ORM类库想要加入ORM和SQL的关系、HTTP负载均衡器使用OpenTracing标准来设置请求、跨进程的任务(gRPC等)使用OpenTracing的标准格式注入追踪数据。所有这些,都只需要对接OpenTracing API,而无需关心后面的追踪、监控、日志等如何采集和实现。
2、Span(跨度)指代系统中具有“操作名称”、“开始时间”和“执行时长”的逻辑运行单元。
3、Trace(追踪)指代一个分布式的、可能存在并行数据和轨迹的系统,直观上看就是一次请求在分布式系统中行进的生命周期,本质上是多个span组成的有向无环图(DAG)。
4、Operation Names(操作名称):每个span都有一个操作名称,操作名称应该是一个抽象的、通用的标识,具备统计意义的名称。
5、Inter-Span References(内部跨度引用关系):1个span可以和1个或多个span存在因果关系,目前只支持父子节点之间的直接因果关系ChildOf和FollowsFrom。
ChildOf:父span依赖子span,如RPC调用服务器和客户端、ORM的save和mysql的insert、countdownlatch。
Follows From:父span不以任何形式依赖子span结果
6、Logs(日志),每个span可以进行多次logs操作,logs反映了瞬间的状态,带有一个时间戳,以及至少一个k-v对。
7、SpanContexts(跨度上下文),当需要跨越进程进行传递时(例如RPC调用),需要使用到跨度上下文来延续请求调用链。
包含了两部分:
区分span和trace的信息:通常是TraceId和SpanId
baggage(随行数据):k-v集合,在Trace的所有span内全局传输,可以用来存储业务数据(如customerID等)。存储数量量太大或元素太多,可能降低吞吐量、增加RPC延迟。

2、API相关概念

1、Tracer的Inject(注入)/Extract(提取)
跨进程调用的方式有很多,HTTP、gRPC、Dubbo、Kafka等,为了抽象出统一的概念,OpenTracing提出了Tracer的API(io.opentracing.Tracer)通过carrier(Carrier可以是一个接口或者一个数据载体)去操作spanContext,有两个方法:
inject(spanContext, format, carrier)
extract(format, carrier)
format有几个选项:
TEXT_MAP:k-v集合
BINARY:字节数组
HTTP_HEADERS:和k-v类似,但保证了HTTP Header的安全性(保证了key、value的格式合法)
2、ActiveSpan(活跃跨度)
activeSpan(io.opentracing.ActiveSpan),当前运行点附近的跨度。当创建新跨度时,这个活跃跨度默认会被当做父节点(Parent Span),每个线程有且只有1个活跃跨度。
为了避免方法之间把ActiveSpan当做参数传递,用Scope作为ActiveSpan的容器,通过ThreadLocal将Scope存储下来,通过ScopeManager进行管理,就能够在任何地方获取该线程的ActiveSpan了。
这里并没有直接存储ActiveSpan到ThreadLocal,因为当当前span结束(close)时,需要弹栈上一个span,因此通过Scope存储上一个Scope的引用组成链表进行弹栈。
3、OpenTelemetry。
OpenTelemetry合并了Goole的OpenCensus和CNCF(Cloud Native Computing Foundation,云原生计算基金会)的OpenTracing,并统一由CNCF管理。
OpenTelemetry的终极目标是做Logging、Metrics、Tracing的融合,作为CNCF可观察性(Observability)的最终解决方案,包含了:
规范的指定和统一
SDK实现和集成
采集系统的实现
目前官方推荐的是Logging→Fluentd,Metrics→Prometheus,Tracing→Jaeger。
但现在OpenTelemetry还处于沙盒状态,且Jaeger比Skywalking的使用体验差了非常多,侵入性强,功能缺失,还出过生产事故(因为数据加载耗费太多内存导致节点崩溃),因此目前用skywalking是没有什么问题的。skywalking本身支持OpenTracing,因此OpenTelemetry的支持也是OK的。

3、全链路监控考虑因素

低侵入性:代码低侵入,容易切换,且开发工作量小
低性能影响:对业务本身机器资源使用和响应延迟影响较小
操作简便、接入灵活
时效性高:实时或近实时展示数据和报警

五、字节码增强

问题:在不修改原有Java代码的条件下,如何增加我们的新功能?(例如方法调用前打印一条日志)

1、字节码和Java类加载机制

2、运行时类的重载

1、代码AOP
我们在最初,总是会这样来统计方法访问的时间:
public void a() {
long startTimeMs = System.currentTimeMillis();
log.info(“processing…”);
long runningTimeMs = System.currentTimeMillis() - startTimeMs;
}
当有很多个方法都要修改,我们可能用到一些AOP切面,统一去处理。但这样是需要修改代码的,有侵入性。
2、静态重写
为了保证无侵入,如果我们在类被加载前,将这些语句写入.class字节码文件,就OK了

利用ASM和Javassist等工具很容易做到。问题是,这样的操作我们需要一个一个文件手动修改,如何让它自动化呢?
3、动态重载JVMTI、Instrumentation、Bytebuddy
JVM不允许在运行时动态重载一个类(加载1个类2次),因此考虑使用Java类库Instrument,对已加载类进行修改。
JVMTI(JVM Tool Interface),是JVM暴露出来供用户扩展的接口集合,类似于JVM的后门。实现上面就是运行到逻辑点后就插入回调接口的执行,例如前面的“加载”,就插入一些“before加载”,"after加载"等回调钩子。
Java Instrumentation(java.lang.instrument.Instrumentation)是利用JVMTI的接口提供了代理加载的动态库,JDK1.5支持“JVM启动时加载Agent”(premain,-javaagent:yourAgent.jar,例如skywalking),JDK1.6支持“JVM运行时加载Agent”(agentmain,com.sun.tools.attach,例如Arthas)。Agent可以翻译为“代理”或者“探针”
Bytebuddy基于ASM实现,封装了非常友好的API,避免接触JVM底层细节。skywalking正是利用Bytebuddy进行了字节码增强
premain和agentmain premain
【premain】

public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(……){
……
}
}, true);
}
(1)在类加载前,注册自己的classFileTransformer到Instrumention实例中,在classFileTransformer中通过targetClassName可以指定要修改的类限定名;
(2)class文件读入内存后,触发ClassFileLoadHook回调,在该回调中会遍历所有的Instrumentation实例,并执行其中所有的ClassFileTransformer的transform方法,修改字节码
这样指定类的字节码就被我们动态修改了,且这些代码都是在agent里面,不会影响原有业务代码。
【agentmain】

public static void agentmain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(……) {
……
}
}, true);
inst.retransformClasses(xxxxxxxx.class);
}
(1)通过另一个进程JVM,利用Attach API,在native函数的Agent_OnAttach中请求目标加载agent,创建InstrumentationImpl对象、监听ClassFileLoadHook事件,注册机的transformer。
(2)触发retransformClasses方法,然后会去读取ClassFile,触发ClassFileLoadHook事件,后面的流程与premain一致。

3、字节码增强

字节码增强(bytecode-enhance)指的是在Java字节码生成之后,对其进行修改,从而增强其功能。
字节码增强有很多方式,例如编译期增强,直接使用ASM等工具修改字节码,或者运行期增强,例如使用Java Agent等技术。
字节码增强可以用来做很多事情,例如开发期间热部署、或者测试时做一些Mock(如Mockito利用了ASM),或者做一些Trace、性能诊断、故障注入等等。

六、Skywalking实现原理

Skywalking是一个可观测性分析平台(Observability Analysis Platform,OAP)和一个应用性能管理(Application Performance Management,APM)系统。

1、skywalking整体架构


Skywalking目前想要做成跟踪、监控、日志一体的解决方案(Tracing, Metrics and Logging all-in-one solution)。
数据收集:Tracing依赖探针(Agent),Metrics依赖Prometheus或者新版的Open Telemetry,日志通过ES或者Fluentd。
数据传输:通过kafka、Grpc、HTTP传输到Skywalking Reveiver
数据解析和分析:OAP系统进行数据解析和分析。
数据存储:后端接口支持多种存储实现,例如ES。
UI模块:通过GraphQL进行查询,然后通过VUE搭建的前端进行展示。
告警:可以对接多种告警,最新版已经支持钉钉。

2、追踪实现原理

1、Agent和Plugin
skywalking agent为了能够让更多开发者加入开发,并且能够有足够的自由度(比如一些私有协议),使用了插件机制。 agent启动时会加载所有plugins,进行字节码增强。
Plugins的核心问题有2个:
(1)创建span,让它能够显示Trace调用链
(2)考虑如何传输,例如Kafka需要考虑如何把它加入kafka header中;HTTP需要考虑加入Http Header中。
2、TraceSegment设计
skywalking没有使用传统的span模型,处于性能考虑,将span保存为数组,存放到TraceSegment结构中批量发送;同时Segment可以很好地在UI上展示信息。
一个TraceSegment是Trace在一个进程内所有span的集合。如果是多个线程协同产生1个Trace(例如多次RPC调用不同的方法),它们只会共同创建1个TraceSegment。
由于支持多个入口,因此skywalking去掉了RootSpan的概念,skywalking提出了3种span类型:
EntrySpan:进入TraceSegment的请求,一般为HTTP/RPC服务,如SpringMVC。
LocalSpan:内部请求,一般为方法调用,或者跨线程调用。
ExitSpan:从TraceSegment调出,一般为httpClient。
当Kafka等进行批量消费时,消费的数据可能来自于不同的生产者,由于skywalking的TraceSegment支持多个EntrySpan,使得生产和消费的调用链可以保存在同一个Trace中。
3、请求采样设计
org.apache.skywalking.apm.agent.core.sampling:SamplingService

有两种方式可以调整请求采样:
(1)skywalking agent调整采样率,减少数据上传
通过agent.sample_n_per_3_secs设置3秒内采样的数量,一般500~2000是合适的值。默认-1全采样。 在设置agent采样率后,如果调用链上游进行了采样,那么下游会忽略采样率进行强制采样,保证Trace调用链完整。
(2)collector调整采样率,丢弃数据
通过sampleRate调整采样率,丢弃部分数据。默认10000是全采样,如果设置为5000则会有50%数据被丢弃。 丢弃数据只会影响Trace功能,不会影响Metric功能,Metric的所有数据都是根据全量数据计算的。
Trace功能:调用链。
Metric功能:性能检测指标,如成功率等等。
8.4.0开始支持Agent参数配置动态调整,在修改agent采样率时不必重启应用。
4、数据收集和消费(轻量级队列内核)
org.apache.skywalking.apm.commons.datacarrier.buffer
为了解耦数据上传和消费,平衡上传速度和消费速度,skywalking在内部构建了一个轻量级的消息队列。

channel可以类比为Topic,Buffer可以类比为Partition。
(1)生产数据,先判断存储在哪个Buffer中,再判断存储在Buffer的哪个位置。
Partition:默认实现为从第一个到最后一个Buffer轮询。
判断存储位置:Buffer维护了一个从0开始的循环索引,记录下个可用位置:
BLOCKING:如果当前位置还有数据未消费,则阻塞新数据写入,且产生回调事件
OVERRIDE:如果当前位置还有数据未消费,直接覆盖新数据
IF_POSSIBLE:从当前index往后找n位,如果有空闲位置,则保留,如果没有,则丢弃。
(2)消费消息,每个消费者可以有多个消费线程
如果Buffer队列数量>消费线程数量,则1个线程可以消费多个Buffer,和普通消息队列一样;
如果Buffer队列数量<消费线程数量,则部分Buffer可能对应多个线程,对应的方式是平分Buffer长度,例如长度500,平分0249给Thead4,250499给Thread3。 在消息消费时,消费线程会初始化一个1500长度的consumeList,然后把Buffer从头到尾遍历,遇到非null值就存入consumeList中,并将index置为null可写,然后调用consume方法执行具体的消费逻辑。

3、性能剖析实现原理

当线上代码运行缓慢时,我们希望找出缓慢的原因。一种常见的方式就是增加日志打印→重新编译→重新提测→上线观察→找到问题后修改代码→重新编译……一套流程走下来一周就过去了。 因此skywalking利用自身tracing优势+java agent技术,实现了“性能剖析”功能。

1、线程堆栈分析

当性能剖析开始后,会对执行线程周期性地创建线程栈快照,并将所有快照进行汇总分析。当两个连续的快照含有同样的方法栈,说明大概率这段时间都在执行这个方法,估算出方法执行时间,就能够帮助判断性能问题出在哪里。
另外,LineNumberTable也是在方法信息里的,因此可以直接看到代码行数,实现代码级别的性能问题定位:

2、性能损耗控制 由于操作的是生产环境,不能对现有代码产生严重影响,所以需要控制性能损耗。
相比于侵入性地编写log打印,skywalking的性能剖析不需要埋点,也就不会增加额外的日志打印开销,也不会对日志系统/监控系统产生压力(例如有些应用会要求线上开启debug进行调试)。
采样间隔、采样数量,采样时间段,采样接口等都可以配置,且大于指定执行时间的调用链才会被监控
监控时间可以设置定时,在业务低谷期进行处理 几乎是无损耗。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值