貪喫的jetty被撐死了

轉自 http://benni82.iteye.com/blog/875494


在大量請求並且請求處理時間較長的情況下,jetty的nio模式會導致容器運行緩慢。

 

測試方法:

用apache ab對jetty容器發出大規模持續的併發請求,

用命令“jstat -gcutil -h 10 PID 1000"查看GC情況,等到young、old區到100%時停止施壓。

 

用“jmap -histo PID | less" 可以看到大量的SelectChannelEndPoint對象。


分析一下原因:

首先介紹一下jetty的nio模式,如下圖

 

mainReactor:jetty從線程池中分配一個線程用於接受用戶的連接請求(ServerSocketChannel.accpet()),

這個線程就做一件事,接受用戶的連接,將channel註冊到selector中。

 

Jetty mainreactor代碼  收藏代碼
  1. _manager.dispatch(new Runnable()  
  2. {  
  3.     public void run()  
  4.     {  
  5.         final ServerSocketChannel server=_acceptChannel;  
  6.         while (isRunning() && _acceptChannel==server && server.isOpen())  
  7.         {  
  8.             try  
  9.             {  
  10.                 SocketChannel channel = server.accept();  
  11.                 channel.configureBlocking(false);  
  12.                 Socket socket = channel.socket();  
  13.                 configure(socket);  
  14.                 _manager.register(channel);  
  15.             }  
  16.             catch(IOException e)  
  17.             {  
  18.                 Log.ignore(e);  
  19.             }  
  20.         }  
  21.     }  
  22. });  
    

 

而jetty的subReactor線程詢註冊進來的channel,將channel包裝成SelectChannelEndPoint對象加入到_endPoints。(可以把endpoint看作是一個連接)

 

Selectmanager.selectset代碼  收藏代碼
  1. private ConcurrentMap<SelectChannelEndPoint,Object> _endPoints = new ConcurrentHashMap<SelectChannelEndPoint, Object>();    
  2. public void doSelect() throws IOException {  
  3. ...  
  4.     else if (change instanceof SocketChannel)  
  5.     {  
  6.         // Newly registered channel  
  7.         final SocketChannel channel=(SocketChannel)change;  
  8.         SelectionKey key = channel.register(selector,SelectionKey.OP_READ,null);  
  9.         SelectChannelEndPoint endpoint = createEndPoint(channel,key);  
  10.         key.attach(endpoint);  
  11.         endpoint.schedule();  
  12.     }  
  13. ...  
  14. }  
 

 

爲什麼要加入到_endPoints,爲了對所有的endpoint做空閒檢查。

 

Endpoint空閒檢查代碼  收藏代碼
  1. public void doSelect() throws IOException {  
  2. 。。。  
  3. // Idle tick  
  4. if (now-_idleTick>__IDLE_TICK)  
  5. {  
  6.     _idleTick=now;  
  7.       
  8.     final long idle_now=((_lowResourcesConnections>0 && selector.keys().size()>_lowResourcesConnections))  
  9.         ?(now+_maxIdleTime-_lowResourcesMaxIdleTime)  
  10.         :now;  
  11.           
  12.     dispatch(new Runnable()  
  13.     {  
  14.         public void run()  
  15.         {  
  16.             for (SelectChannelEndPoint endp:_endPoints.keySet())  
  17.             {  
  18.                 endp.checkIdleTimestamp(idle_now);  
  19.             }  
  20.         }  
  21.     });  
  22. }  
  23. 。。。  
  24. }  
 

 

這裏有個問題,回收動作需要從線程池中分配線程處理,而如果線程池中沒有空閒的線程時,那麼回收動作將無法正常進行。所以嘗試修改__IDLE_TICK到30毫秒(默認是400),希望能提高空閒檢查頻率,卻無法起效。

 

還有這個可惡的_endPoints對象,它將持有大量的endpoint,而這些endpoint又得不到及時處理,內存都被它消耗光。

 

我配置的最大線程池爲250,任務隊列長度無限制

 

線程池配置代碼  收藏代碼
  1. <Set name="ThreadPool">  
  2.   <!-- Default queued blocking threadpool -->  
  3.   <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">  
  4.     <Set name="minThreads">10</Set>  
  5.     <Set name="maxThreads">250</Set>  
  6.   </New>  
  7. </Set>  

在jvm的young、old區達到100%時,250線程也都已經分配(可以用命令"jstack PID | grep "\"qtp" | wc -l"查看),但是很多都block住了;因爲線程運行過程中也會有對象創建,也需要一點內存空間,可已經沒有內存空間,杯具就這樣發生了。

 

如果把mainReactor比作人在喫東西,那麼subReator就是他的胃在消化,

大部分情況都是喫個7分飽,此時胃的消化能力很強,

一旦出現暴飲暴食,就會出現胃脹,消化能力反而減弱。


一個解決方案:

分析下來覺得jetty缺了胃反射功能,胃脹信息沒有即使反饋給大腦。

可以適當擴展一下mainReactor,看下面的代碼:

擴展後的mainreactor代碼  收藏代碼
  1. _manager.dispatch(new Runnable()  
  2. {  
  3.     public void run()  
  4.     {  
  5.         final ServerSocketChannel server=_acceptChannel;  
  6.         while (isRunning() && _acceptChannel==server && server.isOpen() && !_manager.isLowResourcesConnections())  
  7.         {  
  8.             try  
  9.             {  
  10.                 SocketChannel channel = server.accept();  
  11.                 channel.configureBlocking(false);  
  12.                 Socket socket = channel.socket();  
  13.                 configure(socket);  
  14.                 _manager.register(channel);  
  15.             }  
  16.             catch(IOException e)  
  17.             {  
  18.                 Log.ignore(e);  
  19.             }  
  20.         }  
  21.     }  
  22. });  

 添加了_manager.isLowResourcesConnections()方法,嘴巴準備喫的時候要先問一下胃先。

 

subReactor添加一個新方法:

Java代碼  收藏代碼
  1. public boolean isLowResourcesConnections() {  
  2. // 這裏的判斷閥值是個大概的值。  
  3. // 拿配置的閥值和第一個selector的keys大小做比較  
  4. // 任何情況下第一個selector都是存在的,所以這個比較還是靠譜的。  
  5.     return _lowResourcesConnections < _selectSet[0].getSelector().keys().size();  
  6. }  

另外也可以配置自定義隊列加以限制。

<New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
      <Arg>
           <New class="java.util.concurrent.ArrayBlockingQueue">
              <Arg type="int">6000</Arg>
           </New>
      </Arg>
        <Set name="minThreads">10</Set>
        <Set name="maxThreads">200</Set>
        <Set name="detailedDump">false</Set>
      </New>



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章