HBase探索篇 _ 單節(jié)點多RegionServer部署與性能測試

目錄導(dǎo)讀

[toc]

1. 引言

隨著集群中總的Region數(shù)持續(xù)增長,每個節(jié)點平均管理的Region數(shù)已達550左右,某些大表的寫入流量一上來,Region Server就會不堪重負,相繼掛掉。

在HBase中,Region的一個列族對應(yīng)一個MemStore,通常一個MemStore的默認大小為128MB(我們設(shè)置的為256MB),見參數(shù)hbase.hregion.memstore.flush.size。當可用內(nèi)存足夠時,每個MemStore可以分配128MB的空間。

當表的寫入流量上升時,假設(shè)每個Region的寫入壓力相同,則理論上每個MemStore會平均分配可用的內(nèi)存空間。

因此,節(jié)點中Region過多時,每個MemStore分到的內(nèi)存空間就會變小。此時,寫入很小的數(shù)據(jù)量,就會被強制flush到磁盤,進而導(dǎo)致頻繁刷寫,會對集群HBase與HDFS造成很大的壓力。

同時,Region過多導(dǎo)致的頻繁刷寫,又會在磁盤上產(chǎn)生非常多的HFile小文件,當小文件過多的時候,HBase為了優(yōu)化查詢性能就會做Compaction操作,合并HFile,減少文件數(shù)量。當小文件一直很多的時候,就會出現(xiàn) “壓縮風暴”。Compaction非常消耗系統(tǒng)的IO資源,還會降低數(shù)據(jù)的寫入速度,嚴重時會影響正常業(yè)務(wù)的進行。

2. 合理的Region數(shù)量

關(guān)于每個Region Server節(jié)點中,Region數(shù)量大致合理的范圍,HBase官網(wǎng)上也給出了定義:

region-number

可見,通常情況下,每個節(jié)點擁有20-200個Region是比較正常的。

其實,每個Region Server的最大Region數(shù)量由總的MemStore內(nèi)存大小決定。每個Region的每個列族會對應(yīng)一個MemStore,假設(shè)HBase表都有一個列族,那么每個Region只包含一個MemStore。一個MemStore大小通常在128~256MB,見參數(shù):hbase.hregion.memstore.flush.size。默認情況下,RegionServer會將自身堆內(nèi)存的40%(我們線上60%)(見參數(shù):hbase.regionserver.global.memstore.size)提供給節(jié)點上的所有MemStore使用,如果所有MemStore的總大小達到該配置大小,新的更新將會被阻塞并且會強制刷寫磁盤。因此,每個節(jié)點最理想的Region數(shù)量應(yīng)該由以下公式計算(假設(shè)HBase表都有統(tǒng)一的列族配置):

((RS memory) * (total memstore fraction)) / ((memstore size)*(column families))

其中:

  • RS memory:表示Region Server堆內(nèi)存的大小,即HBASE_HEAPSIZE。
  • total memstore fraction:表示所有MemStore占HBASE_HEAPSIZE的比例,HBase0.98版本以后由hbase.regionserver.global.memstore.size參數(shù)控制,老版本由hbase.regionserver.global.memstore.upperLimit參數(shù)控制,默認值0.4。
  • memstore size:即每個MemStore的大小,HBase中默認128M。
  • column families:即表的列族數(shù)量,通常情況下只設(shè)置1個,最好不超過3個。

以我們線上集群的配置舉例,我們每個RegionServer的堆內(nèi)存是32GB,那么節(jié)點上最理想的Region數(shù)量應(yīng)該是:32768*0.6/256 ≈ 76(32768*0.6/128 ≈ 153)

500個region反推每個節(jié)點需要的堆內(nèi)存為:500 * 256 / 0.6 / 1024 ≈ 208G (500 * 128 / 0.6 / 1024 ≈ 104G)

