论坛首页 综合技术论坛

btrace使用实例

浏览 5798 次
精华帖 (0) :: 良好帖 (7) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2010-02-03   最后修改:2010-02-07

BTrace 说明】

http://kenai.com/projects/btrace 是一个实时监控工具,使用了 java agent jvm attach 技术,可以在不停机的情况下实时监控线上程序的运行情况,另外,对 btrace 脚本(实际上就是 java 程序)做了非常严格的安全限制,安全性很高,对应用程序基本没有影响。在性能方面, cobar 进行过测试,对方法进行调用耗时统计的时候,基本消费在微秒级别,可以说微不足道。

【背景】

在中文站 napoli 上线过程后,发现了一个奇怪的现象,尽管“已知”的 offer 发送端都已经迁移到 napoli 系统中,但是老的 mq 系统仍然有新的 offer 消息进来,因为连接 mq 的服务器非常多,定位消息来源成了一个非常大的问题。这种情况,想到了使用 BTrace 在某一台服务器进行线上监控进而期望发现这个幽灵。

【过程】

首先,我们需要知道两个基本信息:消息类型和来源 ip ,这样才可以定位 offer 消息的来源。

要知道来源 ip ,需要找到服务器端 socket 管理的类,只有在建立 socket 的地方,才可以抓到具体 ip ,经过分析 amq 代码,发现 tcp 连接基本是有下面这个类来服务所有消息的接收的:

 public class TcpTransport extends TransportThreadSupport implements Transport, Service, Runnable {
    private static final Log LOG = LogFactory.getLog(TcpTransport.class);
    private static final ThreadPoolExecutor SOCKET_CLOSE;
    protected final URI remoteLocation;
    protected final URI localLocation;
    protected final WireFormat wireFormat;

    protected int connectionTimeout = 30000;
    protected int soTimeout;
    protected int socketBufferSize = 64 * 1024;
    protected int ioBufferSize = 8 * 1024;
    protected boolean closeAsync=true;
    protected Socket socket;

这个类中包含一个 socket 对象的成员变量,所有我们只要监控 readCommand 方法,这个方法的返回值实际上就是一个 ActivemqObjectMessage 对象,这样就可以在一个方法上加拦截器就可以同时捕获到 ip 和消息对象,两全其美!!!

protected Object readCommand() throws IOException {
        return wireFormat.unmarshal(dataIn);
    }

因为原有 ESB 消息通道都是一个队列 ESBQueue ,所以无法通过队列名称来确定消息类型,必须通过 ESBTransferObject 对象来取得消息类型: destType offer 的区间是 1000-1008

public class ESBTransferObject implements Serializable {

    private static final long serialVersionUID = -5975115234845303878L;
    /**
     * 消息体,原则上对象序列化后的XML数据(String) 注意使用XML1.1规范。
     */
    private Object            content;
    /**
     * 用户自定义数据
     */
    private Object            userDefineData;
    /**
     * 目的消息类型
     */
    private int               destType         = -1;

但是,在服务器端并没有 ESBTransferObject 对象,无法反序列化( BTrace 也不支持反序列化操作),所以没有方法简单取得消息类型信息!!!

OK ,我不反序列化,直接拿二进制 byte[] ,类型信息应该是在固定位置的吧?但是发现这个对象 content 变长字符串定义在类型之前,类型位置不确定了,晕倒啊

不死心,输出二进制数据,柳暗花明啊,原来对象序列化的时候, primitive field 都是紧接着类型信息写入的,所以,类型信息是在固定位置的 ,类型信息始终是 255 256 两个字节(实际上是 4 个字节,但是目前我们只占有 2 个)

Ok ,编写代码,测试环境运行一下,晕倒,竟然有数组溢出!

使用 BTrace ,把这个数组打印下来(这个需要点技巧, btrace for 都不允许),竟然发现位置偏移到 205 206 位置 ,这个真的不知道什么原因,估计是客户端发送的时候压缩了,简单修改偏移量,测试运行, ok ,所有的消息类型和 ip 的对照表打印出来了。

 

 

package com.alibaba.btrace.script;

import static com.sun.btrace.BTraceUtils.*;

import com.sun.btrace.annotations.*;

@BTrace
public class AMQQueue2IP {

    @OnMethod(clazz = "org.apache.activemq.transport.tcp.TcpTransport",        //需要拦截的类名
      method = "readCommand",                                         //需要拦截的方法名
      location = @Location(Kind.RETURN))              //拦截位置,方法返回时
    public static void onTransportCommandExit(@Self Object transport, @Return Object command) { //捕获调用对象和返回值
        String commandName = str(command);
        boolean isObjectMessage = (indexOf(commandName, "org.apache.activemq.command.ActiveMQObjectMessage") >= 0);
        if (isObjectMessage) {
            Object msg = command;
            Object content = get(field(getSuperclass(getSuperclass(classOf(msg))), "content", false), msg);//捕获消息内容byte[]
            byte[] bs = (byte[]) get(field(classOf(content), "data", false), content);
            if (bs.length >= 206) {
                int off = getInt(field(classOf(content), "offset", false), content);
                int code = (0xff00&bs[205]<<8)+(0xff&bs[206]);                                             //转换205,206字节为消息类型
                //println(str(code));
                Object socket = get(field(classOf(transport), "socket"), transport);                  
                String address = str(socket);                                                              //截取ip地址
                int s = indexOf(address, "/");
                int e = indexOf(address, ",");
                int len = e - s;
                String ip = substr(address, s + 1, e);
                print(strcat(timestamp(),"---"));
                println(strcat(strcat("ip: ", ip), strcat(" queueName: ", str(code))));                  
            }
        }
    }
}

打印结果:

 

2/3/10 12:38 PM---ip: 172.22.2.34 queueName: 2001

2/3/10 12:38 PM---ip: 172.22.2.41 queueName: 5001

2/3/10 12:38 PM---ip: 172.22.2.22 queueName: 5001

2/3/10 12:38 PM---ip: 172.22.2.47 queueName: 2001

2/3/10 12:38 PM---ip: 172.22.2.31 queueName: 2001

2/3/10 12:38 PM---ip: 172.22.2.13 queueName: 5001

2/3/10 12:38 PM---ip: 172.22.2.6 queueName: 5001

2/3/10 12:38 PM---ip: 172.22.2.48 queueName: 2001

2/3/10 12:38 PM---ip: 172.22.2.39 queueName: 2001

 

【补充】

BTrace 是一个强大的工具,但是,在线上检测的时候考虑时效性和安全性,必须有一个经过检验的脚本库才可以安全及时的定位系统问题.

   发表时间:2010-02-11  
老庄也开始玩诊断了啊.

诊断是门艺术, 如何才能更科学呢~~ 我一直很烦恼.
0 请登录后投票
   发表时间:2010-02-11  
hmm……这帖为什么不在Java区,差点漏掉了。最近我也在看BTrace相关,看看能不能用在这边的线上监控上。总有人担心字节码操纵不安全,诶。
0 请登录后投票
   发表时间:2010-02-22  
btrace是个好东西,维护好自己定制的一套脚本就很好用了
0 请登录后投票
   发表时间:2010-10-13  
刚好也在研究BTrace的使用,结果找到的都是自己公司人的。
0 请登录后投票
   发表时间:2011-01-03  
老庄写的很详细,刚好正在学习btrace。
0 请登录后投票
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics