1 要解决的目标 及理论模型
1.1 背景
互联网电商行业,一次用户的购买,涉及背后的系统很多,如果出现问题,我应该怎样快速定位是那个系统环节出现问题?A系统,B系统,C系统,巨量的体系下面,每秒成千上万的流量,那都是钱A! 老板允许你交易失败一分钟?这就是链路追踪要解决得问题。如何解决?如何统计每个环节消耗的时间?最简单的就是从入口仍一个标识进去,每经过一个环节,建立子环节和整条链路的关系,并打印代码执行的时间,输出到日志中,最后通过数据汇总,分析,完成关系运算,输出到界面中,可以吗?ok?yes,对就那简单。这就是一个思路。elk实现方案,就是这样搞的?elk不懂得自己百度。还有没有其他方案?除了能监控关键方法层面,代码层面有没有?代码层面的监控,能提供直接可靠的 问题定位机制,能快速发现问题。有 metrics 技术,可以参考 https://www.jianshu.com/p/effe8e259d25,就不依依解释了。
1.2 我们要监测什么东西?
技术体系的变更引起了监测对象的变化,不同的技术监测对象要求有不同的方案,apm技术也在发展,以前中间件的监控,就能满足体系要求,随着微服务技术发展,apm框架技术也在升级迭代。下面就从最原的基础理论谈起。
1.2.1
业务跟踪监测原理
我们如何知道一条链路经过的各个节点?这就是业务监控。那我门采用什么数据结构? 比如一次访问要经过 A,B,C 三个节点,他们之间可能是并行的,也可能是串行的。访问一个节点的时候,他的上级是什么?所以会引入parent的概念。跨线程,怎办?大家都应该有一个统一的上级,这就引入了tracementId的概念.我如何记录当前处理的节点数据,为了解决这个问题就引入了栈的概念。举例如下:访问经过的路径如下
user -> jetty -> A(spring bean) -> B(spring bean)
-> C(spring bean)
那应该怎处理呢?
即线程按顺序调用到jetty,A,B,C,pinpoint会将其依次放入到CallStack中(执行trace.traceBlockBegin()),当方法执行完毕(调用trace.traceBlockEnd()),会从栈中弹出。
此时会产生几个SpanEvent,关于组织其调用关系属性有如下三个:
spanId:标志这个SpanEvent归属于哪个span
deepth:标志调用深度,即入栈时的当前数组下标
sequence:标志调用顺序
而对于例子中的几个SpanEvent其值时什么呢?
根据深度和顺序号,就很容易能够梳理出调用树了。
需要注意一点,上述SpanEvent B和C为什么深度一样?因为B和C是串行的两个方法,B调用完毕,会出栈,C入栈,所以C和B的深度是一样的。这就涉及到了本地线程的技术,存储上下下文变量。
跨系统怎办?简单啊,通过数据传输,那就把上个相关所属于的 segmentId,最后一次访问节点的相关信息传递到下一个进行,做为上级来处理呢。
1.2.2 除了业务监控,我们还需要监控什么?jvm监控,内存监控,垃圾回收监控,cpu监控,线程数量,这些都是对资源的监控。 这些可以通过java提供的ManagementFactory 技术实现。
1.2.3 除了这些要监控的对象,我们还要考虑什么问题?
监控数据 再业务系统中呈现爆炸性的增长,我们应该怎存储?增加监控系统我们对我门的业务系统性能带来什么冲击?我们的是否每条数据都需要?
这就涉及到采样频率的考虑,能不能根据性能自动调节采样率?elk就提供了自适应采用方案。
1.2.4 怎样实现的问题?
说了那多上面的东西,用java技术我门应该怎实现?监控的对象,包过业务,redis ,数据源,各种中间件,我们应该用怎样的架构组织去实现,最大程度上的抽象,最小的代码的开发?这就是插件技术的引用。链路中间的通信,我们应该用什么协议实现?一方面是应用代码,一方面是监控代码,我们手写代码?侵入到业务逻辑中?NONO,我们一定要做成公共组件,这就是aop思想,那我门在什么时机对 代码进行更改?那多需要更改的方法,总不能一个个配置切入点把。这就需要利用 java angent 技术,从虚拟机层面对类进行改写,可以通过https://www.jianshu.com/p/63c328ca208d了解。本质上来说就是对java 字节码就行修改,这些都是字节码技术。因为涉及到底层,难啊!字节码技术各自不同的特点,能有我们程序员直接看懂,面向对象开发的吗?有!技术的进步就是为了提高生产力,让我门当傻瓜把。
那多插件。我们应该怎样组织起来,方面集中管理,如果需要扩展插件,我们应该怎办?这就需要考虑扩展性的问题。这方面skywalking 做的就比pinpont 比较好,我只需要简单配置下我拦截的类,和 方法,和要插入的公共业务,通过配置就可以实现,其他的你别跟我谈 什么agent 技术,什么 字节码修改,你的体系api ,我通通不需要知道?怎实现的?
2 目前市面的技术体系
我们一切从头开始制造轮子?你扯蛋呢?巨人也是站在巨人的肩膀上,才能看的更远。市面上有 Zipkin Pinpoint SkyWalking CAT Jaeger appdash 我们应该怎样选型? 下面就进行回答这个问题
3技术选型
每个技术的演变都是有其背景和应用的领域.
1 Zipkin 是 Spring Cloud 指定的默认性能监控工具,
2 Jaeger 纳入云原生计算基金会(CNCF)的孵化项目 主要为k8s 服务的基于go语言的.
3 Pinpoint 是韩国搞的.
4skywalking 是国人搞的,已经进入了Apache 基金会,目前国内比较火。国内强烈建议这个,由于 应用了byptebuddy字节码 技术,面向对象思维编程模型,性能也不错,最终导致傻瓜式开发。配置配置完事了。和pinpoint 相比不需要了解 底层api 开发效率和易用性都非常高。
5探针性能影响
比较关注探针的性能,毕竟APM定位还是工具,如果启用了链路监控组建后,直接导致吞吐量降低过半,那也是不能接受的。对skywalking、zipkin、pinpoint进行了压测,并与基线(未使用探针)的情况进行了对比。
选用了一个常见的基于Spring的应用程序,他包含Spring Boot, Spring MVC,redis客户端,mysql。 监控这个应用程序,每个trace,探针会抓取5个span(1 Tomcat, 1 SpringMVC, 2 Jedis, 1 Mysql)。这边基本和skywalkingtest的测试应用差不多。
模拟了三种并发用户:500,750,1000。使用jmeter测试,每个线程发送30个请求,设置思考时间为10ms。使用的采样率为1,即100%,这边与生产可能有差别。pinpoint默认的采样率为20,即50%,通过设置agent的配置文件改为100%。zipkin默认也是1。组合起来,一共有12种。下面看下汇总表:
从上表可以看出,在三种链路监控组件中,skywalking的探针对吞吐量的影响最小,zipkin的吞吐量居中。pinpoint的探针对吞吐量的影响较为明显,在500并发用户时,测试服务的吞吐量从1385降低到774,影响很大。然后再看下CPU和memory的影响,在内部服务器进行的压测,对CPU和memory的影响都差不多在10%之内。
6 collector的可扩展性
skywalking 有集群模式,collector 向zookper 注册,进行扩展服务能力。
zipkin 通过mq 方式进行消息订阅,进行服务扩展
pinpoint 也有集群模式,通过zookper 提供 在agent 和collector 和collector 和hbase 之间的高可用
7 监控的细粒度 pinpoint>skywalking>zipkin
以上都支持微服务和云生态。下面基于实现原理,支持的语言,支持的组件支持的数据存储,扩展性,多维角度去解析,架构基本一样,实现细节侧重点不同,选型是兼顾目前公司的技术和长远发展,取舍后的结果。
8 collector的可扩展性
pinpoint 和skywalking 通过java agent 提供的接口 利用字节码增强技术实现无侵入性。在虚拟机加载类的时候,动态增强类功能。
9 监控力度
4通过刨析 pin个技术框架,查看最基本的模型构成
1 整体业务流程
pinpoint
监控的业务有: 业务跟踪,通过插件实现。
业务机器监控:监控业务机器的 ip 端口,进程号,状态,jvm
agent 状态 监控:收集各种agent相关的信息,比如cpu,jvm内存,trace响应时间等数据
支持的协议:Tcp ,Udp,Grpc,thrift 协议
整体架构处理
collect端的主要框架类图
客户端体系整合涉及的知识点
1 系统加载插件 步骤
Pinpoint Agent 随 JVM 一起启动
Agent 加载所有 plugin 目录下的插件
Agent 调用已加载的插件的 ProfilerPlugin.setup(ProfilerPluginSetupContext) 方法
2 插件开发层,实现的功能如下
声明在哪个拦截点 进行拦截 ,TransformerCallback 声明哪些类需要被增强,和如何增强。举例如下:
asm 知识详情见
https://developer.ibm.com/zh/articles/j-lo-asm30/
3 插件业务逻辑层次
4存储和发送层
模块的管理采用guice 技术,实现了依赖注入,根据配置灵活配置实现类和依赖类
参考 https://blog.csdn.net/xxxlxy2008/article/details/89736544
比如
5
每当类被加载的时候,Pinpoint Agent 会寻找注册到该类的回调 TransformerCallback
6
如果 TransformerCallback 被注册,Agent 就调用它的 doInTransform 方法
TransformerCallback 修改目标类的字节码 (例如添加拦截器、字段等)
修改后的代码返回到 JVM,类被加载的时候使用修改后的字节码 TransformerCallback 修改目标类的字节码 (例如添加拦截器、字段等
修改后的代码返回到 JVM,类被加载的时候使用修改后的字节码
skywalking
思路 和pinpoint 类似,只是具体细节不一样。
架构
1加载插件
入口
2插件里面定义了,那些类,哪些方法,执行增强。
3 agent启动得时候,每加载一个类,查找类有没有匹配得增强。
通过 agentBuilder 对 transform 方法进行增强,
每加载一个类,查找对应的插件
最终调用插件的 define 方法进行增强
如果有得话通过bytebuddy 执行增强。
collector 端处理,简要举例说明:
业务跟踪过程
存储层,采用了生产者和消费者模式,存储是多级分片集合,接收到数据可以放入不同的分片上,分片有对应的数据消费者,提高消费能力。后面继续 讲解。