上述最理想的情況是假設(shè)每個Region上的填充率都一樣,包括數(shù)據(jù)寫入的頻次、寫入數(shù)據(jù)的大小,但實際上每個Region的負載各不相同,有的Region可能特別活躍、負載特別高,有的Region則比較空閑。所以,通常我們認為23倍的理想Region數(shù)量也是比較合理的,針對上面舉例來說,大概200300個Region左右算是合理的。

3. Region數(shù)量優(yōu)化

針對上文所述的Region數(shù)過多的隱患,以下內(nèi)容主要從兩方面考慮來優(yōu)化。

  • 提高單個Region Server的堆內(nèi)存,保證每個Region的MemStore被分配到比較充裕的內(nèi)存空間,避免寫入高峰期,引起頻繁刷寫。
  • 單節(jié)點多Region Server配置,如果你的物理機資源充裕,可以考慮部署多個RS進程來削弱寫入壓力(這也許可以作為一個應(yīng)急的方案)。

3.1提高RegionServer的堆內(nèi)存

提高內(nèi)存的目的是為了增加每個Region擁有的MemStore的空間,避免其寫入壓力上升時,MemStore頻繁刷寫,形成小的HFile過多,引起壓縮風暴,占用大量IO。

但其實RS的堆內(nèi)存并不是越大越好,我們開始使用HBase的時候,對CMS和G1相關(guān)的參數(shù),進行了大量壓測,測試指標數(shù)據(jù)表明,內(nèi)存分配的越大,吞吐量和p99讀寫平均延時會有一定程度的變差(也有可能是我們的JVM相關(guān)參數(shù),當時調(diào)配的不合理)。

在我們?yōu)榧杭蒵dk15,設(shè)置為ZGC之后,多次壓測并分析JVM日志之后,得出結(jié)論,在犧牲一定吞吐量的基礎(chǔ)上,集群的GC表現(xiàn)能力確實提升的較為明顯,尤其是GC的平均停頓時間,99.9%能維持在10ms以下。

而且ZGC號稱管理上T的大內(nèi)存,停頓時間控制在10ms之內(nèi)(JDK16把GC停頓時間控制在1ms內(nèi),期待JDK17 LTS),STW時間不會因為堆的變大而變長。

因此理論上,增加RS堆內(nèi)存之后,GC一樣不會成為瓶頸。

3.2 單節(jié)點多Region Server的部署

之所以考慮在單節(jié)點上部署多個Region Server的進程,是因為我們單個物理機的資源配置很高,內(nèi)存充足(三百多G,RS堆內(nèi)存只分了32G)、而HBase又是弱計算類型的服務(wù),平時CPU的利用率低的可憐,網(wǎng)絡(luò)方面亦未見瓶頸,唯一掉鏈子的也就屬磁盤了,未上SSD,IO延遲較為嚴重。

當然,也曾考慮過虛擬機的方案,但之前YCSB壓測的數(shù)據(jù)都不太理想;K8s的調(diào)研又是起步都不算,沒有技術(shù)積累。因此,簡單、直接、易操作的方案就是多RS部署了。

以下內(nèi)容先敘述CDH中多RS進程部署的一些關(guān)鍵流程,后續(xù)將在多RS、單RS、單RS大堆環(huán)境中,對集群進行基準性能測試,并對比試驗數(shù)據(jù),分析上述兩種優(yōu)化方案的優(yōu)劣。

我們使用的HBase版本是2.1.0-cdh6.3.2,非商業(yè)版,未上Kerberos,CDH中HBase相關(guān)的jar包已替換為用JDK15編譯的jar。

多Region Server的部署比較簡單,最關(guān)鍵的是修改hbase-site.xml中region server的相關(guān)端口,避免端口沖突即可??刹僮髁鞒倘缦?。

修改所需配置文件

hbase-site.xml配置文件一定不要直接從/etc/hbase/conf中獲取,這里的配置文件是給客戶端用的。CDH管理的HBase,配置文件都是運行時加載的,所以,找到HBase最新啟動時創(chuàng)建的進程相關(guān)的目錄,即可獲取到服務(wù)端最新的配置文件,如:/var/run/cloudera-scm-agent/process/5347-hbase-REGIONSERVER。需要準備的目錄結(jié)構(gòu)如下:

