日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

基于Netty的自研流系統緩存實現挑戰:內存碎片與OOM困境

來源: 責編: 時間:2024-07-19 08:03:52 596觀看
導讀1.前言Kafka 作為流處理平臺,在實時流計算和在線業務場景,追尾讀追求端到端低延遲。在離線批處理和削峰填谷場景,數據冷讀追求高吞吐。兩個場景都需要很好的數據緩存設計來支撐,Apache Kafka 的數據存儲在本地文件,通過 mm

1.前言

Kafka 作為流處理平臺,在實時流計算和在線業務場景,追尾讀追求端到端低延遲。在離線批處理和削峰填谷場景,數據冷讀追求高吞吐。兩個場景都需要很好的數據緩存設計來支撐,Apache Kafka 的數據存儲在本地文件,通過 mmap 將文件映射到內存中訪問,天然就可以依托操作系統來完成文件的緩沖持久化、緩存加載和緩存驅逐。H6128資訊網——每日最新資訊28at.com

AutoMQ 采用存算分離的架構,將存儲分離至對象存儲,本地沒有數據文件,因此無法像 Apache Kafka 一樣直接使用數據文件 mmap 來進行數據緩存。這時候通常緩存對象存儲的數據有兩種做法:H6128資訊網——每日最新資訊28at.com

  • 第一種是將對象存儲文件下載到本地文件,然后再通過 mmap 讀取本地文件。這種做法在實現上比較簡單,但是需要一塊額外的磁盤來緩存數據,然后根據緩存所需的大小和速率,還需要購買磁盤空間和 IOPS,該做法不夠經濟;
  • 第二種是根據流處理的數據消費特征,直接基于內存來進行數據緩存。這種做法實現起來會復雜一些,相當于需要實現一個類似操作系統的內存管理。但是就像萬事萬物都有兩面性一樣,自己實現內存緩存管理,就可以根據業務場景取得最佳的緩存效率和經濟性。

為了降低運維的復雜性和持有成本,提高緩存的效率,AutoMQ 最終選擇的是第二種做法:“直接基于內存來進行數據緩存”。H6128資訊網——每日最新資訊28at.com

2.AutoMQ 緩存設計

直接基于內存來進行數據緩存,AutoMQ 針對追尾讀和冷讀兩個場景,根據兩者的數據訪問特點,設計了兩套緩存機制:LogCache 和 BlockCache。H6128資訊網——每日最新資訊28at.com

圖片圖片H6128資訊網——每日最新資訊28at.com

LogCache 針對于追尾讀場景設計,數據上傳到對象存儲的同時,也會以單個 RecordBatch 的形式在 LogCache 中緩存一份,這樣熱數據就可以從直接緩存中獲取,提供極低的端到端延遲。相比操作系統通用的緩存設計,LogCache 還具備以下兩個特質:H6128資訊網——每日最新資訊28at.com

  • FIFO:針對追尾讀持續訪問新數據的特點,LogCache 采用先入先出的緩存驅逐策略,優先保證新數據緩存的可用性;
  • 低延遲:LogCache 有獨占的緩存空間,只負責熱數據的緩存,避免了冷數據讀取影響熱數據消費的問題。

BlockCache 針對冷讀場景設計,當無法在 LogCache 中訪問到需要的數據時,則從 BlockCache 中讀取。BlockCache 相比 LogCache 具備以下兩個不同點:H6128資訊網——每日最新資訊28at.com

  • LRU:BlockCache 使用 Least Recently Used 策略來進行緩存驅逐,在多倍 Fanout 的冷讀場景可以獲得更佳的緩存利用率;
  • 高吞吐:冷讀關注的是吞吐量,因此 BlockCache 會大塊(~4MB)的從對象存儲讀取 & 緩存數據,并且通過預讀策略來提前加載后續可能讀取的數據;

Java 程序中在內存中緩存數據可以選擇堆內內存或堆外內存。為了減輕 JVM GC 的負擔,AutoMQ 使用堆外內存 Direct Memory 來緩存數據,并且為了提高 Direct Memory 的申請效率,采用業界成熟的 Netty PooledByteBufAllocator 從池化內存中進行內存的申請和釋放。H6128資訊網——每日最新資訊28at.com

3.“慘案”發生

期望是使用 Netty 的 PooledByteBufAllocator 后,AutoMQ 既可以通過池化來獲得高效的內存分配速度,又有久經打磨的內存分配策略來最小化內存分配的 Overhead,就可以高枕無憂無憂了,然而在 AutoMQ 1.0.0 RC 壓測過程中被現實給了當頭一棒。H6128資訊網——每日最新資訊28at.com

AutoMQ 生產機型為 2C16G,設置堆外內存使用上限 6GiB -XX:MaxDirectMemorySize=6G,內存分配為 2GiB LogCache + 1GiB BlockCache + 1GiB 其他小項 ~= 4GiB < 6GiB。理論計算下,堆外內存還綽綽有余,然而在實際 AutoMQ 1.0.0 RC 版在各種不同負載下長時間運行后發現,分配內存有 OOM OutOfMemoryError 異常拋出。H6128資訊網——每日最新資訊28at.com

本著優先懷疑自己而不是懷疑成熟的類庫和操作系統的原則。H6128資訊網——每日最新資訊28at.com

觀測到異常后,首先懷疑的是代碼中哪里有遺漏調用 ByteBuf#release。于是調整 Netty 的泄漏檢測等級 -Dio.netty.leakDetection.level=PARANOID,檢測每個的 ByteBuf 是否有存在被 GC 但是還沒有被釋放的問題。跑了一段時間未發現有 Leak 日志,于是乎排除漏釋放的可能。H6128資訊網——每日最新資訊28at.com

接著懷疑點轉移到是否代碼中有哪塊內存分配量超出了預期值。Netty 的 ByteBufAllocatorMetric只提供全局的內存占用統計,傳統的內存分配火焰圖也只能提供特定時間的內存申請量,而我們需要的是某個時刻各種類型的內存使用量。因此 AutoMQ 將 ByteBuf 的申請收口到自己實現的 ByteBufAlloc工廠類中,通過WrappedByteBuf 跟蹤各種類型內存的申請和釋放,以此來記錄當前時刻各個類型的內存使用量。并且將 Netty 的實際內存使用量也記錄下來,這樣就知道 AutoMQ 總體內存和分類內存的使用量。H6128資訊網——每日最新資訊28at.com

