Dubbo源代碼分析八:再說Provider線程池被EXHAUSTED

轉自:http://manzhizhen.iteye.com/blog/2391177html

          在上回《Dubbo源代碼實現六》中咱們已經瞭解到,對於Dubbo集羣中的Provider角色,有IO線程池(默認無界)和業務處理線程池(默認200)兩個線程池,因此當業務的併發比較高,或者某些業務處理變慢,業務線程池就很容易被「打滿」,拋出「RejectedExecutionException: Thread pool is EXHAUSTED! 」異常。固然,前提是咱們每給Provider的線程池配置等待Queue。併發

          既然Provider端已經拋出異常,代表本身已經受不了了,但線上咱們卻發現,Consumer無動於衷,發出的那筆請求還在那裏傻傻的候着,直到超時。這樣極其容易致使整個系統的「雪崩」,由於它違背了fail-fast原則。咱們但願一旦Provider因爲線程池被打滿而沒法收到請求,Consumer應該當即感知而後快速失敗來釋放線程。後來發現,徹底是Dispatcher配置得不對,默認是all,咱們應該配置成message。app

         咱們從源碼角度來看看究竟是咋回事,這裏先假設咱們用的是Netty框架來實現IO操做,上回咱們已經提到過,NettyHandler、NettyServer、MultiMessageHandler、HeartbeatHandler都實現了ChannelHandler接口,來實現接收、發送、鏈接斷開和異常處理等操做,目前上面提到的這四個Handler都是在IO線程池中按順序被調用,但HeartbeatHandler調用後下一個Handler是?這時候就要Dispatcher來上場了,Dispatcher是dubbo中的調度器,用來決定操做是在IO中執行仍是業務線程池執行,來一張官方的圖(http://dubbo.io/user-guide/demos/線程模型.html):框架



 

上圖Dispatcher後面跟着的ThreadPool就是咱們所說的業務線程池。Dispatcher分爲5類,默認是all,解釋也直接參考官方截圖:異步


 

由於默認是all,因此包括請求、響應、鏈接、斷開和心跳都交給業務線程池來處理,則無疑加大了業務線程池的負擔,由於默認是200。每種Dispatcher,都有對應的ChannelHandler,ChannelHandler將Handler的調動造成調用鏈。若是配置的是all,那麼接下來上場的就是AllChannelHandler;若是配置的是message,那麼接下來上場的就是MessageOnlyChannelHandler,這些ChannelHandler都是WrappedChannelHandler的子類,WrappedChannelHandler默認把請求、響應、鏈接、斷開、心跳操做都交給Handler來處理:ide

protected final ChannelHandler handler;ui

public void connected(Channel channel) throws RemotingException {spa

    handler.connected(channel);線程

}htm

public void disconnected(Channel channel) throws RemotingException {

    handler.disconnected(channel);

}

public void sent(Channel channel, Object message) throws RemotingException {

    handler.sent(channel, message);

}

public void received(Channel channel, Object message) throws RemotingException {

    handler.received(channel, message);

}

public void caught(Channel channel, Throwable exception) throws RemotingException {

    handler.caught(channel, exception);

}

 

很顯然,若是直接使用WrappedChannelHandler的處理方式,那麼Handler的調用會在當前的線程中完成(這裏是IO線程),咱們看看AllChannelHandler內部實現:

public void connected(Channel channel) throws RemotingException {

    ExecutorService cexecutor = getExecutorService();

    try{

        cexecutor.execute(new ChannelEventRunnable(channel, handler ,ChannelState.CONNECTED));

    }catch (Throwable t) {

        throw new ExecutionException("connect event", channel, getClass()+" error when process connected event ." , t);

    }

}

public void disconnected(Channel channel) throws RemotingException {

    ExecutorService cexecutor = getExecutorService();

    try{

        cexecutor.execute(new ChannelEventRunnable(channel, handler ,ChannelState.DISCONNECTED));

    }catch (Throwable t) {

        throw new ExecutionException("disconnect event", channel, getClass()+" error when process disconnected event ." , t);

    }

}

public void received(Channel channel, Object message) throws RemotingException {

    ExecutorService cexecutor = getExecutorService();

    try {

        cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));

    } catch (Throwable t) {

        throw new ExecutionException(message, channel, getClass() + " error when process received event .", t);

    }

}

public void caught(Channel channel, Throwable exception) throws RemotingException {

    ExecutorService cexecutor = getExecutorService();

    try{

        cexecutor.execute(new ChannelEventRunnable(channel, handler ,ChannelState.CAUGHT, exception));

    }catch (Throwable t) {

        throw new ExecutionException("caught event", channel, getClass()+" error when process caught event ." , t);

    }

}

 

能夠看出,AllChannelHandler覆蓋了WrappedChannelHandler全部的關鍵操做,都將其放進到ExecutorService(這裏指的是業務線程池)中異步來處理,但惟一沒有異步操做的就是sent方法,該方法主要用於應答,但官方文檔卻說使用all時應答也是放到業務線程池的,寫錯了?這裏,關鍵的地方來了,一旦業務線程池滿了,將拋出執行拒絕異常,將進入caught方法來處理,而該方法使用的仍然是業務線程池,因此頗有可能這時業務線程池仍是滿的,因而悲劇了,直接致使下游的一個HeaderExchangeHandler沒機會調用,而異常處理後的應答消息正是HeaderExchangeHandler#caught來完成的,因此最後NettyHandler#writeRequested也沒有被調用,Consumer只能死等到超時,沒法收到Provider的線程池打滿異常。

          從上面的分析得出結論,當Dispatcher使用all時,一旦Provider線程池被打滿,因爲異常處理也須要用業務線程池,若是此時運氣好,業務線程池有空閒線程,那麼Consumer將收到Provider發送的線程池打滿異常;但極可能此時業務線程池仍是滿的,因而悲劇,異常處理和應答步驟也沒有線程能夠跑,致使沒法應答Consumer,這時候Consumer只能苦等到超時!

 

           這也是爲何咱們有時候能在Consumer看到線程池打滿異常,有時候看到的確是超時異常。

 

        爲啥咱們設置Dispatcher爲message能夠規避此問題?直接看MessageOnlyChannelHandler的實現: 

 

public void received(Channel channel, Object message) throws RemotingException {

    ExecutorService cexecutor = executor;

    if (cexecutor == null || cexecutor.isShutdown()) {

        cexecutor = SHARED_EXECUTOR;

    }

    try {

        cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));

    } catch (Throwable t) {

        throw new ExecutionException(message, channel, getClass() + " error when process received event .", t);

    }

}

 

沒錯,MessageOnlyChannelHandler只覆蓋了WrappedChannelHandler的received方法,意味着只有請求處理會用到業務線程池,其餘的非業務操做直接在IO線程池執行,這不正是咱們想要的嗎?因此使用message的Dispatcher,不會存在Provider線程池滿了,Consumer卻還在傻等的狀況,由於默認IO線程池是無界的,必定會有線程來處理異常和應答(若是你把它設置成有界,那我也沒啥好說的了)。

 

因此,爲了減小在Provider線程池打滿時整個系統雪崩的風險,建議將Dispatcher設置成message:

<!--StartFragment--> <!--EndFragment-->

<dubbo:protocol name="dubbo" port="8888" threads="500" dispatcher="message" />

  • 大小: 129.8 KB
  • 大小: 23 KB
相關文章
相關標籤/搜索