往事如風
          記錄工作中的點點滴滴 留住那些淡淡的回憶
          posts - 6,  comments - 3,  trackbacks - 0

          BTrace 說明】 http://kenai.com/projects/btrace
          是一個實時監(jiān)控工具,使用了 java agent jvm attach 技術,可以在不停機的情況下實時監(jiān)控線上程序的運行情況,另外,對 btrace 腳本(實際上就是 java 程序)做了非常嚴格的安全限制,安全性很高,對應用程序基本沒有影響。在性能方面, cobar 進行過測試,對方法進行調用耗時統(tǒng)計的時候,基本消費在微秒級別,可以說微不足道。

          【背景】

          在中文站 napoli 上線過程后,發(fā)現(xiàn)了一個奇怪的現(xiàn)象,盡管"已知"的 offer 發(fā)送端都已經遷移到
          napoli 系統(tǒng)中,但是老的 mq 系統(tǒng)仍然有新的 offer 消息進來,因為連接 mq 的服務器非常多,定位消息來源成了一個非常大的問題。這種情況,想到了使用 BTrace 在某一臺服務器進行線上監(jiān)控進而期望發(fā)現(xiàn)這個幽靈。

          【過程】

          首先,我們需要知道兩個基本信息:消息類型和來源 ip ,這樣才可以定位 offer 消息的來源。

          要知道來源 ip ,需要找到服務器端 管理的類,只有在建立 socket 的地方,才可以抓到具體 ip ,經過分析 amq 代碼,發(fā)現(xiàn) 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 對象的成員變量,所有我們只要監(jiān)控 readCommand 方法,這個方法的返回值實際上就是一個 ActivemqObjectMessage 對象,這樣就可以在一個方法上加攔截器就可以同時捕獲到 ip 和消息對象,兩全其美!!!

          protected Object readCommand() throws IOException {

          return wireFormat.unmarshal(dataIn);

          }

          因為原有 ESB 消息通道都是一個隊列 ESBQueue ,所以無法通過隊列名稱來確定消息類型,必須通過
          ESBTransferObject 對象來取得消息類型: destType offer 的區(qū)間是 1000-1008

          public class ESBTransferObject implements Serializable {

          private static final long serialVersionUID = -5975115234845303878L;

          /**

          * 消息體,原則上對象序列化后的XML數(shù)據(jù)(String) 注意使用XML1.1規(guī)范。

          */

          private Object content;

          /**

          * 用戶自定義數(shù)據(jù)

          */

          private Object userDefineData;

          /**

          * 目的消息類型

          */

          private int destType = -1;


          但是,在服務器端并沒有 ESBTransferObject 對象,無法反序列化( BTrace 也不支持反序列化操作),所以沒有方法簡單取得消息類型信息!!!

          OK ,我不反序列化,直接拿二進制 byte[] ,類型信息應該是在固定位置的吧?但是發(fā)現(xiàn)這個對象
          content 變長字符串定義在類型之前,類型位置不確定了,暈倒啊
          不死心,輸出二進制數(shù)據(jù),柳暗花明啊,原來對象序列化的時候, primitive field 都是緊接著類型信息寫入的,所以,類型信息是在固定位置的 ,類型信息始終是 255 256 兩個字節(jié)(實際上是 4 個字節(jié),但是目前我們只占有 2 個) Ok ,編寫代碼,測試環(huán)境運行一下,暈倒,竟然有數(shù)組溢出!

          使用 BTrace ,把這個數(shù)組打印下來(這個需要點技巧, btrace for 都不允許),竟然發(fā)現(xiàn) 位置偏移到 205 206 位置 ,這個真的不知道什么原因,估計是客戶端發(fā)送的時候壓縮了,簡單修改偏移量,測試運行, 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字節(jié)為消息類型

          //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 是一個強大的工具,但是,在線上檢測的時候考慮時效性和安全性,必須有一個經過檢驗的腳本庫才可以安全及時的定位系統(tǒng)問題.

          posted on 2010-07-14 19:18 井底青蛙,常望天空 閱讀(375) 評論(0)  編輯  收藏 所屬分類: javatools

          <2010年7月>
          27282930123
          45678910
          11121314151617
          18192021222324
          25262728293031
          1234567

          常用鏈接

          留言簿

          隨筆分類

          隨筆檔案

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 大荔县| 大竹县| 西乌珠穆沁旗| 兰考县| 栾川县| 武宁县| 梁山县| 塘沽区| 句容市| 合山市| 兴义市| 宽甸| 永济市| 仙居县| 花垣县| 易门县| 威宁| 新竹市| 澄城县| 西充县| 株洲县| 象州县| 滁州市| 高平市| 侯马市| 社会| 安西县| 稷山县| 长兴县| 桦甸市| 阳泉市| 和硕县| 景德镇市| 霍林郭勒市| 平山县| 元阳县| 珲春市| 浦北县| 三江| 亳州市| 广饶县|