Buffer usage: ByteBufAllocMetric{allocatorMetric=PooledByteBufAllocatorMetric(usedDirectMemory: 2294284288; ...), // Physical Memory Size Allocated by NettyallocatedMemory=1870424720, // Total Memory Size Requested By AutoMQ1/write_record=1841299456, 11/block_cache=0, ..., // Detail Memory Size Requested By AutoMQpooled=true, direct=true} (com.automq.stream.s3.ByteBufAlloc)

加上分類內存統計后,發現各種類型的內存使用量都在預期范圍內。不過異常的是,AutoMQ 申請的內存量和 Netty 實際申請的內存量有較大的差距,并且隨著運行兩者之間的差值越來越大,甚至有時候 Netty 實際升級的內存是 AutoMQ 申請的內存量的兩倍,這個差值為內存分配的內存碎片。H6128資訊網——每日最新資訊28at.com

最終 OOM 的誘發原因定位為 Netty PooledByteBufAllocator 的內存碎片。初步定位了問題的原因,那么問題轉換為 Netty 為什么會有內存碎片和 AutoMQ 如何規避內存碎片問題。H6128資訊網——每日最新資訊28at.com

4.Netty 內存碎片

首先我們來探索一下 Netty 內存碎片的原因。Netty 的內存碎片分為內部碎片和外部碎片:H6128資訊網——每日最新資訊28at.com

  • 內部碎片:由于 size 規約化對齊引起的碎片,例如期望分配 1byte,但是底層實際占用了 16byte,那么內部碎片就浪費了 15byte;
  • 外部碎片:簡單的來說,所有除了內部碎片以外引起的碎片都算外部碎片,通常是由于分配算法導致的內存布局碎片導致的;

內部碎片和外部碎片,在不同的 Netty 版本有不同的表現,下面將以 Netty 4.1.52 版本為分割線簡要介紹一下 Buddy 分配算法和 PageRun/PoolSubPage 分配算法的工作機制和內存碎片成因。H6128資訊網——每日最新資訊28at.com

4.1 Buddy 分配算法 Netty < 4.1.52

Netty < 4.1.52 采用 Buddy 分配算法,算法源自 jemalloc3。Netty 為了提升內存申請的效率,會一次性從操作系統申請一段連續內存(PoolChunk),在上層申請 ByteBuf 時,按需將這一段內存邏輯拆分返回給上層。默認 PoolChunk 的大小為 16MB,PoolChunk 邏輯上被劃分為 2048 個 8KB 大小的 Page,通過一個完全二叉樹來表示內存的使用情況。H6128資訊網——每日最新資訊28at.com

圖片圖片H6128資訊網——每日最新資訊28at.com

完全二叉樹的每個節點用一個 byte 來表示節點的狀態(memoryMap):H6128資訊網——每日最新資訊28at.com

  • 初始值為層數,狀態值 == 層數代表該節點完全空閑;
  • 當層數 < 狀態值 < 12 時,代表該節點被使用了一部分,但仍舊有剩余空間;
  • 當狀態值 == 12 時,代表該節點已經被完全分配;

內存分配分為 Tiny [0, 512byte] 、 Small (512byte, 8KB) 、 Normal [8KB, 16M] 和 Huge (16M, Max) 四種類型,其中 Tiny 和 Small 由 PoolSubpage 負責,Normal 由 PoolChunk 負責,Huge 直接分配。H6128資訊網——每日最新資訊28at.com

先來看看小內存塊的分配效率,Tiny [0, 512byte] 和 Small (512byte, 8KB) 將一個 Page 通過 PoolSubpage 切分成等長的邏輯塊,由一個 bitmap 來標記塊的使用情況:H6128資訊網——每日最新資訊28at.com

  • Tiny 內存分配的基礎單位為 16 byte,意味著如果請求大小為 50 byte,實際分配的是 64 byte,內部碎片率為 28%;
  • Small 內存分配的基礎單位是 1KB,意味著請求大小為 1.5KB,實際分配的是 2KB,內部碎片率為 25%;

再來看看中等的內存塊 Normal [8KB, 16M],假設從一個完全空閑的 PoolChunk 申請 2MB + 1KB = 2049KB 內存:H6128資訊網——每日最新資訊28at.com

  • 2049KB 以 2 為底向上規格化后變為 4MB,于是查找目標為 Depth-3 的空閑節點;
  • 檢查 index=1 節點,發現節點有空閑,則檢查左子樹;
  • 檢查 index=2 節點,發現節點有空閑,則繼續檢查左子樹;
  • 檢查 index=4 節點,發現節點未被分配,則將 index=4 的狀態標記為 12,并且將父節點的狀態更新為兩個子節點中最小的那個,也就是將 index=2 的狀態變為 3,同理依次更新父節點狀態;
  • 分配完成;

從分配結果可以看出,申請 2049KB 內存,實際標記占用 4MB 內存,意味著內部碎片率為 49.9%。H6128資訊網——每日最新資訊28at.com

假設再申請一個 9MB 的內存,雖然剛才的 PoolChunk 仍有 12MB 的剩余空間,但是由于 Buddy 內存分配算法的原理,index=1 已經被占用了部分,此時只能新開一個 PoolChunk 來分配 9MB 的內存。分配后的外部碎片率為 1 - (4MB + 9MB) / 32MB = 59.3%。最終所需內存 / 底層實際占用內存 = 有效內存利用率 = 僅為 34.3%。H6128資訊網——每日最新資訊28at.com

更進一步,在各種不同大小的內存塊持續的分配釋放場景,即使 PoolChunk 實際分配出去的空間不大,也有可能被零散的內存塊邏輯分割,進一步增加更多的外部內存碎片。以下圖為例,雖然上層應用最終只保留了 4 * 8KB,但是已經無法再從這個 PoolChunk 申請 4MB 的內存了。H6128資訊網——每日最新資訊28at.com

圖片圖片H6128資訊網——每日最新資訊28at.com

4.2 PageRun/PoolSubpage 分配算法 Netty >= 4.1.52

Netty >= 4.1.52 參考 jemalloc4 將內存分配升級到 PageRun/PoolSubPage 分配策略。相比原來的 Buddy 分配算法無論在小內存的分配還是在大內存的分配都有低的內部 & 外部內存碎片率。H6128資訊網——每日最新資訊28at.com

PageRun/PoolSubpage 分配算法相比原來 Buddy 分配算法:H6128資訊網——每日最新資訊28at.com

  • Chunk 默認大小從 16MB 變為 4MB;
  • 保留了 Chunk 和 Page,增加了 Run 的概念,一串連續的 Pages 組成一個 Run,通過 Run 來分配 Normal (28KB, 4MB)  中等內存;
  • 將 Tiny 和 Small 級別的內存塊替換成可跨多個 Page & [16byte ... 28KB] 共 38 級基礎分配大小的 PoolSubpage;

圖片圖片H6128資訊網——每日最新資訊28at.com

首先仍舊是先看看小內存塊的分配效率,以申請 1025 byte 為例:H6128資訊網——每日最新資訊28at.com

  •  首先 1025 會根據 PoolSubpage 級別規約到 1280 這個基礎分配大小;
sizeIdx2sizeTab=[16, 32, 48, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 640, 768, 896, 1024, 1280, 1536, 1792, 2048, 2560, 3072, 3584, 4096, 5120, 6144, 7168, 8192, 10240, 12288, 14336, 16384, 20480, 24576, 28672, ...]
  • 然后 PoolChunk 會對 1280 byte 和 Page Size 8K 取最小公倍數 40KB 來決定該 PoolSubPage 包含  5 個 Pages;
  • 從 PoolChunk 中分配 5 個連續的 Pages,并通過 bitmapIdx 記錄已分配出去的 element;
  • 至此分配完成,內部碎片率為 1 -  1025 / 1280 = 19.9%

得益于 PoolSubpage 相比原來分級更加精細,從原來的 2 級變成 38 級,小內存塊的分配效率大大提高。H6128資訊網——每日最新資訊28at.com

然后再來看看中等的內存塊 Normal (28KB, 4M] 的內存分配效率。假設從一個完全空閑的 PoolChunk 申請 2MB + 1KB = 2049KB 內存:H6128資訊網——每日最新資訊28at.com

  • 2049KB 按照 8KB 向上規整后,發現需要 257 個 Pages;
  • PoolChunk 中找到滿足大小的 Run Run{offset=0, size=512};
  • PoolChunk 將 Run 拆分成 Run{offset=0, size=257} 和 Run{offset=257, size=255},第一個 Run 返回給請求方,第二個 Run 加入到空閑 Run 列表(runsAvail)中;
  • 至此分配完成,內部碎片率為 1 - 2049KB / (257 * 8K) = 0.3%;

通過 PageRun 機制,Netty 可以控制大于 28KB 的內存塊分配的內存浪費不超過 8KB,內部碎片率小于 22.2%。H6128資訊網——每日最新資訊28at.com

假設再申請一個 1MB 的內存,這時候 PoolChunk 仍舊運行相同的邏輯將 Run{offset=257, size=255} 拆分成 Run{offset=257, size=128} 和 Run{offset=385, size=127},前者返回給上層,后者加入到空閑 Run 列表。此時外部碎片率為 25%。如果按照老的 Buddy 算法,在 PoolChunk 的大小為 4MB 的場景下,就需要新開一個 PoolChunk 了,外部碎片率為 62.5%。H6128資訊網——每日最新資訊28at.com

雖然 PageRun/PoolSubpage 分配算法在大小內存上相比原有的 Buddy 分配算法有更低的內部內存碎片率和外部內存碎片率,但是畢竟不像 JVM 內通過 GC 來 Compact 零散的內存,仍舊會出現在各種不同大小的內存塊持續的分配釋放場景,將 PoolChunk 中的可用 Run 切分很零碎,內存碎片率逐漸提升最終導致 OOM。H6128資訊網——每日最新資訊28at.com

5.AutoMQ 應對之道

前面介紹完 Netty 內存分配的機制和內存碎片產生的場景,那 AutoMQ 能怎么解決內存碎片問題的呢?H6128資訊網——每日最新資訊28at.com

LogCache 針對追尾讀持續訪問新數據的特點,采用先入先出的緩存驅逐策略,換個角度思考就是在相鄰時間分配內存的會在相鄰時間釋放。AutoMQ 采用的策略是抽象一個 ByteBufSeqAlloc:H6128資訊網——每日最新資訊28at.com

  • ByteBufSeqAlloc 每次向 Netty 申請 ChunkSize 大小的 ByteBuf,避免產生外部內存碎片,做到零外部內存碎片;
  • ByteBufSeqAlloc分配內存時,通過底層 ByteBuf#retainSlice 緊挨著連續從底層大的內存拆分出小的內存,避免 size 規約化產生內部內存碎片,做到零內部內存碎片;
  • 釋放的時候是相鄰的一起釋放,有可能一塊里面大部分都釋放了,但其中少部分還在有效期內,這時候整個大塊都無法釋放,但這個大塊的浪費有且僅會存在一個,并且也只會浪費一個 ChunkSize 的大小;

BlockCache 的特點是追求冷讀高吞吐,會從對象存儲中大塊讀取數據段。AutoMQ 采用的策略是大塊緩存對象存儲中的原始數據:H6128資訊網——每日最新資訊28at.com

  • 按需解碼:等需要查詢時,再解碼成具體的 RecordBatch,通過降低常駐內存塊的數量來降低內存碎片;
  • 規整化拆分:未來可以將大塊緩存規整化拆分成規整的 1MB 內存塊,來避免各種不同大小的內存塊持續的分配釋放導致的內存碎片率逐漸提升;

圖片圖片H6128資訊網——每日最新資訊28at.com

可以看到 LogCache 和 BlockCache 優化的本質都是根據自身緩存的特點通過大塊 & 規整的內存分配來規避 Netty 內存分配策略帶來的內存碎片問題。通過該方式,AutoMQ 在追尾讀、冷讀和大小消息等各種場景長期運行,也能將堆外內存的內存碎片率控制在 35% 以下,再也沒有出現過堆外內存 OOM。H6128資訊網——每日最新資訊28at.com

圖片圖片H6128資訊網——每日最新資訊28at.com

6.總結

Netty 的 PooledByteBufAllocator 不是銀彈,使用的時候需要考慮內存碎片帶來的實際內存占用的空間放大,規劃預留出合理的 JVM 內存大小。如果只是使用 Netty 作為網絡層框架,由 PooledByteBufAllocator 分配的內存生命周期會比較短,因此內存碎片引起的內存放大實際并不會很明顯,不過仍舊建議使用 Netty 的系統都將版本升級到 4.1.52 之上,以獲得更好的內存分配效率。如果使用 Netty 的 PooledByteBufAllocator 來做緩存,建議根據緩存的特征,使用大塊內存分配然后再自行連續拆分,來規避 Netty 的內存碎片。H6128資訊網——每日最新資訊28at.com

參考文檔

  1. https://netty.io/wiki/reference-counted-objects.html
  2. https://netty.io/news/2020/09/08/4-1-52-Final.html

本文鏈接:http://www.www897cc.com/showinfo-26-101722-0.html基于Netty的自研流系統緩存實現挑戰:內存碎片與OOM困境

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 淺析Vite插件機制,你學會了嗎?

下一篇: 新的 HTML dialog 標簽:絕對改變游戲規則

標簽:
  • 熱門焦點
  • 7月安卓手機性價比榜:努比亞+紅魔兩款新機入榜

    7月登場的新機有努比亞Z50S Pro和紅魔8S Pro,除了三星之外目前唯二的兩款搭載超頻版驍龍8Gen2處理器的產品,而且努比亞和紅魔也一貫有著不錯的性價比,所以在本次的性價比榜單
  • Java NIO內存映射文件:提高文件讀寫效率的優秀實踐!

    Java的NIO庫提供了內存映射文件的支持,它可以將文件映射到內存中,從而可以更快地讀取和寫入文件數據。本文將對Java內存映射文件進行詳細的介紹和演示。內存映射文件概述內存
  • Flowable工作流引擎的科普與實踐

    一.引言當我們在日常工作和業務中需要進行各種審批流程時,可能會面臨一系列技術和業務上的挑戰。手動處理這些審批流程可能會導致開發成本的增加以及業務復雜度的上升。在這
  • 量化指標是與非:挽救被量化指標扼殺的技術團隊

    作者 | 劉新翠整理 | 徐杰承本文整理自快狗打車技術總監劉新翠在WOT2023大會上的主題分享,更多精彩內容及現場PPT,請關注51CTO技術棧公眾號,發消息【WOT2023PPT】即可直接領取
  • 從零到英雄:高并發與性能優化的神奇之旅

    作者 | 波哥審校 | 重樓作為公司的架構師或者程序員,你是否曾經為公司的系統在面對高并發和性能瓶頸時感到手足無措或者焦頭爛額呢?筆者在出道那會為此是吃盡了苦頭的,不過也得
  • 拼多多APP上線本地生活入口,群雄逐鹿萬億市場

    Tech星球(微信ID:tech618)文 | 陳橋輝 Tech星球獨家獲悉,拼多多在其APP內上線了&ldquo;本地生活&rdquo;入口,位置較深,位于首頁的&ldquo;充值中心&rdquo;內,目前主要售賣美食相關的
  • 大廠卷向扁平化

    來源:新熵作者丨南枝 編輯丨月見大廠職級不香了。俗話說,兵無常勢,水無常形,互聯網企業調整職級體系并不稀奇。7月13日,淘寶天貓集團啟動了近年來最大的人力制度改革,目前已形成一
  • 年輕人的“職場羞恥感”,無處不在

    作者:馮曉亭 陶 淘 李 欣 張 琳 馬舒葉來源:燃次元&ldquo;人在職場,應該選擇什么樣的著裝?&rdquo;近日,在網絡上,一個與著裝相關的帖子引發關注,在該帖子里,一位在高級寫字樓亞洲金
  • 華為發布HarmonyOS 4:更好玩、更流暢、更安全

    在8月4日的華為開發者大會2023(HDC.Together)大會上,HarmonyOS 4正式發布。自2019年發布以來,HarmonyOS一直以用戶為中心,經歷四年多的發展HarmonyOS已
Top 主站蜘蛛池模板: 绍兴市| 察雅县| 辛集市| 尉犁县| 漯河市| 鹤峰县| 湘阴县| 平阴县| 喀什市| 桃源县| 麻江县| 云和县| 广安市| 宁安市| 蒲江县| 九台市| 上蔡县| 历史| 临西县| 九江县| 林州市| 交口县| 余姚市| 平阳县| 霍山县| 正阳县| 开原市| 临猗县| 邮箱| 和平区| 徐闻县| 吉首市| 专栏| 中卫市| 清远市| 乳源| 荆州市| 元阳县| 依安县| 卓尼县| 徐汇区|