dir-hbase

不需要HBase完整安裝包中的內(nèi)容(在自編譯的完整安裝包中運行RS進程時,依賴沖突或其他莫名其妙的報錯會折磨的你抓狂),只需要bin、conf目錄即可,pids文件夾是自定義的,RS進程對應(yīng)pid文件的輸出目錄,start_rs.sh、stop_rs.sh是自定義的RS進程的啟動和關(guān)閉腳本。

重點修改下圖標注的配置文件,

conf
  • hbase-site.xml
hbase-site
  • log4j
log.threshold=INFO
main.logger=RFA
hbase.root.logger=INFO,RFA
log4j.rootLogger=INFO,RFA
log.dir=/var/log/hbase
log.file=${hbase.log.file}
max.log.file.size=200MB
max.log.file.backup.index=10
log4j.appender.RFA=org.apache.log4j.RollingFileAppender
log4j.appender.RFA.File=/var/log/hbase/${hbase.log.file}
log4j.appender.RFA.layout=org.apache.log4j.PatternLayout
log4j.appender.RFA.layout.ConversionPattern=%d{ISO8601} %p %c: %m%n
log4j.appender.RFA.MaxFileSize=200MB
log4j.appender.RFA.MaxBackupIndex=10
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.target=System.err
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{2}: %m%n
log4j.logger.org.apache.zookeeper=INFO
log4j.logger.org.apache.hadoop.hbase.zookeeper.ZKUtil=INFO
log4j.logger.org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher=INFO
# CM日志采集相關(guān)的配置去掉,否則啟動報Log4j相關(guān)的異常
  • hbase-env.sh
# 自定義HBase 配置文件的路徑
export CUSTOM_HBASE_HOME=`dirname "$(cd "$(dirname $0)"; pwd)"`
# 定義HBase Region Server的相關(guān)端口號
REGION_SERVER_PORT=16110
# 定義JMX配置,暴露JMX端口給普羅米修斯,采集相關(guān)Metric數(shù)據(jù)
JMX_REMOTE_PORT=11100
JMX_PROMETHEUS_AGENT_PORT=9320
# 定義本機名稱
HOST_NAME="`hostname`"
export JAVA_HOME=/usr/java/openjdk-15.0.2
# RS運行時找的HBase home,非常重要
export HBASE_HOME=/opt/cloudera/parcels/CDH/lib/hbase/
# RS運行時找的HBase配置文件home,非常重要
export HBASE_CONF_DIR=$CUSTOM_HBASE_HOME/conf
# HBASE日志相關(guān)
export HBASE_LOG_DIR=/var/log/hbase
export HBASE_LOGFILE="hbase-regionserver-$HOST_NAME-$REGION_SERVER_PORT"
export HBASE_PID_DIR=$CUSTOM_HBASE_HOME/pids

export HBASE_OPTS=""

