取消堆集以提高延迟并减少AWS账单
大多數性能問題可以通過幾種不同的方式解決。 多數人都容易理解和應用許多適用的解決方案。 一些解決方案,例如從JVM管理的堆中刪除某些數據結構,則更為復雜。 因此,如果您不熟悉此概念,我建議您繼續學習我們最近如何減少應用程序的延遲,以及如何將Amazon AWS費用減少一半。
我將從解釋需要解決方案的上下文開始。 如您所知, Plumbr密切關注每次用戶交互。 這是使用部署在處理交互的應用程序節點旁邊的代理來完成的。
這樣做時,Plumbr代理正在從此類節點捕獲不同的事件。 所有事件都發送到中央服務器,并組成我們稱為事務的事務。 事務包含多個屬性,包括:
- 交易的開始和結束時間戳;
- 執行交易的用戶的身份;
- 執行的操作(將項目添加到購物車,創建新發票等);
- 該操作所屬的應用程序;
在我們面臨的特定問題的背景下,重要的是概述僅將對實際值的引用存儲為事務的屬性。 例如,不是存儲用戶的實際身份(例如電子郵件,用戶名或社會保險號),而是在交易本身旁邊存儲對此類身份的引用。 因此,事務本身可能如下所示:
| #1 | 12:03:40 | 12:05:25 | #11 | #222 | #3333 |
| #2 | 12:04:10 | 12:06:00 | #11 | #223 | #3334 |
這些參考與對應的人類可讀值對應。 通過這種方式,可以維護每個屬性的鍵值映射,以便將ID為#3333和#3334的用戶分別解析為John Smith和Jane Doe。
這些映射在運行時期間使用,當訪問事務的查詢將用人類可讀的參考數據替換參考時:
| #1 | 12:03:40 | 12:05:25 | www.example.com | /登錄 | 約翰·史密斯 |
| #2 | 12:04:10 | 12:06:00 | www.example.com | /購買 | 簡·多伊 |
天真的解決方案
我敢打賭,我們的讀者中的任何人都可以在不睜眼的情況下針對這種要求提出簡單的解決方案。 選擇一個喜歡的java.util.Map實現,將鍵值對加載到Map并在查詢期間查找引用的值。
當我們發現我們選擇的基礎架構(具有存儲在Kafka主題中的查找數據的Druid存儲)已經通過Kafka查找開箱即用地支持此類Maps時,感覺容易的事情變得微不足道。
問題
幼稚的方法為我們服務了一段時間。 一段時間后,隨著查找映射的大小增加,需要查找值的查詢開始花費越來越多的時間。
我們在吃自己的狗糧并使用Plumbr監視Plumbr本身時注意到了這一點。 我們開始看到在Druid Historical節點上,GC暫停越來越頻繁,而且更長,服務于查詢并解決了查詢。
顯然,一些最有問題的查詢必須從地圖中查找超過100,000個不同的值。 這樣做時,查詢被GC啟動打斷,并超出了以前的100ms以下查詢的持續時間,超過了10秒鐘。
在尋找根本原因的同時,我們讓Plumbr從此類有問題的節點公開了堆快照,確認長時間的GC暫停后大約70%的已用堆已被查找表完全消耗了。
同樣明顯的是,該問題還需要考慮另一個方面。 我們的存儲層基于節點集群,集群中為查詢提供服務的每臺計算機都運行多個JVM進程,而每個進程都需要相同的參考數據。
現在,考慮到所討論的JVM具有16G堆,并有效地復制了整個查找映射,因此這也成為容量規劃中的一個問題。 支持越來越大的堆所需的實例大小開始在我們的EC2賬單中付出了代價。
因此,我們不得不提出一種不同的解決方案,既減輕了垃圾收集的負擔,又找到了降低Amazon AWS成本的方法。
解決方案:編年史地圖
我們實施的解決方案基于Chronicle Map構建。 編年史地圖在內存鍵值存儲區中處于堆外狀態。 正如我們的測試所示,存儲的延遲時間也非常長。 但是,我們選擇編年史地圖的主要優點是它能夠跨多個流程共享數據。 因此,除了將查找值加載到每個JVM堆之外,我們只能使用集群中不同節點訪問的映射的一個副本:
在進入細節之前,讓我為您提供編年史地圖功能的高級概述,我們發現它特別有用。 在編年史地圖中,數據可以保存到文件系統中,然后由任何并發進程以“查看”模式訪問。
因此,我們的目標是創建一個具有“編寫者”角色的微服務,這意味著它將將所有必需的數據實時地持久存儲到文件系統中,并作為“讀取器”的角色(即我們的Druid數據存儲)。 由于Druid不支持現成的Chronicle Map,因此我們實現了自己的Druid擴展 ,該擴展能夠讀取已經保存的Chronicle數據文件,并在查詢期間用人類可讀的名稱替換標識符。 以下代碼提供了一個示例,說明如何初始化編年史地圖:
ChronicleMap.of(String.class, String.class) .averageValueSize(lookup.averageValueSize) .averageKeySize(lookup.averageKeySize) .entries(entrySize) .createOrRecoverPersistedTo(chronicleDataFile);在初始化階段需要此配置,以確保Chronicle Map根據您預測的限制分配虛擬內存。 虛擬內存預分配不是唯一的優化,如果像我們一樣將數據持久化到文件系統中,您會注意到創建的Chronicle數據文件實際上是稀疏文件 。 但這將是一個完全不同的帖子的故事,所以我不會深入探討這些。
在配置中,您需要為嘗試創建的編年史地圖指定鍵和值類型。 在我們的例子中,所有參考數據都是文本格式,因此我們為鍵和值都指定了String類型。
在指定鍵和值的類型之后,Chronicle Map初始化還有更多有趣的部分是獨特的。 正如方法名稱所暗示的, averageValueSize和averageKeySize都要求程序員指定期望存儲在Chronicle Map實例中的平均鍵和值大小。
使用方法條目,您可以為Chronicle Map提供可以存儲在實例中的預期數據總數。 也許有人會懷疑,如果隨著時間的推移,記錄數量超過預定義的大小會發生什么? 顯然,如果超過了配置的限制,則在最后輸入的查詢上性能可能會下降。
當超出預定義的條目大小時,還要考慮的另一件事是,如果不更新條目大小,則無法從Chronicle Map文件中恢復數據。 由于Chronicle Map在初始化期間會預先計算數據文件所需的內存,因此,如果條目大小保持不變,并且實際上文件中包含的內容(比如說多4倍的條目),則數據將無法容納到預計算的內存中,因此Chronicle Map初始化將失敗。 如果要在重啟后正常運行,請務必牢記這一點。 例如,在我們的場景中,當重新啟動持久化來自Kafka主題的數據的微服務時,在初始化Chronicle Map實例之前,它會根據Kafka主題中的消息數量動態計算數量條目。 這使我們能夠在任何給定時間重新啟動微服務,并使用更新的配置恢復已持久保存的Chronicle Map文件。
帶走
使Chronicle Map實例能夠在微秒內讀寫數據的各種優化立即開始產生良好的效果。在發布基于Chronicle Map的數據查詢后幾天,我們已經看到了性能改進:
此外,從每個JVM堆中刪除查找映射的冗余副本可以顯著減少存儲節點的實例大小,從而在我們的Amazon AWS賬單中產生明顯的凹痕。
翻譯自: https://www.javacodegeeks.com/2017/02/going-off-heap-improve-latency-reduce-aws-bill.html
總結
以上是生活随笔為你收集整理的取消堆集以提高延迟并减少AWS账单的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 上海文物备案局(上海文物备案)
- 下一篇: (linux .link)