往事如風(fēng)
          記錄工作中的點(diǎn)點(diǎn)滴滴 留住那些淡淡的回憶
          posts - 6,  comments - 3,  trackbacks - 0

          標(biāo)題黨,哈哈,OK,言歸正傳。
          最近兩天被一個(gè)故障搞死了,因?yàn)樵瓉硪粋€(gè)報(bào)警腳本是shell寫的,從一個(gè)java程序的jmx接口抓取信息并匯總發(fā)到監(jiān)控系統(tǒng),個(gè)人shell腳本能力比較稀松,在python牛人建議下,使用python寫了一個(gè)腳本,發(fā)覺非常爽,我這個(gè)菜鳥(學(xué)習(xí)經(jīng)歷1天)簡(jiǎn)單就能編寫出來一個(gè)復(fù)雜的腳本,比shell的簡(jiǎn)化將近5倍,晚上就得意洋洋的向一個(gè)同學(xué)吹噓ing,結(jié)果被BS:語(yǔ)法甜點(diǎn)而已,java也可以輕松做出來,而且也能做的那么精干。。

          回來看了一下java如果要實(shí)現(xiàn)這些功能,代碼可能比shell還要多,看來這個(gè)領(lǐng)域?qū)嵲诓皇莏ava的專長(zhǎng)。
          我以前也認(rèn)為語(yǔ)法甜點(diǎn)確認(rèn)只是錦上添花,但是使用了python之后,發(fā)現(xiàn)自己以前還是偏見啊,在特定環(huán)境下,如腳本、頁(yè)面等情況下,語(yǔ)法甜點(diǎn)可以大大減少輸入量和代碼出錯(cuò)可能性,動(dòng)態(tài)語(yǔ)言還是我們工具箱不可缺少的工具。

          posted @ 2010-07-14 19:18 井底青蛙,常望天空 閱讀(180) | 評(píng)論 (0)編輯 收藏

          最近在做一個(gè)項(xiàng)目,綜合使用了jboss的microcontainer,jetty和自己定義的war包,war包中還會(huì)用到spring,因?yàn)闋可娴蕉鄠€(gè)容器,同時(shí)又有自己的自定義類,所以classloader環(huán)境異常復(fù)雜,ClassNotFound問題搞得頭都大了,最后綜合各種因素,設(shè)計(jì)了如下的一個(gè)classloader層次:

          cl.png

          其中,紅色部分是系統(tǒng)(也就是啟動(dòng)java程序加載Main函數(shù)的classloader),主要的設(shè)計(jì)考量有以下幾點(diǎn):

          1、使用自定義的ExtClassLoader(加載java的ext目錄下的jar包)把程序加載的class完全和系統(tǒng)加載的class隔離開,這樣即使在eclipse容器中啟動(dòng)都不會(huì)有類沖突。

          為什么不從系統(tǒng)的ExtClassLoader作為自定義classloader數(shù)的根有兩個(gè)考慮,第一個(gè)是系統(tǒng)ExtClassLoader有可能不存在,第二個(gè)就是如果使用同一個(gè)ExtClassLoader中,在處理JNDI、XML和URL解析等java擴(kuò)展功能時(shí)會(huì)遇到后加載的handler部分導(dǎo)致不同classloader樹加載的同一個(gè)類的ClassCastException,具體參見這些模塊的源代碼。

          2、WarClassLoader除了系統(tǒng)類和Common類(目前只有l(wèi)og相關(guān)類)以外的類都從war包的WEB-INFO和classes下加載。

          3、所有執(zhí)行War包中代碼的線程ThreadContextClassLoader都設(shè)置為WarClassLoader,以供Spring和Webx中的相關(guān)工具類使用這個(gè)classloader結(jié)構(gòu)的后門來加載war包中的類,典型例子是Webx中ResourceLoaderService就是使用ContextClassLoader來加載類的。

          4、RialtoClassLoader也就是這個(gè)項(xiàng)目的容器加載器和WarClassLoader不在同一個(gè)樹路徑上,可以避免程序使用類和war使用類的class沖突,典型的是Spring容器相關(guān)代碼。

          5、CommonClassLoader加載的類需要嚴(yán)格控制,否則可能會(huì)導(dǎo)致運(yùn)行期類沖突,例如Spring的相關(guān)jar包絕對(duì)不可以出現(xiàn)在這個(gè)classloader作用范圍內(nèi)。

          總之,ClassLoader采用父分派機(jī)制,后來增加的Thread ContextClassLoader在這個(gè)體系上增加了一個(gè)后門,帶來了靈活性,也帶來了很多令人困擾的問題,在做容器類的項(xiàng)目時(shí)難免會(huì)遇到class loader層次設(shè)計(jì)的問題,這里拋磚引玉,歡迎達(dá)人拍磚。

          posted @ 2010-07-14 19:18 井底青蛙,常望天空 閱讀(1070) | 評(píng)論 (0)編輯 收藏

          ActiveMQ是一個(gè)流行的開源MQ,我們也大規(guī)模應(yīng)用在網(wǎng)站的方方面面,每天處理上億消息,取得了較好效果。ActiveMQ有一個(gè)很好很強(qiáng)大的插件體系,提供了很強(qiáng)的擴(kuò)展能力,ActiveMQ本身就是使用這一套插件體系實(shí)現(xiàn)了很多擴(kuò)展功能,包括他的權(quán)限管理,日志管理,事務(wù)等模塊都是作為一個(gè)插件集成的,我們自己也在消息路由、補(bǔ)償式事務(wù)方面使用了它的插件功能,確實(shí)非常方便。

          在ActiveMQ中,Broker代表一個(gè)運(yùn)行的MQ節(jié)點(diǎn),ActiveMQ的插件實(shí)際上是基于Broker的一個(gè)Filter鏈,整個(gè)設(shè)計(jì)類似于servlet的Filter結(jié)構(gòu),所有的Plugin構(gòu)成一個(gè)鏈?zhǔn)浇Y(jié)構(gòu),每個(gè)插件實(shí)際上都是一個(gè)"Interceptor",類結(jié)構(gòu)圖如下:

          Main.jpg

          其中Broker接口封裝了一個(gè)AMQ節(jié)點(diǎn)的方方面面的方法,包括連接管理、session管理、消息的發(fā)送和接收以及其它的一些功能,BrokerFilter實(shí)現(xiàn)這個(gè)接口,并提供了鏈?zhǔn)浇Y(jié)構(gòu)支持,可以攔截所有Broker方法的實(shí)現(xiàn)并傳遞結(jié)果給鏈?zhǔn)浇Y(jié)構(gòu)的下一個(gè),形成了一個(gè)完整的"職責(zé)鏈"模式,具體層次關(guān)系如下,其中,"System Plugin"是指AMQ內(nèi)部使用Plugin機(jī)制實(shí)現(xiàn)的一些系統(tǒng)功能,用戶不能定制,"AMQ Plugin"指的是ActiveMQ已經(jīng)實(shí)現(xiàn)好了,可以在配置文件中自由選擇的一些插件,例如簡(jiǎn)單的安全插件,JAAS安全插件和DLQ插件等等,用戶插件就是指用戶自己實(shí)現(xiàn)的amq插件,需要用戶把相關(guān)jar包放入到amq的啟動(dòng)classpath中,并在配置文件中進(jìn)行配置才能正確加載的插件。

          圖片1.jpg

          在上面這個(gè)層次結(jié)構(gòu)中,最下面的RegionBroker是核心組件,在其之上的都是Broker的插件,繼承之于BrokerFilter,和Broker保持接口兼容但是擴(kuò)展Broker的功能。

          下面舉一個(gè)簡(jiǎn)單的例子,具體說明一下AMQ的插件是如何工作的。

          我們?cè)谑褂肁MQ的過程中發(fā)現(xiàn),在測(cè)試環(huán)境維護(hù)方面有很大的麻煩,具體表現(xiàn)在很多同學(xué)在測(cè)試項(xiàng)目的時(shí)候往往只關(guān)注自己項(xiàng)目牽涉的隊(duì)列,不會(huì)去消費(fèi)其他"不相關(guān)"的隊(duì)列,這樣導(dǎo)致的一個(gè)問題就是ActiveMQ經(jīng)常發(fā)生大量數(shù)據(jù)阻塞,導(dǎo)致測(cè)試環(huán)境不可用,影響相關(guān)項(xiàng)目的測(cè)試工作。為了避免這個(gè)問題,我們假定在測(cè)試環(huán)境可以定義以下一些限制條件:

          1、 所有隊(duì)列堆積消息不超過1000條,超過之后立即清除。

          2、 消息超過1個(gè)小時(shí)沒有消費(fèi),就直接過期。

          我們可以編寫一個(gè)簡(jiǎn)單的amq插件來完成這兩個(gè)限制條件:

          首先,編寫一個(gè)插件安裝類:

          package com.alibaba.napoli.plugins;

          import org.apache.activemq.broker.Broker;
          import org.apache.activemq.broker.BrokerPlugin;
          import org.apache.commons.logging.Log;
          import org.apache.commons.logging.LogFactory;

          public class MessageControlBrokerPlugin implements BrokerPlugin {
          private static Log log = LogFactory.getLog(StatisticsBrokerPlugin.class);

          public Broker installPlugin(Broker broker) throws Exception {
          log.info("install MessageControlBrokerPlugin");
          return new MessageControlBroker(broker);
          }
          }

          其次,編寫真正的插件實(shí)現(xiàn):

          package com.alibaba.napoli.plugins;

          import java.io.IOException;

          import org.apache.activemq.broker.Broker;
          import org.apache.activemq.broker.BrokerFilter;
          import org.apache.activemq.broker.ConnectionContext;
          import org.apache.activemq.broker.ProducerBrokerExchange;
          import org.apache.activemq.broker.region.Destination;
          import org.apache.activemq.broker.region.MessageReference;
          import org.apache.activemq.broker.region.Queue;
          import org.apache.activemq.command.Message;
          import org.apache.commons.logging.Log;
          import org.apache.commons.logging.LogFactory;

          /**
          * 開發(fā)環(huán)境管理插件,符合兩個(gè)條件進(jìn)行消息清理:<br>
          * 1 消息累積超過1000條
          * 2 消息超過1個(gè)小時(shí)無人消費(fèi)
          * @author guolin.zhuanggl
          *
          */
          public class MessageControlBroker extends BrokerFilter {
          public static Log log = LogFactory.getLog(DiscardingDLQBroker.class);
          private static final long DEFAULT_EXPIRATION = 3600*1000;
          private static final long DEFAULT_PURGE_COUNT = 1000;

          public MessageControlBroker(Broker next) {
          super(next);
          }

          @Override
          public void messageExpired(ConnectionContext context,
          MessageReference message) {

          Message msg = null;
          try {
          msg = message.getMessage();
          } catch (IOException e) {
          log.error("failed to fetch content: ",e);
          }
          purgeMessage(msg);
          // TODO Auto-generated method stub
          super.messageExpired(context, message);
          }

          /**
          * 清除隊(duì)列中的所有消息
          */
          private void purgeMessage(Message message){
          Destination r = message.getRegionDestination();
          if(r instanceof Queue){
          try {
          //如果累積消息超過1000個(gè),清除隊(duì)列消息
          if(((Queue) r).getMessages().size() > DEFAULT_PURGE_COUNT){
          ((Queue) r).purge();
          }
          } catch (Exception e) {
          // TODO Auto-generated catch block
          log.error("failed to purge queue "+r.getName(),e);
          }
          }

          }
          /**
          * 當(dāng)消息發(fā)送時(shí),全部設(shè)置過期時(shí)間1個(gè)小時(shí),測(cè)試環(huán)境專用!!!
          */
          @Override
          public void send(ProducerBrokerExchange producerExchange,Message messageSend) throws Exception {
          long oldExp = messageSend.getExpiration();
          messageSend.setExpiration(oldExp < DEFAULT_EXPIRATION && oldExp > 0 ? oldExp : DEFAULT_EXPIRATION );
          purgeMessage(messageSend);
          super.send(producerExchange, messageSend);
          }

          }

          然后,將這兩個(gè)類打包為myplugin.jar,并放在activemq啟動(dòng)目錄下的lib目錄下

          最后,在activemq.xml文件中增加一個(gè)簡(jiǎn)單的spring配置項(xiàng):

          <bean xmlns=" id="purgePlugin"
          class="com.alibaba.napoli.plugins.MessageControlBrokerPlugin">
          </bean>

          然后,重啟activemq,就會(huì)發(fā)現(xiàn)這個(gè)插件已經(jīng)被加載。

          posted @ 2010-07-14 19:18 井底青蛙,常望天空 閱讀(1984) | 評(píng)論 (3)編輯 收藏

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

          【背景】

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

          【過程】

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

          要知道來源 ip ,需要找到服務(wù)器端 管理的類,只有在建立 socket 的地方,才可以抓到具體 ip ,經(jīng)過分析 amq 代碼,發(fā)現(xiàn) tcp 連接基本是由下面這個(gè)類來服務(wù)所有消息的接收的:

          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;


          這個(gè)類中包含一個(gè) socket 對(duì)象的成員變量,所有我們只要監(jiān)控 readCommand 方法,這個(gè)方法的返回值實(shí)際上就是一個(gè) ActivemqObjectMessage 對(duì)象,這樣就可以在一個(gè)方法上加攔截器就可以同時(shí)捕獲到 ip 和消息對(duì)象,兩全其美!!!

          protected Object readCommand() throws IOException {

          return wireFormat.unmarshal(dataIn);

          }

          因?yàn)樵? ESB 消息通道都是一個(gè)隊(duì)列 ESBQueue ,所以無法通過隊(duì)列名稱來確定消息類型,必須通過
          ESBTransferObject 對(duì)象來取得消息類型: destType offer 的區(qū)間是 1000-1008

          public class ESBTransferObject implements Serializable {

          private static final long serialVersionUID = -5975115234845303878L;

          /**

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

          */

          private Object content;

          /**

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

          */

          private Object userDefineData;

          /**

          * 目的消息類型

          */

          private int destType = -1;


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

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

          使用 BTrace ,把這個(gè)數(shù)組打印下來(這個(gè)需要點(diǎn)技巧, btrace for 都不允許),竟然發(fā)現(xiàn) 位置偏移到 205 206 位置 ,這個(gè)真的不知道什么原因,估計(jì)是客戶端發(fā)送的時(shí)候壓縮了,簡(jiǎn)單修改偏移量,測(cè)試運(yùn)行, ok ,所有的消息類型和 ip 的對(duì)照表打印出來了。


          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)) //攔截位置,方法返回時(shí)

          public static void onTransportCommandExit(@Self Object transport, @Return Object command) { //捕獲調(diào)用對(duì)象和返回值

          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);//捕獲消息內(nèi)容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]); //轉(zhuǎn)換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))));

          }

          }

          }

          }


          打印結(jié)果:


          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


          【補(bǔ)充】

          BTrace 是一個(gè)強(qiáng)大的工具,但是,在線上檢測(cè)的時(shí)候考慮時(shí)效性和安全性,必須有一個(gè)經(jīng)過檢驗(yàn)的腳本庫(kù)才可以安全及時(shí)的定位系統(tǒng)問題.

          posted @ 2010-07-14 19:18 井底青蛙,常望天空 閱讀(374) | 評(píng)論 (0)編輯 收藏

          今天公司DNS切換,結(jié)果napoli這邊收到大量報(bào)警,這就奇怪了,數(shù)據(jù)都是正確的,報(bào)警的結(jié)果確都是錯(cuò)誤的,調(diào)試了一些腳本,發(fā)現(xiàn)有這個(gè)奇怪的文本" Binary file (standard input) matches "原來grep把輸入數(shù)據(jù)臨時(shí)文件當(dāng)成是二進(jìn)制文件了,這里加 -a 就可以解決這個(gè)問題。

          但是,為什么文本文件會(huì)被當(dāng)成是二進(jìn)制文件?和今天的DNS切換有什么關(guān)系?分析后發(fā)現(xiàn),因?yàn)閐ns的問題,抓數(shù)據(jù)的腳本執(zhí)行時(shí)間明顯變長(zhǎng),這樣,在文件還在寫入的時(shí)候,監(jiān)控腳本就開始讀取數(shù)據(jù)文件,在這樣的并發(fā)訪問下,grep會(huì)認(rèn)為自己正在訪問一個(gè)binary文件,導(dǎo)致監(jiān)控誤報(bào)警。

          posted @ 2010-07-14 19:17 井底青蛙,常望天空 閱讀(174) | 評(píng)論 (0)編輯 收藏

          最近在線上部署的ActiveMQ發(fā)生一次故障,因?yàn)橐慌_(tái)ActiveMQ故障將前臺(tái)的關(guān)鍵應(yīng)用全部連接掛住,根本原因有兩條:session的timeout設(shè)置不合理以及session池沒有限制大小。這里說的不是這個(gè)問題,而是在后續(xù)設(shè)置client的timeout過程中,有同學(xué)發(fā)現(xiàn)AMQ有一個(gè)嚴(yán)重的bug,timeout根本不起作用!!!

          調(diào)試代碼發(fā)現(xiàn):

          fr.png

          在Activemq的send response處理中,使用了一個(gè)BlockingQueue,在有timeout的方法里,使用了poll方法,這個(gè)方法的api說明中指出,當(dāng)timeout發(fā)生時(shí),這個(gè)方法返回null!!!

          我們?cè)诳碅MQ經(jīng)過層層調(diào)用后,在ActiveMQConnection方法中如何處理這個(gè)返回值:

          amc.png

          對(duì)返回值為空的情況沒有做任何處理,即使消息發(fā)送超時(shí),amq也認(rèn)為這個(gè)消息發(fā)送成功!估計(jì)這哥們理解poll在timeout的時(shí)候會(huì)拋出異常吧。

          解決辦法很簡(jiǎn)單,在response為空的時(shí)候,拋出JMSException,告知發(fā)生Timeout錯(cuò)誤。

          posted @ 2010-07-14 19:17 井底青蛙,常望天空 閱讀(182) | 評(píng)論 (0)編輯 收藏
          僅列出標(biāo)題  

          <2025年6月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          常用鏈接

          留言簿

          隨筆分類

          隨筆檔案

          搜索

          •  

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 元江| 濮阳市| 弋阳县| 搜索| 石狮市| 湟中县| 蓝山县| 大丰市| 察隅县| 通州市| 泸西县| 沿河| 泰顺县| 南康市| 荆州市| 德庆县| 双峰县| 红河县| 海口市| 湾仔区| 永靖县| 吉木萨尔县| 清原| 蓝田县| 南华县| 长汀县| 固原市| 三门峡市| 嘉善县| 兴宁市| 响水县| 弥渡县| 深水埗区| 桂东县| 剑阁县| 石柱| 花莲县| 象州县| 噶尔县| 武强县| 长岭县|