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