# Uncomment one of the below three options to enable java garbage collection logging for the server-side processes.
export HBASE_REGIONSERVER_OPTS="-XX:MaxDirectMemorySize=26843545600 -Xms34359738368 -Xmx34359738368 -XX:ReservedCodeCacheSize=256m -XX:InitialCodeCacheSize=256m -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -XX:ConcGCThreads=2 -XX:ParallelGCThreads=6 -XX:ZCollectionInterval=120 -XX:ZAllocationSpikeTolerance=5 -XX:+UnlockDiagnosticVMOptions -XX:-ZProactive -Xlog:safepoint,classhisto*=trace,age*,gc*=info:file=/var/log/hbase/region-server-$REGION_SERVER_PORT-zgc-%t.log:time,tid,tags:filecount=5,filesize=500m --illegal-access=warn --add-exports java.base/jdk.internal=ALL-UNNAMED --add-exports java.base/jdk.internal.access=ALL-UNNAMED --add-exports java.base/jdk.internal.misc=ALL-UNNAMED --add-exports java.base/java.lang=ALL-UNNAMED --add-exports java.base/java.lang.reflect=ALL-UNNAMED --add-exports java.base/java.nio=ALL-UNNAMED --add-exports java.base/sun.nio.ch=ALL-UNNAMED --add-exports java.base/sun.nio.cs=ALL-UNNAMED --add-exports java.base/java.security=ALL-UNNAMED --add-exports java.base/sun.security.pkcs=ALL-UNNAMED --add-exports java.security.jgss/sun.security.krb5=ALL-UNNAMED --add-exports java.base/java.util=ALL-UNNAMED --add-exports java.base/java.util.regex=ALL-UNNAMED --add-exports java.base/java.util.zip=ALL-UNNAMED --add-exports java.base/java.net=ALL-UNNAMED --add-exports java.base/java.io=ALL-UNNAMED --add-exports java.base/javax.crypto=ALL-UNNAMED --add-opens java.base/jdk.internal=ALL-UNNAMED --add-opens java.base/jdk.internal.access=ALL-UNNAMED --add-opens java.base/jdk.internal.misc=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/sun.nio.cs=ALL-UNNAMED --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/sun.security.pkcs=ALL-UNNAMED --add-opens java.security.jgss/sun.security.krb5=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.regex=ALL-UNNAMED --add-opens java.base/java.util.zip=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/javax.crypto=ALL-UNNAMED -Dorg.apache.hbase.thirdparty.io.netty.tryReflectionSetAccessible=true -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.port=$JMX_REMOTE_PORT -javaagent:/data/hbase/monitor/jmx_prometheus_javaagent-0.11.0.jar=$JMX_PROMETHEUS_AGENT_PORT:/data/hbase/monitor/hbase_regonserver.yml -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/hbase_hbase-REGIONSERVER-$HOST_NAME-$REGION_SERVER_PORT.hprof -XX:OnOutOfMemoryError=/opt/cloudera/cm-agent/service/common/killparent.sh"

export HBASE_MANAGES_ZK=false

還有日志文件名的一些輸出細節(jié),可以按需在bin/hbase-daemon.sh中修改。

  • start_rs.sh 和stop_rs.sh
#!/usr/bin/env bash
current_hbase_home="$(cd "$(dirname $0)"; pwd)"
sh $current_hbase_home/bin/hbase-daemon.sh start regionserver


#!/usr/bin/env bash
current_hbase_home="$(cd "$(dirname $0)"; pwd)"
sh $current_hbase_home/bin/hbase-daemon.sh stop regionserver

運行或關(guān)閉RS進程

sudo -uhbase sh /data/hbase/hbase_16110/start_rs.sh
sudo -uhbase sh /data/hbase/hbase_16110/stop_rs.sh

中間有異常,請查看相關(guān)日志輸出。

4. 單RS、多RS、單RS大堆集群環(huán)境的YCSB壓測數(shù)據(jù)對比

集群Region數(shù)瘋漲,當寫入存在壓力時,會導(dǎo)致RS節(jié)點異常退出。為了解決目前的這種窘境,本次優(yōu)化主要從單節(jié)點多Region Server部署和提高單個Region Server節(jié)點的堆內(nèi)存兩方面著手。

那這兩種優(yōu)化方案對HBase的讀寫性能指標,又會產(chǎn)生什么樣的影響呢?我們以YCSB基準測試的結(jié)果指標數(shù)據(jù)做為參考,大致評價下這兩種應(yīng)急方案的優(yōu)劣。

用于此次測試的HBase集群的配置

  • 使用的集群:5個節(jié)點集群(5個Region Server)
  • 說明:Intel(R) Xeon(R) CPU E5-2620 v4 @ 2.10GHz,377GB Ram,12-3TB磁盤
  • 安全性:未配置(無Kerberos)
  • CDH版本:hbase2.1.0-cdh6.3.2
  • JDK使用AdoptOpenJDK15
  • HBase Region Server配置了32GB堆
  • HBase HMaster已配置有8GB堆

