Alluxio 是一個開源的數據編排系統,致力于解決解決大數據分析及 AI 場景下的一些痛點問題,它可以加速數據查詢和AI模型訓練的速度,提升系統在高并發場景下的高可用能力。這些應用場景決定了 Alluxio 需要具備大吞吐量特性,本文首先介紹 Alluxio Master 的線程池結構,基于線程池分析結果提出 Alluxio 吞吐量調優方案。
作者簡介
?
劉堯龍
騰訊Alluxio Oteam 研發工程師,Alluxio Committer。主要負責Alluxio及分布式一致性相關的開發工作。
業務背景?
本次線程池結構分析與調優的對象是 Alluxio 的開源版本,且 Alluxio 的配置項均為默認配置項,分析的場景是游戲 AI 的特征計算階段。特征計算需要大量讀取用戶數據分析用戶操作,然后針對計算結果進行游戲環境的還原。特征計算任務初期會有數千至上萬的進程同時對底層分布式存儲節點發起訪問,產生流量洪峰。在這種讀密集場景下,Alluxio 的高吞吐可以有效緩解存儲端壓力。
Alluxio 默認線程池結構與 JVM 參數
在業務運行過程中,通過 jstack 生成系統線程信息,導入FastThread(https://fastthread.io/)分析。分析結果如下:
?
Alluxio Master 節點有 1432 個線程,其中 RUNABLE 狀態的線程數僅占46%,有大量線程處于 WATING ?和? TIME_WAITING ?狀態,Master 節點線程數較多,容易發生 OOM,需要根據線程池工作與線程間的調用關系,適當調整線程數量。Alluxio Master 上有八個線程組,分別為:Alluxio、master、grpc、ForkJoinPool.commonPool、Gang.worker、qtpXXX、MetricsMaster、LockPool Evictor。它們之間的工作與線程調用關系如下圖所示。接下來將結合線程模型和 Alluxio 源碼分析這些線程組的作用及調優方向。
Alluxio 線程組
Alluxio 線程組共600個線程,它們的狀態如下:
由上表可知,Alluxio 線程組中共有5種線程,其中,負責client的線程均處于 RUNNABLE 狀態,其余線程處于 TIME_WATING 和 WATING 狀態,下面將介紹每個線程的功能。
Alluxio-client-netty-event-loop-RPC
這是 Netty 框架的線程池,屬于 NioEventLoopGroup 類型。在這次采樣數據中,該線程池256個線程均處于 RUNNABLE 狀態,該線程用于 client 端與 server 端建立連接時使用,可以通過下列配置項進行配置,它的默認值為0。
?
?
alluxio.user.network.netty.worker.threads
?
?
Alluxio-ufs-sync
該線程池主要用于并發地執行元數據同步操作,它是 ThreadPoolExecutor 類型。具體的元數據同步操作由 Alluxio-ufs-sync-prefetch 線程組完成。在本次采樣中,線程全部處于 TIME_WATING 狀態。該線程池的核心線程數與最大線程數相等,它的默認值為系統當前的核心數,可以通過下列配置項修改。
?
?
alluxio.master.metadata.sync.executor.pool.size
?
?
Alluxio-ufs-sync-prefetch
該線程池主要在一個元數據同步操作內,并發地從 UFS 中獲取元數據。在本次采樣中,線程全部處于 TIME_WAITING 狀態。該線程池的核心線程數與最大線程數相等,默認值為系統當前的線程數的 10 倍,可以通過下列配置項修改。
?
?
alluxio.master.metadata.sync.ufs.prefetch.pool.size
?
?
Alluxio-master
這個線程組有5個線程:
本次提取的堆棧信息是從 Alluxio-master-1 節點中取出的,它是 Leader 節點。這五個線程用于支持 Alluxio-master 的具體工作:上表的前兩個線程是 master-1 與兩個 raft follower 節點進行通信的守護線程,用于 raft 集群中追加日志時進行通信。第三個線程為 Leader 節點特有的,主要用于 Leader 選舉的相關操作。第四個線程為 RaftLog 相關的線程,這個線程會處理與 Raft log 相關的 I/O OPS 相關的操作。第五個線程與 StateMachine 相關,它是 Ratis 用于狀態機更新的線程。
master 線程組
Master 線程組共256個線程,均處于 WATING 狀態。它們組成了一個 ForkJoinPool 類型的線程池,ForkJoinPool 是ExecutorService 的補充,它采用分而治之的思想,比較適合計算密集型任務。Alluxio 用該線程池處理 RPC 請求,它從 RPC Queue 不斷取出積壓的任務,然后進行處理,這個線程池的創建源碼為:
?
?
ExecutorServiceBuilder#executorService = new ForkJoinPool(parallelism, ? ?ThreadFactoryUtils.buildFjp(threadNameFormat, true), null, isAsync, corePoolSize, ? ?maxPoolSize, minRunnable, null, keepAliveMs, TimeUnit.MILLISECONDS);
?
?
GRPC 線程組
GRPC 線程組由4種線程構成,在本次采樣中共262個線程。這四種線程屬于 GRPC 框架,它們為 Alluxio 提供 RPC 通信服務。
MetricsMaster 線程組
該線程組共4個線程,它們是一個 FixedThreadPool 類型的線程池,即該線程池的核心線程數與最大線程數相等。該線程池主要用于并行獲取從 worker 或者 client 提交的 Metric 數據,并根據數據更新集群的指標信息,這些信息可以通過 Grafana 與Prometheus 相結合地方式直觀地檢查系統狀態。該線程池的核心線程數是可配置的,通過下列配置項完成。
?
?
alluxio.master.metrics.service.threads=5(默認值)
?
?
LockPool Evictor 線程組
LocakPool Evictor 線程組由2個 SingleThreadExecutor 類型的線程池組成,它們作為鎖池使用。該線程池的源碼如下:
?
?
private final LockPoolmInodeLocks = ? ?new LockPool<>((key) -> new ReentrantReadWriteLock(), ? ? ? ?ServerConfiguration.getInt(PropertyKey.MASTER_LOCK_POOL_INITSIZE), ? ? ? ?ServerConfiguration.getInt(PropertyKey.MASTER_LOCK_POOL_LOW_WATERMARK), ? ? ? ?ServerConfiguration.getInt(PropertyKey.MASTER_LOCK_POOL_HIGH_WATERMARK), ? ? ? ?ServerConfiguration.getInt(PropertyKey.MASTER_LOCK_POOL_CONCURRENCY_LEVEL)); private final LockPool mEdgeLocks = ? ?new LockPool<>((key) -> new ReentrantReadWriteLock(), ? ? ? ?ServerConfiguration.getInt(PropertyKey.MASTER_LOCK_POOL_INITSIZE), ? ? ? ?ServerConfiguration.getInt(PropertyKey.MASTER_LOCK_POOL_LOW_WATERMARK), ? ? ? ?ServerConfiguration.getInt(PropertyKey.MASTER_LOCK_POOL_HIGH_WATERMARK), ? ? ? ?ServerConfiguration.getInt(PropertyKey.MASTER_LOCK_POOL_CONCURRENCY_LEVEL));
?
?
其中,mInodeLocks 用于提供 inode 鎖:要鎖定一個 inode,必須在該池中得它的id然后獲取它的讀鎖;mEdgeLocks 用于提供邊鎖,這里的邊指的是 inode 樹中的一條邊,邊從父 inode id 指向子 inode id。
這兩種鎖池中的鎖均為可重入的讀寫鎖,鎖池的初始數量、最小鎖數量、最大鎖數量、并發度均可以配置。
qtpXXX 線程組
該線程組用于提供 Jetty 服務,Jetty 是一個開源的 Servlet 容器,對外提供 web 服務。它們屬于 QueueThreadPool 類型的線程池。在本次采樣結果中共14個線程,這個線程池的最大線程數為254個,最小線程數為8。
Gang.worker 線程組
Gang worker 線程組用于 JVM 的垃圾回收。該線程組的線程數可以通過修改 JVM 參數進行 -XX:ParallelGCThreads 進行修改。
調優原理與結果
審計日志
在吞吐量測試過程中,我們在編譯器研發團隊的幫助下,通過 Kona Profile 采樣,并對采樣結果進行分析,發現 Alluxio 在運行過程中,生成審計日志時存在明顯的鎖競爭,blocking queue 的 size 成為了瓶頸點。
基于這種現象,我們選擇在非生產環境下關閉審計日志,在生產環境下調高審計日志的 blocking queue size 的方式調優性能。在調整后發現,系統吞吐量明顯提升。
?
調整 UFS-SYNC-PREFETCH 線程池
在 Alluxio Master 運行業務時,大量的任務積壓在 master rpc queue 中,ForkJoinPool 線程池的處理速度受限于物理機的資源成為了瓶頸點。Alluxio-UFS-SYNC-PREFETCH 線程池用于執行元數據的同步工作,這個線程數默認為系統 CPU 核數的10倍,在實際系統中并不需要這么多的線程數,因此這種配置存在線程浪費情況。因此,我們將該線程數調整為2倍的 CPU 核數,調整后發現沒有出現性能下降的情況。
JVM GC 線程數調優
Alluxio 在騰訊自研的 KonaJDK 11 上作測試,使用 JDK 默認參數會出現集群 Full GC 時間過長的情況,Master 停頓時間過長會導致 Leader 切換,這種切換的成本很高。通過分析 GC 日志,我們發現默認的 GC 并行線程數較小,僅為30。考慮到我們使用的為64服務器,我們將 GC 并行線程數調整為40,發現 GC 停頓時間明顯降低,系統的穩定性增強。
后續調優方向展望
Alluxio 作為存儲引擎和計算框架之間的中間件,承載著數據緩存、訪問加速、緩解存儲壓力等任務,這些功能都對 Alluxio 系統的吞吐量提出了較高的要求。在接下來工作中,我們將繼續改進方案,不斷提升系統吞吐量性能。為此,我們設計了幾種方案。
將 Follower Master 開啟讀服務
現階段,Alluxio 僅有 Leader Master 接受 Client 端的讀寫請求,這限制了 Alluxio 吞吐量的提升。這會產生單點瓶頸效應,我們計劃在后續將 Follower Master 接受 Client 端的讀請求,這樣會有效提升系統吞吐量。
這種架構的優勢在于可以充分利用現有的節點,不需要有非常大的代碼改動,但缺點為系統提升仍會有瓶頸,不具備無限擴展能力。
開發 Observer Master
為了使集群在理論上擁有無限擴展能力,可以借鑒 Zookeeper 的 Observer Node 思想,開發 Oberserver Master 節點,系統模型如下:
這種模型可以添加多個 Observer Master 節點,它們的元數據同步可以主動 tail raft group 中的 log,并回放到本地狀態機上,worker 的 block 信息可以周期性地進行同步。這種架構的優勢為理論上可以無限擴展,但劣勢是需要額外的節點資源。且這種方案代碼改動較多、開發難度大,block 位置信息的同步開銷也比較大。
總結
本次初衷是基于線程池結構對 Alluxio 吞吐量性能進行調優,根據本次測試采樣結果分析了性能瓶頸點,調優相關瓶頸點后得到性能提升。
基于 Alluxio Master 的源碼,本文介紹了 Alluxio Master 的線程池結構與每個線程的功能。在調優過程中,利用分析結果調整審計日志的 blocking queue,調整 UFS-SYNC-PREFETCH 線程數,調優 JVM 參數。通過實驗證明,Alluxio 吞吐量提升7倍。
最后,本文提出了 Alluxio 吞吐量提升的未來優化方向。
編輯:黃飛
評論