posts - 42,comments - 83,trackbacks - 0
                  前段時間,有同事跟我說客戶那邊有很多狀態為receive的message,這些message只有在JMS Server或weblogic Server充啟之后才能被消費。經過調查后,這個問題可能是weblogic的一個bug,當然也不排除跟具體環境有關的可能。下面我們來看看問題的根本原因是什么,這種分析有助我們更進一步理解weblogic JMS的實現。

                  首先我們看一下什么是receive,receive表示一個message已經被consumer消費,但服務端還沒有關于這個message的ack,所以消息不能從queue中刪除, 由于queue中的消息是point-2-point的,所以某個消息被標為receive后,這個消息自然不能被其他consumer消費。那么這個ack由誰負責發送給Server呢,什么時候發送呢?這些都由我們創建JMS Session時使用的Ack_mode決定,典型的ack-mode有如下兩種:
            auto-ack: 自動響應模式,consumer.receive()調用后,如果服務器端發現有可用的message,消息返回到客戶端JMS實現層,在消息返回給客戶前,由weblogic client(JMSSession.getAsyncMessageForConsumer(),異步接受,比如MessageListener,或JMSSession.receiveMessage(),同步接受)層實現直接調用acknowledge()通知服務器端,服務器端收到ack后,它會負責負責將處于receive的message從物理queue中刪除。
                  client-ack: 客戶響應模式,consumer.receive()調用后,客戶端收到消息后,客戶端程序決定什么時候發送ack,可以在消息后立即發送,也可以在消息處理成功后發送,ack的發送通過message.acknowledge()實現。后面的過程和auto-ack相同。

            初看這個問題,感覺是ack沒有收到,那么什么情況下會出現ack丟失呢?網絡問題? 那么客戶端或服務器端的server log應該能夠看到異常,客戶堅持說沒有任何異常。有點不可思議,要了客戶的代碼,他們沒有代碼,實際上他們的應用是基于Spring Framework的,通過簡單的配置來實現他們的業務需要,看了下Spring的相關代碼,客戶之所以說沒有異常,因為Spring catch了服務器端返回的JMSException,并吃掉了這個異常(即異常沒有打印出來),這個異常輸出是可以通過Spring的配置來實現。客戶配置后,給了我具體的異常,如下:
          java.lang.IllegalArgumentException: Delay is negative.
                  at weblogic.timers.internal.TimerManagerImpl.schedule(TimerManagerImpl.java:388)
                  at weblogic.timers.internal.TimerManagerImpl.schedule(TimerManagerImpl.java:340)
                  at weblogic.messaging.kernel.internal.ReceiveRequestImpl.<init>(ReceiveRequestImp l.java:98)
                  at weblogic.messaging.kernel.internal.QueueImpl.receive(QueueImpl.java:820)
                  at weblogic.jms.backend.BEConsumerImpl.blockingReceiveStart(BEConsumerImpl.java:1 172)
                  at weblogic.jms.backend.BEConsumerImpl.receive(BEConsumerImpl.java:1383)
                  at weblogic.jms.backend.BEConsumerImpl.invoke(BEConsumerImpl.java:1088)
                  at weblogic.messaging.dispatcher.Request.wrappedFiniteStateMachine(Request.java:7 59)
                  at weblogic.messaging.dispatcher.DispatcherImpl.dispatchAsyncInternal(DispatcherI mpl.java:129)
                  at weblogic.messaging.dispatcher.DispatcherImpl.dispatchAsync(DispatcherImpl.java :112)
                  at weblogic.messaging.dispatcher.Request.dispatchAsync(Request.java:1046)
                  at weblogic.jms.dispatcher.Request.dispatchAsync(Request.java:72)
                  at weblogic.jms.frontend.FEConsumer.receive(FEConsumer.java:557)
                  at weblogic.jms.frontend.FEConsumer.invoke(FEConsumer.java:806)
                  at weblogic.messaging.dispatcher.Request.wrappedFiniteStateMachine(Request.java:7 59)
                  at weblogic.messaging.dispatcher.DispatcherServerRef.invoke(DispatcherServerRef.j ava:276)
                  at weblogic.messaging.dispatcher.DispatcherServerRef.handleRequest(DispatcherServ erRef.java:141)
                  at weblogic.messaging.dispatcher.DispatcherServerRef.access$000(DispatcherServerR ef.java:36)
                  at weblogic.messaging.dispatcher.DispatcherServerRef$2.run(DispatcherServerRef.ja va:112)
                  at weblogic.work.ExecuteThread.execute(ExecuteThread.java:209)
                  at weblogic.work.ExecuteThread.run(ExecuteThread.java:181)

            現在我們看一下Weblogic JMS的receive的基本流程,看看這個exception為什么會被拋出來。
            JMSConsumer.receive(long timewait),客戶端發起receive請求,其中timewait可有可無,不做指定的話,說明沒有可用消息到達的話,我們會一直等下去。如要不作等待的話,可以使用receiveNoWait()。receive()中會檢查timeout值,如果沒有指定timeout,那么Long.maxValue會被設定成這個timeout,如果timeout小于0,客戶端將會收到Invalid Timeout異常,接下來請求會被delegate到JMSSession。
                  |
                  JMSSession.receiveMessage(consumer,timeout),這里timeout會被重新計算,然后我們會創建一個FEConsumerReceiveRequest對象。這個對象中包含計算后的timeout,計算后的timeout應該是個非負值(上面的異常就是這里的計算導致的,至于為什么客戶指定的timeout為1,計算后的timeout變成了負數,從而導致上面的異常,從代碼層面,看不出有什么問題)。FEConsumerReceiveRequest對象創建后,由JMS FrontEnd Dispatcher負責把請求交給后端的JMS Server,Dispatcher是Weblogic JMS中用于負責請求傳輸的,它依賴于RJVM layer,這里不做贅述。
                  |     
                  RJVM layer, 負責RMI socket層的數據發送   
                  |        
                  FEConsumer.receive(invocableRequest),RJVM層處理完socket數據后,請求會被轉給JMSConsumer,JMSConsumer通過狀態機(state machine)來控制請求處理,沒有過多的邏輯,它會基于收到的receive request創建一個BEConsumerReceiveRequest對象,然后把這個請求通過JMS BackEnd Dispatcher轉發給BEConsumerImpl。之所以存在FrontEnd /BackEnd Dispatcher,主要考慮到處理請求的server和queue所在的不是同一server。
                  |
                  BEConsumerImpl.receive(request),request進入BEConsumerImpl后,它也通過state machine來控制請求處理,下面兩個方法在調用過程中被順序調用,
                  BEConsumerImpl.blockingReceiveStart(request),這里首先檢查timeout值,然后調用QueueImpl.receive(...)從queue中獲取message,receive()的具體參數如下,包括timeout, expression(即檢查條件,我們定義的message selector就在其中)。
                  BEConsumerImpl.blockingReceiveProcessMessage(request)
                  BEConsumerImpl.blockingReceiveComplete(request)
                  |
                  QueueImpl.receive(expression,count,acknowledge,owner,timeout,started,userBlob),這里除了狀態檢查,沒有其他邏輯,它會根據傳進來的參數,初始化一個ReceiveRequestImpl對象。
                  |
                  ReceiveRequestImpl.new(),這個new代表ReceiveRequestImpl的構造函數。
                  |
                  QueueImpl.get(...),如果timeout = 0,即如果客戶調用的是receiveNoWait的話,我們直接去通過QueueImpl.get(...),如果沒有match的message,那么直接將新建request的result設定為no result,否則將match的message設定為result。
                  QueueImpl.addReader(receiveRequestImpl),如果timeout != 0,我們會在ReceiveRequestImpl.start()中調用QueueImpl.addReader(),addReader()中同樣會通過QueueImpl.get()檢查是否有match的message,如果找到相應的message,我們會把message reference狀態改為receive。
                  TimerManagerImpl.schedule(timeout),如果QueueImpl.addReader()中的QueueImpl.get()沒有找到相應的message,我們需要等待(依據客戶指定的timeout),這個等待通過timer去實現,如下:
                          timer = timerManager.schedule(this, timeout);
            指定的timeout到達后,如果和沒有可用的message,no result將被返回。從上面的異常堆棧來看,問題就出在這里,如果timeout為負數,timerMangerImpl在啟動trigger的時候,會拋出如下的runtimeException,
              java.lang.IllegalArgumentException: Delay is negative.
                  
            也許你會疑問,這沒什么問題吧,timerTrigger只有在沒有message的時候才會被schedule,既然沒有message,那有談何狀態receive message?沒錯,起timerTrigger之前我們的確沒有修改message狀態,但你注意到沒有,我們在起timerTrigger前,把receiveRequestImpl加入到QueueImpl去了,但我們在碰到IllegalArgumentException時并沒有把這個receiveRequestImpl從QueueImpl中刪除,問題就在這里。

           1   synchronized void addReader(Reader reader) throws KernelException {
           2             
           3     List list = get(..);
           4     int newCount;
           5     if (list != null) {
           6             
           7     } else {
           8       reader.incrementReserveCount(-reservedCount);
           9       newCount = reader.getCount();
          10     }
          11     if (newCount > 0) {
          12       logger.debug("Adding consumer to reader list");
          13       readerList.add(reader);
          14     }
          15   }

            如果我們不把receiveRequestImpl從QueueImpl的readerList中刪除,那么如果過一會有message sender發送一條和我們上述請求match的message到這個queue。weblogic收到這個message后,它會檢查readerList,如果這個message match某個reader,我們會把message狀態改成receive,當由于IllegalArgumentException,客戶端收到它的時候,客戶端會close JMSSession,也就是說這個消息雖然有reader,但無法deliver到客戶端。

            我們再來看看Weblogic JMS sender的相關流程,
            QueueImpl.messageSendComplete(),消息發送過程結束后(比如涉及store的話,消息此時已經被存儲),到這一步的話,我們會調整系統接受的消息數,然后通過makeMessageAvailable()把消息標成visiable或deliver給正在等待的reader。
                  QueueImpl.makeMessageAvailable(),它會直接調用match()去檢查readerList中是否存在正在等待它的reader。
                  QueueImpl.match(),它通過finderReader()從readerList中檢查reader,如果有符合條件的reader,它會把這個message標志為receive,同時把這個message挪到pending list中去。

            前面我們說了,雖然reader還在,但與之對應的JMSConsumer已經被close,所以這個消息根本就無法deliver出去,自然就不會有ack從客戶端返回了,這個消息也只能一直pending了。

            這個問題可能是個bug,目前還在確認之中,但我同時也在和客戶溝通,可能跟他的環境有一定關系(比如NTP時間同步問題)。

            雖然這個問題能引發message pending,但并不是所有的message pending問題都是由它應起的。網絡問題也能引發類似問題,具體問題具體分析,主要的參考客戶端的JMSException。位于receive后的狀態是transation,也就是如果發現狀態為transaction的message的話,一般而言,是這個消息要么就是發送還沒結束,要么就是消息正處于一個delete的事務單元中,這里就不再一一羅列了。

          posted on 2009-06-17 09:07 走走停停又三年 閱讀(3881) 評論(9)  編輯  收藏 所屬分類: Weblogic

          FeedBack:
          # re: 關于JMS Message Pending的問題
          2009-06-16 20:46 | sunnycare
          最近項目中正好用到了JMS。
          看了你的blog,有兩個問題問下:
          1.如何重現這個問題?
          2.既然沒有patch出來,那么有什么別的方式避免這個問題?  回復  更多評論
            
          # re: 關于JMS Message Pending的問題
          2009-06-16 21:16 | 走走停停又三年
          這個問題基本很難重現,原因很可能跟系統環境有關系。weblogic在JMSSession中計算timeout的時候,參考了System.currentTimeMills(),如果系統起了NTP client定期做時間同步的話,可能會在計算的時候引起負值。如果真跟系統時間有關,那么最好的做法就是保證客戶端運行期間,不要做系統時間同步。

          另外一個客戶端回避的方法就是客戶端使用receiveNoWait()來代替receive()或receive(long timeout)。  回復  更多評論
            
          # re: 關于JMS Message Pending的問題
          2009-06-17 18:26 | sunnycare
          3x....  回復  更多評論
            
          # re: 關于JMS Message Pending的問題
          2009-06-19 12:33 | 找個美女做老婆
          JBOSS 和 SPRING 的JMS 用過,WEBLOGIC的還沒有用過

          Java樂園 技術交流社區:http://www.javaly.cn
          Java樂園 群號:15651281
          驗證消息 : Java樂園  回復  更多評論
            
          # re: 關于JMS Message Pending的問題
          2009-08-03 11:22 | ybb
          我這里碰到一個奇詭的問題:

          環境 jdk1.6, weblogic 10.3

          配置了一個 TestQueue。

          當我們使用 MDB方式接收Queue消息時,如果拋出運行時異常,這個消息會自動重發。

          如果客戶端是采用consumer messageListener 的形式接收,當 listener.onMessage() 拋出運行時異常時,這個消息的status String 一直是 receive 狀態,只有重啟客戶端才會重新收到一次。
          代碼如下:
          QueueSession session = connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
          MessageConsumer receiver = session.createConsumer(sq);
          receiver.setMessageListener(new Receiver());

          難道messageListener形式不能像MDB一樣的自動重發?

          找了很多資料,一點思路都沒了。


            回復  更多評論
            
          # re: 關于JMS Message Pending的問題
          2009-08-03 11:55 | 走走停停又三年
          什么樣的runtime exception呢? 有可能是你的acknowledege可能由于網絡問題沒有發過去,導致message處于receive,所以不會重新發送。建議你用client_acknowledge,等你消息成功處理后直接調用message.acknowledge()。  回復  更多評論
            
          # re: 關于JMS Message Pending的問題
          2009-08-03 13:26 | ybb
          運行時異常,比如拋出空指針異常。
          我的網絡環境是局域網,acknowledege 肯定能收到的。
          所有在客戶端onMessage中拋出運行時異常的message 都是 receive狀態。

          但在ejb MDB中,如果出現運行時異常,這消息會等待10、20秒后重發。

          如果我自行處理 acknowledege,如何讓message自動重發呢?

            回復  更多評論
            
          # re: 關于JMS Message Pending的問題
          2009-08-03 13:30 | ybb
          能否 交流一下?

          我的msn: ybbkd2@hotmail.com , qq:11898620

          期待  回復  更多評論
            
          # re: 關于JMS Message Pending的問題
          2009-09-02 10:00 | 走走停停又三年
          對于系統時間問題,我們可以用下面的小程序測試一下,

          public class JVMTimeTest {

          public static void main(String args[]){
          JVMTimeTest test = new JVMTimeTest();
          test.retriveTime();
          }

          private void retriveTime(){
          long previousTime = -1;
          while(true){
          long currentTime = System.currentTimeMillis();
          System.out.println("currentTime is: " + currentTime);
          if(previousTime > currentTime)
          System.out.println("OS time change is detected! and:" +
          "previousTime: " + previousTime + " " +
          "currentTime: " + currentTime);
          previousTime = currentTime;
          try{
          Thread.currentThread().sleep(100);
          }catch(Exception e){}
          }
          }
          }
            回復  更多評論
            
          主站蜘蛛池模板: 阳春市| 安仁县| 称多县| 福泉市| 阜南县| 龙南县| 剑阁县| 石楼县| 海口市| 贡觉县| 靖远县| 河间市| 黑河市| 时尚| 尉犁县| 类乌齐县| 德令哈市| 曲麻莱县| 井研县| 遂平县| 乌兰浩特市| 武平县| 泽库县| 天津市| 青铜峡市| 凤冈县| 乡宁县| 乐东| 饶平县| 大同市| 湘阴县| 吴堡县| 凉城县| 玉环县| 石城县| 大田县| 茌平县| 罗定市| 阳新县| 巢湖市| 平度市|