2011-06-22 57 views
4

有什麼工具或最佳實踐可用於在突發的內存密集型請求期間正常降級Java服務中的服務?有問題的應用程序是多線程的。處理每個請求所需的工作量可能差別很大,並且不容易分解和並行化。Java中的優雅降級以避免內存不足錯誤

我對編寫與堆使用和GC有關的應用程序級代碼保持警惕,但我們發現應用程序可能因爲承擔多個密集請求而陷入麻煩,即內存不足錯誤或完整GC 。一個完整的GC通常無法找到任何空閒的內存。

長話短說:我正在考慮增加一些限制或排隊功能來預先解決這類問題。

任何意見或建議表示讚賞。

+0

我假設你已經對應用程序進行了剖析,以確保你不能減少使用的內存量。您也檢查過無法獲得更大的服務器,16 GB的PC可能會花費1000美元。 –

+0

彼得 - 是的,我們對應用程序進行了剖析,以瞭解我們可以在哪裏減少內存使用量。更多的物理內存,更大的服務器 - 這些選項可能不會導致GC在發生時更長更痛苦嗎? – ChrisW

+0

GC的成本與使用量和免費量的倒數成正比。如果使用相同數量的內存,則GC暫停將相同,但更少。如果你使用更多的記憶,它會更長,但這比直接失敗要好。 –

回答

0

我想知道是否有一種方法可以預先確定給定工作大約需要多少內存....如果有某種方法可以確定某個特定輸入可能產生爆炸性內存大小,也許您可以試圖阻止它在另一個高使用率工作的同時運行。

如果您可以確定從工作到工作的相對大小(這是一個很大的假設),您可以允許(比如說)使用計數Semaphore一次運行100個工作單元。一個典型的工作可能只能算作一個單位(並且只獲得一個許可證),其中較大的工作可能需要在運行之前獲得10或20個許可證。......

當然,如果您無法預先確定即將消耗的內存的大小,您可能仍然能夠探索進一步細分問題的方法,以便您執行大量的小內存作業,而不是少量的大作業。

0

在應用程序服務器中,通常有工作程序線程池的設置。該池中的最大線程數大致定義了您將消耗多少內存。這是一個簡單而重要的工作概念。

雖然我不稱之爲「優雅的退化」。這是節流。優雅的降級涉及降低服務水平(例如提供給用戶的細節的數量)以至少保持對於每個當前用戶可用的基本必要功能。限制額外的用戶只是運氣不好。

該定義的優雅降級需要知道應用程序的性質,因此您必須讓代碼知道它。

顯而易見的方法是將所有可能的操作按用戶的需要劃分爲類。第一類應始終處理。第2(3rd,4th,...)類只有在服務器低於特定的負載水平時才被提供,否則返回「暫時不可用」的錯誤。

1

正如joeslice所說,通過一個簡單的資源池實施節流。在最基本的層面上,這是一個信號量 - 你的工作線程在處理請求之前需要獲得許可證。既然你說你有不同的任務,你可能想讓這個許可變得更復雜一些,例如獲得一些與工作規模成正比的許可證。

在過去,我發現這並不總是奏效。假設你的啓發式算法已關閉,並且你的應用程序拋出一個OOM。防止進程在不良狀態下掛起很重要,因此請立即停止並重新啓動進程。有幾種方法可以注意到OOM發生的時間,例如見java out of memory then exit

+0

這個過程不一定會處於不良狀態。 OOM將展開有問題的線程堆棧,釋放一些內存。在線程池中,OOM可以被捕獲並且啓發式調整可以在某種程度上考慮到已更改的請求模式。整個應用程序可以存活並繼續提供服務。 –

+1

OOM可能有各種各樣的原因,其中只有一個是由信號量/啓發式控制的,所以我不認爲你所描述的是可靠的。換句話說,抓住OOM有時可能有效,但是我發現安全而不是抱歉好多了。保留GC和應用程序日誌,以便稍後在生產環境之外重現問題! – jtoberon

1

以下是Netty(link)作者的實現示例。他們基本上跟蹤內存使用情況並基於該統計信息直接進行節流。

另一個更粗糙的方法是通過使用固定線程池和有界隊列來限制併發執行。一旦這個隊列已滿,通常的方法是讓queue.put()的調用者自己執行任務。通過這種方式,負載將(一直應該)傳播回客戶端,直到新請求的創建變慢。因此,應用程序的行爲。變得更加「優美」。

實際上,我幾乎只使用上述的「粗略」方式。它工作得很好。基本上,固定線程池和有界隊列+呼叫者的組合運行拒絕策略。我保持參數(隊列大小,線程池大小)可配置,然後設計完成後,我會調整這些參數。有時很明顯,線程池可以在服務等中共享,所以在這種情況下,使用類ThreadPoolExecutor來獲得固定的線程池/有界隊列/調用者運行策略都非常方便。

0

您使用的是J2EE嗎?因爲這是Application Server負責進行負載平衡的工作,並且我相信很多主流AppServers都支持它。你的申請不應該關心它。