Greys主要实现原理

转载自:https://www.iflym.com/index.php/code/201801170001.html

greys是一个使用java management tool进程注入javaagent实现在线系统的诊断一个工具。原github为(https://github.com/oldmanpushcart/greys-anatomy),其主要的功能在于系统不停机的情况下。可以查看系统中的线程信息,cpu使用情况,jmx信息,以及某个方法在运行时的调用栈,调用参数等。

一个典型的场景就是线上某个功能出bug,但是系统中并没有记录参数信息,这时候即可通过这个功能注入agent,临时地打印出这个调用方法的参数,以方便定位相应的问题。如无此工具,则只能改代码,然后重新上线。在这种情况下,可能出错的场景就不能再复现。(当然也有其它工具(如log monitor)作到在线系统参数记录开关的目的,这里不作描述)

本文主要描述greys是如何工作的,包括如何注入到在线系统,然后脚本client与注入后server端的交互,以及如何实现一个简单的参数拦截记录功能。从整个实现机制层面描述其工作原理。

 

1. 将agent注入到在线系统中

自java 6之后,jvm提供了一系列的tools工具,用于与jvm进行交互。其中在attach中,即可以通过相应的api拿到本地上正在运行的jvm实例,进而获取一系列的数据信息。一个简单的操作如下所示:

01

02

03

04

05

06

07

08

09

10

11

12

public static VirtualMachine findByPid(String pid) throws Exception {

    List<VirtualMachineDescriptor> virtualMachineList = VirtualMachine.list();

    for(VirtualMachineDescriptor descriptor : virtualMachineList) {

        //这里的id即与jps中的pid相同,即进程id

        String id = descriptor.id();

        if(Objects.equals(id, pid)) {

            return VirtualMachine.attach(descriptor);

        }

    }

 

    throw new RuntimeException("不能找到虚拟机:" + pid);

}

如上的代码,即可通过pid找到一个目标虚拟机,然后再通过 VirtualMachine中的相关方法可以拿到相关的信息,但更主要的即是可以通过此类注入一个新的agent到目标jvm中,然后此agent即可以通过instrument对象进行一系列的操作了。相应的代码如下参考所示:

1

2

3

4

5

6

7

8

9

public static void injectAgent(VirtualMachine virtualMachine, String agentPath, String otherConfig) throws Exception {

    //agent path即实际要注入的agent jar的实际地址,如/data/x/agent.jar

    //otherConfig在相应的agent main中,jvm会将此参数传递此目标方法参数中

    virtualMachine.loadAgent(agentPath, otherConfig);

}

 

public static void premain(String agentArgs, Instrumentation inst) {

     

}

在上面的代码中,通过将agent.jar注入到目标系统,并且通过otherConfig参数进一步提供其它参数,这样可以进一步的进行一系列操作。
在greys中,实际注入过程如下。

  1. greys整体分为2个jar, 1个为greys-agent.jar,另一个为greys-core.jar,认为greys-agent.jar是注入引导类,core为主要逻辑实现。(后续简称agent.jar, core.jar)
  2. 通过loadAgent将agent.jar注入在目标系统,并且传递了core.jar的目标地址,以及其它参数
  3. 传递给agent.jar的参数分为2部分,一部分即为core.jar路径信息,另一部分即为后续agent server启动时的配置参数
  4. agent解析参数,拿到core.jar的地址信息,并由此构建一个全新的classLoader,通过classLoader可以拿到此jar中的所有类信息
  5. 由classLoader构建出全局单例对象 gaServer,并将instrument对象绑定其中,后续的类扫描,重定义等均通过instrument来引导处理
  6. gaServer开始解析由agent.jar中传递过来的配置信息,并且启动监听server,监听指定端口,准备接收相应的请求

至此注入过程完成。相应的agent.jar和core.jar都已经在线上系统中存在。其中 core.jar由单独classLoader持有,线上系统感知不到.

2. 指令client与agent server的交互

通过注入过程,相当于在线上系统开了一个后门,并且后门中可以看到重要的信息,比如通过mxbean拿到线上系统信息,同时instrument功能可以重定义对象。
整个过程由类GaServer完成,以下详细描述过程:

  1. 通过GaServer#bind 读取配置信息,初始化ServerSocketChannel对象,并绑定ip和端口
  2. 通过一个简单的nio代码(GaServer#activeSelectorDaemon),开启一个新线程,读取客户端请求连接和输入数据
  3. 客户端通过连接此ip和端口(GreysConsole类实现),并发送简单的指令来完成交互过程,一个连接过程称之为一个Session,其中封装相应的流信息以及控制逻辑
  4. 通过定义简单的通信协议,一行为一个指令,输入回车即完成此指令,并等待server返回,然后将此信息打印在控制台
  5. 服务端读取到指令之后(GaServer#doRead),简单解析(DefaultCommandHandler#executeCommand 和 Commands#newCommand), 并构建出command对象
  6. 通过command对象以及相应的action,根据相应的类型执行不同的action动作,并将响应信息以及执行结果(封装为Affect),写回session动作中,并由简单的循环读取响应信息,最终输入回客户端中

上述的动作,4-6是可以多次处理的,即客户端在执行完一个指令之后,可以再输入下一条指令,继续执行其它指令。
同时,如上所述,所有的指令均是通过Command完成,并通过全局扫描自动添加至Commands中,相应的交互实际即在线上系统中执行相应的command指令。

3. agent server在线系统监控

command对象主要的执行过程主要还是通过读取mxBean和类增强两大类来完成。其中前者不需要改变线上系统内部行为,后者需要改变线上行为,并对类的执行过程产生影响。

3.1 JvmCommand指令
此指令非常简单,即打印出线上机器当前的情况,其Action为silentAction,即不会改变线上结果,仅简单返回数据信息。主要信息通过ManagementFactory拿到各个mxBean,从中获取到如类加载信息,gc信息,内存信息,操作系统信息等,然后进行数据组合,最后封装为TTable对象,展现为一个控制台表格形式的数据。直接返回即可。

3.2 TraceCommand指令

此指令用于输出单个方法在调用时,此方法的代码所访问的每一个方法的调用信息,主要包括耗时信息。其输出如下参考所示:

1

2

3

4

5

`---+Tracing for : thread_name="http-nio-8080-exec-9" thread_id=0xa9;is_daemon=true;priority=5;

    `---+[1,1ms]xxx.YController:zzz()

        +---[1,0ms]org.springframework.web.servlet.ModelAndView:<init>(@45)

        +---[1,0ms]org.springframework.web.servlet.ModelAndView:addObject(@47)

        `---[1,0ms]org.springframework.web.servlet.ModelAndView:setViewName(@48)

其实现机制即通过重定义要进行监控的类,然后在执行过程中打印相应的调用过程。整个过程详细如下:

  1. 通过client指令(如 trace abc.* methodA)中解析出类信息,这里的类信息为一个类正则表达式,可以匹配多个
  2. 通过之前保存的instrument对象拿到线上所有已加载对象(instrument#getAllLoadedClasses), 与类匹配信息进行匹配,提出所要增强的类
  3. 同时,通过增强类列表,再通过方法正则匹配表达式,找到需要监控的方法列表,并同形成 Map<Class<?>, Matcher<AsmMethod>> 对象,即这些类的这些方法需要处理
  4. 通过这些数据以及增强类command所提供action,组合形成一个类增强器Enhancer(使用asm进行代码增强)。 此增强器实现了ClassFileTransformer接口,即可通过instrument#addTransformer添加到线上系统中
  5. 调用instrument#retransformClasses 重新转换这些指定的类,通过之前的匹配,可以避免增强不必要的类,这样对线上系统的影响最小
  6. 在相应的enhance实现中,通过反向勾子,最终利用asm,在整个方法指令集中,通过监控 执行方法前 调用三方方法前, 调用三方方法调用后, 调用三方方法异常后, 执行方法后 这些相应的点,并反向调用相应command提供的监听器。 这里为TraceCommand提供的AdviceListener对象
  7. 在监听器中,即记录起相应的三方方法信息,尽可能启动更多详细的信息,并通过一个简单的树状信息组件 TTree 将这些信息收集在一起(具体可参考相应代码实现)
  8. 最终方法执行后,将此执行信息写回session,即完成一次trace过程

TraceCommand指令主要使用了instrument中的几个关键方法,如getAllLoadedClasses返回所有已加载类,监控的类即从这些类中查找。 retransformClasses重转换类。这些方法都是由jvm自身提供,不需要通过classLoader来中间跳转,并且完成不影响jvm原信息逻辑。 

同时,转换类,即类增强使用了asm,通过在字节码层面处理尽可能多的一些信息,增强这些信息,以拿到线上信息。当然也可以使用javassist(据说greys早期即使用javassist)

值得注意的是,因为每一次增强相当于对线上系统类进行了一次处理,为避免可能对后续执行产生影响,在完成一次类增强之后,greys通过removeTransformer将此次转换器移除掉了,这样此次指令的增强将在下一次指令的增强时失效, 这样可以避免多次相同作用增强。避免出现奇怪的问题.

最后

至此,整个greys的实现分析完毕。整个实现使用到了一些关键的技术,列举如下:

  1. jvm tools attach,用于连接线上jvm信息
  2. classLoader,用于隔离类信息
  3. javaagent & instrument, 用于提供类转换过程, 修改加载类字节码
  4. asm & bytecode, 用于字节码处理,以方便对类进行增强及处理

其中classLoader,instrument和字节码在日常的关键开发中,均起到重要的作用。这些技术有助于技术能力的提升。

诞生 很早的时候,我们使用BTrace排查问题,在感叹BTrace的强大之余,也曾好几次将线上系统折腾挂掉。2012年淘宝的聚石写了HouseMD,将 常用的几个Btrace脚本整合在一起形成一个独立风格的应用,但其核心代码用的是Scala,我们没这方面的编程维护经验,所以只好艳羡HouseMD 的才思敏捷而无法在其上增加功能。 于是乎,Greys诞生了。 PS:目前Greys仅支持Linux/Unix/Mac上的Java6 ,Windows暂时无法支持 Greys是一个java进程执行过程中的异常诊断工具。 在不中断程序执行的情况下轻松完成问题排查工作。 和HouseMD一样,Greys-Anatomy取名同名美剧“实习医生格蕾”,目的是向前辈致敬。代码编写的时候参考了BTrace和HouseMD两个前辈的思路。 目标群体 有时候突然一个问题反馈上来,需要入参才能完成定位,但恰恰没有任何日志。回去加上重新部署,一杯咖啡时间过去了,是不是很崩溃? 当你经过反复这样几次折腾之后变得聪明了,在自己的代码的所有入参和出参地方都加上debug日志,但这次问题似乎暴露在别人的代码中了...是不是很无奈? 突然遇到线上一个性能问题无法确定到底是哪个环节的耗时,只能反复抓jstack猜,还有没有办法可以好好的过日子啦? 遇到以上问题时,你就是我们这类工具的目标客户,此类工具能利用Java6的Instrumentation特性,动态增强你所指定的类,获取你想要到的信息。 我们的座右铭 让程序解决繁琐的事情 特性功能 交互方式 命令行交互 内置功能 查看加载类,方法信息 方法执行监控(调用量,成功失败率,响应时间) 方法执行数据观测(参数,返回结果,异常信息等) 方法执行数据记录 性能开销渲染 方法执行数据自定义观测(js脚本) 查看方法调用堆栈 软件特点 纯Java实现的开源项目 安装使用便捷,仅一个jar包 可无需重启JVM进行CT式诊断 Groovy表达式展开变量,方便你查看入参、出参、异常、当前对象的各种属性细节 常用分析命令集成,monitor、trace等 观察变量的出入参 时间隧道,tt命令能以时间维度纪录下监控期内的每一次调用环境 多人并行协作 基于C/S架构的任务模式甚至能让多人同时远程到同一进程上执行不同的指令、脚本,非常适合团队一起进行线上问题排查与跟踪。Greys采用纯Java编写并留有良好的扩展,如果你有需求,只要你会Java,就可以为你自己编写想要的功能。 Greys最有利的武器是他的ONGL表达式,能让你在感受到HouseMD集成功能便利的同时,也能发挥出自定义Btrace脚本的灵活。 应用管理员拥有JVM进程权限,由他来首先在目标JVM上启动Greys 技术专家A和B平时没有对应机器的权限,但只要网络能访问,他们可以通过指定ip:port直接访问目标机器的JVM进程,仿佛在本地一般 标签:greys
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值