Hbase是一個(gè)高可靠、高性能、面向列、可伸縮的分布式數(shù)據(jù)庫(kù)。其底層的LSM數(shù)據(jù)結(jié)構(gòu)和RowKey有序排列等架構(gòu)上的獨(dú)特設(shè)計(jì),使得hbase具有非常高的寫(xiě)入性能。但是在實(shí)際使用中,由于對(duì)hbase的寫(xiě)入方面的機(jī)制沒(méi)有深入的了解,無(wú)法有效發(fā)揮出hbase的寫(xiě)入性能。本文主要記錄了在剛開(kāi)始使用hbase時(shí),對(duì)hbase沒(méi)有太多的了解的基礎(chǔ)上,在寫(xiě)入方面遇到的一個(gè)問(wèn)題和解決方法。
Hbase原生支持java寫(xiě)入,但由于實(shí)際業(yè)務(wù)的需要和遷移的成本,要使用python來(lái)寫(xiě)入hbase。hbase中集成了thrift框架,支持使用python對(duì)hbase進(jìn)行操作。happybase是 FaceBook 員工開(kāi)發(fā)的操作HBase的 Python庫(kù),其基于 Thrift,封裝了大多數(shù)hbase的操作方法,使得通過(guò)python也能對(duì)hbase進(jìn)行操作。
問(wèn)題描述
在初步安裝好6個(gè)RS的Hbase集群上,新建一張測(cè)試表,使用200G的數(shù)據(jù),進(jìn)行寫(xiě)入性能的測(cè)試。
采用happybase寫(xiě)入hbase,下面是一段簡(jiǎn)化的代碼:
(con, table) = get_hbase_con(table_name=table_name)
with table.batch(batch_size=200) as batch:
with open(filepath) as fp:
for line in fp:
records = line.strip().split(',')
cf={"cf:data":records}
batch.put(row=rowkey, data=cf)
Happybase的put方法原本就是采用batch批量提交數(shù)據(jù),在代碼中使用table.batch可以自己控制batch_size的大小。采用batch提交,減少rpc請(qǐng)求次數(shù),提高寫(xiě)入的性能。
運(yùn)行程序時(shí),程序通過(guò)socket與hbase的thriftsever連接,將請(qǐng)求發(fā)給thriftsever,由thriftsever調(diào)用hbase的client端進(jìn)行寫(xiě)入。使用單線(xiàn)程進(jìn)行數(shù)據(jù)的寫(xiě)入一段時(shí)間之后,總會(huì)出現(xiàn)socket.timeout的問(wèn)題。一旦出現(xiàn)該問(wèn)題后,程序終止運(yùn)行,如下圖所示:
出現(xiàn)此問(wèn)題時(shí),寫(xiě)入的每秒請(qǐng)求數(shù)才5200多,并且是持續(xù)的向一個(gè)region中寫(xiě)入。
問(wèn)題分析和解決
為了解決這個(gè)問(wèn)題,我安裝了ganglia監(jiān)控工具。在寫(xiě)入的時(shí)候,查看集群上的指標(biāo),發(fā)現(xiàn)寫(xiě)入的節(jié)點(diǎn)上負(fù)載過(guò)重,cpu利用率飆升,其它節(jié)點(diǎn)基本處于休眠狀態(tài)。由此現(xiàn)象可以初步斷定是hbase寫(xiě)入的熱點(diǎn)問(wèn)題造成了timeout。解決hbase的熱點(diǎn)問(wèn)題,關(guān)鍵在于rowkey的設(shè)計(jì)和預(yù)分區(qū)上。
Hbase的RowKey是按字點(diǎn)排序由低到高進(jìn)行存儲(chǔ)的,在設(shè)計(jì)時(shí)需要遵循散列性,唯一性,同時(shí)也要兼顧實(shí)際業(yè)查詢(xún)的需求,這樣在寫(xiě)入的過(guò)程中不至于造成一臺(tái)集群負(fù)載過(guò)重的情況。
現(xiàn)有的RowKey的設(shè)計(jì)規(guī)則如下:
很明顯其是按時(shí)間有序排列的,不符合rowkey的散列性,原始數(shù)據(jù)是按時(shí)間順序排列的,在插入時(shí),就會(huì)一直向同一個(gè)region中插入,因此造成熱點(diǎn)問(wèn)題。通過(guò)改進(jìn),將后面的標(biāo)示位放置rowkey的前綴,由于標(biāo)示位是散列的,沒(méi)有規(guī)律,RowKey的設(shè)計(jì)變成了:
RowKey = 標(biāo)示位+預(yù)留10位+時(shí)間戳
清空測(cè)試表后,重新插入數(shù)據(jù)測(cè)試,發(fā)現(xiàn)插入時(shí)仍然會(huì)出現(xiàn)熱點(diǎn)問(wèn)題。繼續(xù)進(jìn)行分析,查到一段話(huà):
默認(rèn)情況下,當(dāng)我們通過(guò)hbaseAdmin指定TableDescriptor來(lái)創(chuàng)建一張表時(shí),只有一個(gè)region正處于混沌時(shí)期,start-end key無(wú)邊界,可謂海納百川。所有的rowkey都寫(xiě)入到這個(gè)region里,然后數(shù)據(jù)越來(lái)越多,region的size越來(lái)越大時(shí),大到一定的閥值,hbase就會(huì)將region一分為二,成為2個(gè)region,這個(gè)過(guò)程稱(chēng)為分裂(region-split)。
如果我們就這樣默認(rèn)建表,表里不斷的put數(shù)據(jù),更嚴(yán)重的是我們的rowkey還是順序增大的,是比較可怕的。存在的缺點(diǎn)比較明顯:首先是熱點(diǎn)寫(xiě),我們總是向最大的start key所在的region寫(xiě)數(shù)據(jù),因?yàn)槲覀兊膔owkey總是會(huì)比之前的大,并且hbase的是按升序方式排序的。所以寫(xiě)操作總是被定位到無(wú)上界的那個(gè)region中;其次,由于熱點(diǎn),我們總是往最大的start key的region寫(xiě)記錄,之前分裂出來(lái)的region不會(huì)被寫(xiě)數(shù)據(jù),有點(diǎn)打入冷宮的感覺(jué),他們都處于半滿(mǎn)狀態(tài),這樣的分布也是不利的。
hbase熱點(diǎn)問(wèn)題(數(shù)據(jù)傾斜)解決方案—rowkey散列和預(yù)分區(qū)設(shè)計(jì) - - ITeye技術(shù)網(wǎng)站
按照這個(gè)解釋?zhuān)词箤?duì)rowkey進(jìn)行散列化設(shè)計(jì),仍然不能解決熱點(diǎn)問(wèn)題,初始建表沒(méi)有進(jìn)行預(yù)分區(qū)時(shí),只有一個(gè)region,其并沒(méi)有一個(gè)上界,導(dǎo)致在插入時(shí)還是不斷的向一個(gè)region中插入。因此接下的操作是進(jìn)行表進(jìn)行預(yù)分區(qū)。
由于RowKey的設(shè)計(jì)已經(jīng)變成了散列性,導(dǎo)致預(yù)分區(qū)不好確定每個(gè)region的RowKey范圍。針對(duì)此問(wèn)題,在有限的數(shù)據(jù)中,采取了將所有的rowkey提取出來(lái)排序后,分段抽取的方式,確定了每個(gè)region的范圍。
通過(guò)rowkey的散列化和預(yù)分區(qū),每個(gè)節(jié)點(diǎn)上都啟動(dòng)ThrfftServer服務(wù),將200G的大文件拆成了多個(gè)小文件,然后采用python的多進(jìn)程再次進(jìn)行測(cè)試。此時(shí)出現(xiàn)了可喜的結(jié)果,
從圖可以看到,寫(xiě)入的請(qǐng)求分布的比較均勻,不再集中到一個(gè)region 上。從ganglia上的監(jiān)控上看,單個(gè)節(jié)點(diǎn)的負(fù)載比之前有很大的下降。到此,hbase的寫(xiě)入熱點(diǎn)問(wèn)題基本上解決。
從圖可以看到,寫(xiě)入的請(qǐng)求分布的比較均勻,不再集中到一個(gè)region 上。從ganglia上的監(jiān)控上看,單個(gè)節(jié)點(diǎn)的負(fù)載比之前有很大的下降。到此,hbase的寫(xiě)入熱點(diǎn)問(wèn)題基本上解決。
但是timeout的情況依然出現(xiàn),出現(xiàn)時(shí)間較之前有延后,可見(jiàn)問(wèn)題并沒(méi)有徹底的解決。timeout出現(xiàn)時(shí)間延后,說(shuō)明解決hbase寫(xiě)入的熱點(diǎn)問(wèn)題對(duì)timeout問(wèn)題是有幫助的。
因此我對(duì)hbase的寫(xiě)入過(guò)程中進(jìn)行了更加深入的分析,看看數(shù)據(jù)在寫(xiě)入的過(guò)程中究竟發(fā)生了什么。
Hbase寫(xiě)入過(guò)程包括了客戶(hù)端和服務(wù)端,采用客戶(hù)端批量寫(xiě)入數(shù)據(jù),數(shù)據(jù)會(huì)現(xiàn)在本地的buffer中緩存,到達(dá)閾值后,客戶(hù)端就會(huì)將數(shù)據(jù)推送到服務(wù)端,服務(wù)端收到數(shù)據(jù)后先寫(xiě)日志,然后在寫(xiě)入memstore中,memstore滿(mǎn)后就會(huì)flush到storefiles,當(dāng)storefiles 的數(shù)量增長(zhǎng)到一定閾值后,就會(huì)進(jìn)行compact成更大的storefile,當(dāng)storefile達(dá)到閾值后就會(huì)split成兩個(gè)region,被hmaster分配到其它的regionserver中。
由于寫(xiě)入過(guò)快,服務(wù)器端需要大量compact,split操作時(shí),很容易阻塞數(shù)據(jù)的寫(xiě)入,會(huì)出現(xiàn)has too many store files; delaying flush up to 90000ms
等問(wèn)題。一旦出現(xiàn)服務(wù)端阻塞寫(xiě)入的問(wèn)題,client端就會(huì)延遲收到響應(yīng)結(jié)果。如果在規(guī)定的時(shí)間內(nèi),客戶(hù)端仍未接收到服務(wù)器端的響應(yīng),socket就會(huì)出現(xiàn)timeout的情況,然后客戶(hù)端和服務(wù)器端的連接就會(huì)斷掉,寫(xiě)入過(guò)程終止。通過(guò)對(duì)日志的分析,也說(shuō)明了出現(xiàn)timeout的問(wèn)題時(shí),hbase正在進(jìn)行compact或split操作。
從上述的分析中,明確了解決timeout問(wèn)題的下一步目標(biāo)是優(yōu)化hbase的寫(xiě)入性能。通過(guò)對(duì)hbase的region size、rpc超時(shí)時(shí)間、scanner超時(shí)時(shí)間、memstore fluse size、blockingStoreFiles等參數(shù)優(yōu)化,降低compact和split的頻率,延遲超時(shí)出現(xiàn)的時(shí)間,也降低了超時(shí)的頻率。
除非全部手動(dòng)進(jìn)行,hbase進(jìn)行compact和split是不可避免的,因此寫(xiě)入超時(shí)也是不可避免的。從happybase的githup上找到了解決timeout一勞永逸的方法,將客戶(hù)端的timeout置為不限。thriftpy0.3.3設(shè)置了默認(rèn)的超時(shí)時(shí)間為3s,要想永不超時(shí),將timeout設(shè)置為None即可。
Since thriftpy 0.3.3, there has been a socket_timeout option to TSocket. The default is 3000 ms. Setting it to None means “never timeout”. Remove the if self.timeout is not None conditional so a value of None can also be set explicitly from happybase.
Set socket timeout unconditionally on TSocket by ecederstrand · Pull Request #146 · wbolster/happybase · GitHub
# timeout = None
con = happybase.Connection(host=host, autoconnect=False, timeout=None, transport='buffered')
網(wǎng)上有建議將timeout設(shè)置更長(zhǎng)一點(diǎn),但是由于網(wǎng)絡(luò)以及服務(wù)端的問(wèn)題不確定性,超時(shí)的風(fēng)險(xiǎn)很大。直接設(shè)置成None,客戶(hù)端就會(huì)一直等待服務(wù)端的響應(yīng),再也不會(huì)出現(xiàn)超時(shí)的問(wèn)題。
上述方法雖然一勞永逸,但是如果有潛在的風(fēng)險(xiǎn)就是一直阻塞。推薦采取手動(dòng)的處理超時(shí)問(wèn)題。在捕獲到超時(shí)后,選擇斷開(kāi)連接,暫停一小段時(shí)間寫(xiě)入,重新連接然后進(jìn)行寫(xiě)入。
總結(jié)
初入Hbase,作為一個(gè)小白,耗費(fèi)了較長(zhǎng)的時(shí)間才解決寫(xiě)入超時(shí)的問(wèn)題。一個(gè)問(wèn)題牽涉了Hbase的方方面面,使我收獲滿(mǎn)滿(mǎn):
1、更了解hbase的特點(diǎn),RowKey的設(shè)計(jì),表的預(yù)分區(qū)。
2、從源碼上了解Hbase的寫(xiě)入過(guò)程
4、如何優(yōu)化hbase性能
5、分析和解決問(wèn)題的思路