此次測試使用的數(shù)據(jù)集大小

  • 1TB數(shù)據(jù)、10億行

測試方法

壓測時選擇的讀寫負載盡量模擬線上的讀寫場景,分別為:讀寫3/7、讀寫7/3、讀寫5/5;

壓測時唯一的變量條件是:多RS部署(32G堆,在每個節(jié)點上啟動3個RS進程,相當于集群中一共有15個RS節(jié)點)、單RS部署(32G小堆)和單RS部署(100G大堆),并盡可能保證其他實驗條件不變,每個YCSB的工作負載各自運行20分鐘左右,并且重復(fù)完整地運行5次,兩次運行之間沒有重新啟動,以測量YCSB的吞吐量等指標,收集的測試結(jié)果數(shù)據(jù)是5次運行中最后3次運行的平均值,為了避免第一輪和第二輪的偶然性,忽略了前兩次的測試。

YCSB壓測的命令是:

python ycsb-0.15.0/bin/ycsb run hbase20 -P ycsb-0.15.0/workloads/workloada_3_7 -p table=usertable -p columnfamily=cf -p operationcount=100000000 -threads 200 -jvm-args '-Xms2048m -Xmx4096m -Xmn1024m'  -cp /etc/hbase/conf/hbase-site.xml -s > logs/run-workloada_3_7.log

收集實驗數(shù)據(jù)后,大致得出不同讀寫負載場景下、各個實驗條件下的指標數(shù)據(jù),如下圖。

指標數(shù)據(jù)

上述的測試數(shù)據(jù)比較粗糙,但大致也能得出些結(jié)論,提供一定程度上的參考。

  • 集群Region數(shù)過多,橫向增加節(jié)點,減少每個RS節(jié)點管理的Region數(shù),在應(yīng)對寫入洪峰時能起到立竿見影的效果。
  • 15個RS進程,在應(yīng)對寫入壓力時,也會游刃有余,不會導(dǎo)致RS節(jié)點意外退出。觀其吞吐量,與單純5個RS節(jié)點相比,也并沒有理論值幾倍的提升(或許是單個客戶端的壓測能力有限,也許是多個RS進程受制于同一個IO環(huán)境),但其(平均、P99)讀寫延遲都有明顯的改善。
  • 盲目增加RS的堆內(nèi)存,并不一定會得到性能上的提升(CDH官方所提供的建議值也是32G),內(nèi)存的增加,GC的壓力可能會隨之增加,我們需要調(diào)試出一組合適的GC參數(shù),或許才能得到最大化地提高集群性能。

5. 總結(jié)

多RS進程部署的模式,起到了一定程度上的進程間資源隔離的作用,分擔了原先單臺RS管理Region的壓力,最大化利用了物理機的資源,但多出來的一些Region Server,需要單獨的管理腳本和監(jiān)控系統(tǒng)來維護,增加了維護成本。多個RS依賴同一臺物理機,物理節(jié)點宕機便會影響多個RS進程,同時,某一個Region Server出現(xiàn)熱點,壓力過大,資源消耗過度,也許會引起同機其他進程的不良,在一定程度上,犧牲了穩(wěn)定性和可靠性。

增加單個RS進程的堆內(nèi)存,MemStore在一定程度上會被分配更充裕的內(nèi)存空間,減小了flush的頻次,勢必會削弱寫入的壓力,但也可能會增加GC的負擔,我們或許需要調(diào)整出合適的GC參數(shù),甚至需要調(diào)優(yōu)HBase本身的一些核心參數(shù),才能兼顧穩(wěn)定和性能。然而,這就又是一件漫長而繁瑣的事情了,在此不過分探討。

面對性能瓶頸的出現(xiàn),我們不能盲目地擴充機器,在應(yīng)急方案采取之后,我們需要做一些額外的、大量的優(yōu)化工作,這或許才是上上之策。

6. 參考鏈接

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容