greys在线诊断工具的主要实现

栏目: Java · 发布时间: 7年前

内容简介:greys在线诊断工具的主要实现

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实例,进而获取一系列的数据信息。一个简单的操作如下所示:

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对象进行一系列的操作了。相应的代码如下参考所示:

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指令

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

---+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和字节码在日常的关键开发中,均起到重要的作用。这些技术有助于技术能力的提升。


以上所述就是小编给大家介绍的《greys在线诊断工具的主要实现》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

A Byte of Python

A Byte of Python

Swaroop C H / Lulu Marketplace / 2008-10-1 / USD 27.98

'A Byte of Python' is a book on programming using the Python language. It serves as a tutorial or guide to the Python language for a beginner audience. If all you know about computers is how to save t......一起来看看 《A Byte of Python》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

MD5 加密
MD5 加密

MD5 加密工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具