项目背景
基本背景
复杂的微服务系统Debug或者调试问题的时候非常麻烦,主要有以下几点
- 线上系统出现一些问题但是没有相关日志记录。
- 线上线下环境隔离,线上无法回放当时的具体栈帧细节,得全靠日志猜。
- 交付测试的时候遇到低概率事件,没打日志需要打日志重启再看。
- 开发联调环境,单独自己Remote Debugger会阻塞其他正常服务流程和开发。
- 无法分析调用链上的细节数据、虽然有分布式链路追踪系统,但是粒度太粗。
- 每次调试预发布环境代码,只能修改一份重启环境、浪费时间和开发机资源。无法做到实时热加载最新修改代码
项目一期版本功能
基于JVMTI
接口并参考大量以往开源项目Stackdriver Debugger
, Zdebugger
, libinstrument
, Arthas
等
- 调试: 断点表达式、断点达成录制快照、快照后期回放、映射
source tree
展开与包跳转、即时修改并redefineClass
- 分析:
Trace
某方法内部调用链路情况(时间、出入参)、Stack
某方法被调用的各链路情况(时间、入参)
以上功能解决了基本的项目背景遇到的问题
先做单机版本、后期再尝试做集群版(集群
Opentracing
分布式链路追踪、集群DebugTrace
分布式调试追踪、集群分布式Metric
)
1 | 1: 接入pinpoint丰富span数据 |
项目愿景
- 开发一期功能Alpha(宋小菜开发联调、宋小菜测试环境预装)
- 一期功能修补Bug后进入Beta、并推入开源社区
- 经过开源社区的公开Issue提供Bug修补进入RC
- 进行部分性能影响功能点阉割后发布一期功能的1.0Release
项目结构
Agent端:放置应用端,做应用层数据录制搜集和上报服务端,
RedefineClasses
代码, 接受Server端的远程操作等
Server端:做数据整理功能和数据的查询服务,以及操作Agent
端的代码与配置推送等
Agent
设计轮廓
Agent技术,无需开发任何介入,对业务是无侵入,即可在部署的时候加入调试追踪、链路追踪等功能
Agent插件系统强大,基本符合开发常用的所有技术栈链路监控和调试,也可自定义相关的追踪业务插件
简介
JavaAgent
jdk1.5以后引入了javaAgent技术,javaAgent是运行方法之前的拦截器。
我们利用javaAgent和ASM字节码技术,在JVM加载class二进制文件的时候.
比如利用ASM动态的修改加载的class文件,在监控的方法前后添加计时器功能.用于计算监控方法耗时
1 | public interface Instrumentation { |
PreMain
1 | package agent; |
Transformer
1 | public class MyTransformer implements ClassFileTransformer { |
Demo
1 | public class TimeTest { |
JVMTIAgent
JVMTI的定义及原理
在介绍JVMTI之前,需要先了解下Java平台调试体系JPDAJava Platform Debugger Architecture
。
它是Java虚拟机为调试和监控虚拟机专门提供的一套接口。
JPDA按照抽象层次,又分为三层,分别是:
- JVM TI(Java VM Tool Interface):虚拟机对外暴露的接口,包括debug和profile。
- JDWP(Java Debug Wire Protocol):调试器和应用之间通信的协议。
- JDI(Java Debug Interface):Java库接口,实现了JDWP协议的客户端,调试器可以用来和远程被调试应用通信。
如下图所示JPDA被抽象为三层实现。其中JVMTI就是JVM对外暴露的接口。JDI是实现了JDWP通信协议的客户端,调试器通过它和JVM中被调试程序通信。
JVMTI 本质上是在JVM内部的许多事件进行了埋点。
通过这些埋点可以给外部提供当前上下文的一些信息。甚至可以接受外部的命令来改变下一步的动作。
外部程序一般利用C/C++实现一个JVMTIAgent,在Agent里面注册一些JVM事件的回调。
当事件发生时JVMTI调用这些回调方法。Agent可以在回调方法里面实现自己的逻辑。
JVMTIAgent是以动态链接库的形式被虚拟机加载的。
JVMTI的历史
JVMTI 的前身是JVMDIJava Virtual Machine Profiler Interface
和 JVMPIJava Virtual Machine Debug Interface
,它们原来分别被用于提供调试 Java 程序以及 Java 程序调节性能的功能。
在 J2SE 5.0 之后 JDK 取代了JVMDI 和 JVMPI 这两套接口,JVMDI 在最新的 Java SE 6 中已经不提供支持,而 JVMPI 也计划在 Java SE 7 后被彻底取代。
JVMTI的功能
JVMTI处于整个JPDA 体系的最底层,所有调试功能本质上都需要通过 JVMTI 来提供。
从大的方面来说,JVMTI 提供了可用于 debug 和profiler 的接口;同时在 Java 5/6 中虚拟机接口也增加了监听Monitoring
,
线程分析Thread analysis
以及覆盖率分析Coverage Analysis
等功能。
从小的方面来说包含了虚拟机中线程、内存、堆、栈、类、方法、变量,事件、定时器处理等等诸多功能。
具体可以参考oracle 的文档:https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html。
通过这些接口开发人员不仅可以调试在该虚拟机上运行的 Java 程序,还能查看它们运行的状态,设置回调函数,控制某些环境变量从而优化程序性能。
JVMTI的实现
JVMTI 并不一定在所有的 Java 虚拟机上都有实现,不同的虚拟机的实现也不尽相同。
不过在一些主流的虚拟机中比如 Sun 和 IBM,以及一些开源的如Apache Harmony DRLVM 中,都提供了标准 JVMTI 实现。
资料参考
https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html
https://www.ibm.com/developerworks/cn/java/j-lo-jpda2/
http://blog.caoxudong.info/blog/2017/12/07/jvmti_reference
http://lovestblog.cn/blog/2015/09/14/javaagent/
https://www.jianshu.com/p/e59c4eed44a2
功能
- 内存管理
- 线程
- 获取线程状态 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetThreadState)
- 获取所有线程 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetAllThreads)
- 挂起线程 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#SuspendThread)
- 挂起线程列表 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#SuspendThreadList)
- 恢复线程 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#ResumeThread)
- 恢复线程列表 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#ResumeThreadList)
- 停止线程 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#StopThread)
- 中断线程 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#InterruptThread)
- 获取线程信息 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetThreadInfo)
- 获取自己的监视器信息 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetOwnedMonitorInfo)
- 获取当前的竞争监视器 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetCurrentContendedMonitor)
- 运行代理线程 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#RunAgentThread)
- 设置线程本地存储 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#SetThreadLocalStorage)
- 获取线程本地存储 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetThreadLocalStorage)
- 线程组
- 栈帧
- 获取堆栈跟踪 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetStackTrace)
- 获取所有堆栈跟踪 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetAllStackTraces)
- 获取线程列表堆栈跟踪 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetThreadListStackTraces)
- 获取帧数 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetFrameCount)
- pop帧 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#PopFrame)
- 获取帧位置 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetFrameLocation)
- 通知帧pop (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#NotifyFramePop)
- 堆
- 获取对象标签 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetTag)
- 设置对象标签 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#SetTag)
- 强制垃圾收集 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#ForceGarbageCollection)
- 遍历可从对象到达的对象 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#IterateOverObjectsReachableFromObject)
- 遍历可到达的对象 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#IterateOverReachableObjects)
- 遍历堆 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#IterateOverHeap)
- 遍历类的实例 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#IterateOverInstancesOfClass)
- 获取带有标签的对象 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetObjectsWithTags)
- 局部变量
- 获取局部变量-对象 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetLocalObject)
- 获取局部变量-整数 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetLocalInt)
- 获取局部变量-长 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetLocalLong)
- 获取局部变量-浮点数 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetLocalFloat)
- 获取局部变量-双精度 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetLocalDouble)
- 断点
- 监视字段
- 设置字段访问监视 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#SetFieldAccessWatch)
- 清除字段访问监视 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#ClearFieldAccessWatch)
- 设置字段修改监视 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#SetFieldModificationWatch)
- 清除视野修改监视 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetLocalObject)
- 类
- 获取加载的类 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetLoadedClasses)
- 获取类加载器类 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetClassLoaderClasses)
- 获取类签名 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetClassSignature)
- 获取类状态 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetClassStatus)
- 获取源文件名 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetSourceFileName)
- 获取类修饰符 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetClassModifiers)
- 获取类方法 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetClassMethods)
- 获取类字段 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetClassFields)
- 获取已实现的接口 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetImplementedInterfaces)
- 是否为接口 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#IsInterface)
- 是数组类 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#IsArrayClass)
- 获取类加载器 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetClassLoader)
- 获取源代码调试扩展 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetSourceDebugExtension)
- 重新定义类(https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#RedefineClasses)
- 对象
- 字段
- 方法
- 获取方法名称(和签名)(https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetMethodName)
- 获取方法声明类(https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetMethodDeclaringClass)
- 获取方法修饰符(https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetMethodModifiers)
- 获取Max Locals(https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetMaxLocals)
- 获取参数大小(https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetArgumentsSize)
- 获取行号表(https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetLineNumberTable)
- 获取方法位置(https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetMethodLocation)
- 获取局部变量表(https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetLocalVariableTable)
- 获取字节码(https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetBytecodes)
- 监视器
- 创建监视器 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#CreateRawMonitor)
- 销毁监视器 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#DestroyRawMonitor)
- 监视器输入 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#RawMonitorEnter)
- 监视器出口 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#RawMonitorExit)
- 监视器等待 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#RawMonitorWait)
- 监视器通知 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#RawMonitorNotify)
- 监视器通知所有 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#RawMonitorNotifyAll)
- JNI功能拦截
- 设置JNI功能表
- 获取JNI功能表
- 事件管理
- 设置事件回调 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#SetEventCallbacks)
- Breakpoint
- Class File Load Hook
- Class Load
- Class Prepare
- Compiled Method Load
- Compiled Method Unload
- Data Dump Request
- Dynamic Code Generated
- Exception
- Exception Catch
- Field Access
- Field Modification
- Frame Pop
- Garbage Collection Finish
- Garbage Collection Start
- Method Entry
- Method Exit
- Monitor Contended Enter
- Monitor Contended Entered
- Monitor Wait
- Monitor Waited
- Native Method Bind
- Object Free
- Single Step
- Thread End
- Thread Start
- VM Death Event
- VM Initialization Event
- VM Object Allocation
- VM Start Event
- 设置事件通知模式
- 设置事件回调 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#SetEventCallbacks)
- 计时器
- 获取当前线程CPU计时器信息 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetCurrentThreadCpuTimerInfo)
- 获取当前线程的CPU时间 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetCurrentThreadCpuTime)
- 获取线程CPU计时器信息 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetThreadCpuTimerInfo)
- 获取线程CPU时间 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetThreadCpuTime)
- 获取计时器信息 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetTimerInfo)
- 获取时间 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetTime)
- 获取可用的处理器 (https://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/jvmti.html#GetAvailableProcessors)
DebugAgent
本工程借鉴了
SkyWalking
,Apollo
,Arthas
等结构和逻辑代码
上图经过更改优化成下图结构形式
逻辑全在JavaAgent端上,JVMTIAgent只做相关的非libstrument标准之外的Jvm接口操作
RPC Service
Grpc通信通道,主要将Leveldb搜集上来的数据进行上报Server以及接受Server上的操作
Server端操作
- 各个插件的启动和关闭
- 插件的各个单独配置
- 如StackDebugPlugin临时表达式的建立,取消等操作
Server端数据汇报
- 上报各个插件的获取的业务数据
Plugin Service
插件服务,主要的逻辑点,搜集怎样的数据主要由Plugin来提供支持,后期根据业务可以制定各类搜集插件
PluginBoostrap 插件启动器
PluginFinder 插件检索器
PluginImpl 各类插件的业务逻辑实现
Cache Service
缓存服务,主要用于汇报数据的时候防止产生大量数据后推送占用服务器带宽、所以需要有缓存来慢慢推数据
Storage Service
存储服务,将用于防止缓存数据占用过大、导致服务器内存出现oom-kill
以一定量内存数据在没有Rpc推送的情况下将会把插件上报数据慢慢推入本地磁盘
JVMTI
基于jvmti接口,封装部分逻辑操作或直接使用透操作接口、提供上层业务Plugin插件服务支持
UI Service
当前Agent搜集情况以及插件启动与配置情况
Server
借鉴SkyWalking,多Module多Provier
不同Module负责不同的功能领域, 一个Module下不同的Provider提供不同的能力
Rpc Module
- Grpc通信接收Agent端的插件数据
Cache Module
基于Opentracing数据模型
单个Trace中,Span间的时间关系
1 |
|
堆栈信息将切割具体的一次方法调用
的Span
,然后Agent将汇报以Span为维度的数据
由于网络不稳定,先后顺序形成的Stack Debug Trace调用链数据
需要靠Server来构建
所以将会有以下结构图
UI Module
以堆栈数据格式输出相关WebApi,前端结合Gitlab项目相关接口进行Debug回显
Storage Module
存储服务,将用于防止缓存数据占用过大、导致服务器内存出现oom-kill
Meta Module & Cluster Module
后期用搜集分布式Stack Debug Opentracing
数据及高可用
OpenTracing
OpenTracing
通过提供平台无关、厂商无关的API,使得开发人员能够方便的添加(或更换)追踪系统的实现
不过OpenTracing
并不是标准。因为 CNCF 不是官方标准机构,但是它的目标是致力为分布式追踪创建更标准的 API 和工具
名词解释
Trace
一个trace
代表了一个事务或者流程在(分布式)系统中的执行过程
Span
一个span
代表在分布式系统中完成的单个工作单元。也包含其他span
的 “引用”,这允许将多个Spans
组合成一个完整的Trace
每个span
根据OpenTracing
规范封装以下内容:
- 操作名称
- 开始时间和结束时间
- key:value span Tags
- key:value span Logs
- SpanContext
Tags
Span tags
(跨度标签)可以理解为用户自定义的Span
注释。便于查询、过滤和理解跟踪数据
Logs
Span logs
(跨度日志)可以记录Span
内特定时间或事件的日志信息。主要用于捕获特定Span
的日志信息以及应用程序本身的其他调试或信息输出
SpanContext
SpanContext
代表跨越进程边界,传递到子级Span
的状态。常在追踪示意图中创建上下文时使用
Baggage Items
Baggage Items
可以理解为trace
全局运行中额外传输的数据集合
国内查看评论需要代理~