HBase那些事

HBase那些事

@(大數據工程學院)[HBase, Hadoop, 優化, HadoopChen, hbase]

[TOC]

HBase特性

HBase是什么

HBase(Hadoop Database),一個高可靠性、高性能、面向列、可伸縮、 實時讀寫的分布式數據庫。

選擇特性

  • 列式儲存:方便存儲結構化和半結構化數據,方便做數據壓縮,對針對某一列查詢有非常大的IO優勢;
  • KV儲存:可以通過key快速查詢到其value,任意的value格式;
  • 海量數據:PB級;
  • 低延時:毫秒級別;
  • 高吞吐量:百萬查詢/每秒;
  • 主鍵索引:基于RowKey的索引,快速檢索KV值;
  • Hadoop集成:文件存儲于HDFS之上,高效集成Hive、Spark;
  • BigTable:HBase是Google的BigTable架構的一個開源實現;
  • CRUD: 支持增刪查改操作;
  • 支持Row級別事務:HBase本身均是基于Row級別的操作,所以行鎖即可以保證ACID;
  • 稀疏存儲: 節省空間、提高查詢效率、便于壓縮;
  • 支持非(半)結構化:列式儲存和KV結構;
  • 容錯性:分布式架構,高效錯誤轉移;
  • 硬件成本低廉:類似于 Hadoop 的分布式集群,硬件成本低廉;
  • 第三方SQL支持:phoenix、hive、sparkSql等等;
  • 開源:基于java開發的開源軟件,社區活躍;

HBase環境搭建

CDH

CM安裝HBase比較簡單,參照安裝步驟即可:

image.png

分布式環境

前提條件

  1. Hadoop集群:hadoop01,hadoop02, hadoop03
  2. 用戶互信
  3. HBase安裝包
  4. JDK
  5. Zookeeper

安裝部署

  1. 解壓安裝包:sudo tar -zxf hbase-1.2.4-bin.tar.gz -C /usr/local/hbase/
  2. 配置環境變量:vi /etc/profile ; export HBASE_HOME=/usr/local/hbase/
  3. 修改配置文件:$HBASE_HOME/conf/hbase-site.xml
<configuration>
  <property>
    <name>hbase.rootdir</name>
       <value>hdfs://nameservice1/hbase</value>
  </property>
  <property>
    <name>hbase.cluster.distributed</name>
    <value>true</value>
  </property>
  <property>
    <name>hbase.zookeeper.quorum</name>
    <value>hadoop01:2181,hadoop02:2181,hadoop03:2181</value>
  </property>
</configuration>
  1. 修改啟動腳本:$HBASE_HOME/conf/hbase-env.sh; export JAVA_HOME=/usr/java/jdk/
  2. 修改HMaster配置:$HBASE_HOME/conf/regionservers
hadoop01
hadoop02
hadoop03
  1. 新增備用HMater: $HBASE_HOME/conf/backup-masters
hadoop02
  1. 啟動集群:$HBASE_HOME/bin/start-hbase.sh
  2. 驗證環境:
create 'hello', 'cf'
put 'hello', 'one', 'cf:a', 'b'
get 'hello', 'one'

HBase架構

看圖說話

HBase采用Master/Slave架構搭建集群,它隸屬于Hadoop生態系統,由一下類型節點組成:HMaster節點、HRegionServer節點、ZooKeeper集群,而在底層,它將數據存儲于HDFS中,因而涉及到HDFS的NameNode、DataNode等,總體結構如下:

image.png
  • HMaster :
  1. 管理HRegionServer,實現其負載均衡;
  2. 管理和分配HRegion;實現DDL操作;
  3. 管理namespace和table的元數據;權限控制;
  • HRegionServer :
  1. 存放和管理本地HRegion;
  2. 讀寫HDFS,管理Table中的數據;
  3. Client直接通過HRegionServer讀寫數據;
  • ZooKeeper :
  1. 存放整個 HBase集群的元數據以及集群的狀態信息;
  2. 實現HMaster主從節點的failover。
  • HRegion:
  1. HBase表數據按行切分成多個HRegion;
  2. HRegion按照列簇切分成多個Store;
  3. 每個Store由一個MemStore和多個StoreFile(HFile)組成;
  4. 每個HRegion還包含一個HLog文件,用于數據恢復;
  • Hadoop:
  1. RegionServer通過DFS Client讀寫HDFS數據;
  2. RS和DN盡量保證在統一節點,確保數據本地化,提升讀寫性能;
  3. MemStore滿足一定條件下會Flush到HDFS上的HFile文件。

HBaseTable

HBase表主要包含namespace(命名空間)、tableName(表名)、rowKey(主鍵)、columnFamily(列簇)、qualifier(列)、cell(值)、timeStamp(版本),用結構化的形式展現如下:

| NameSpace | TableName | RowKey | CF1:Name | CF2:Age
| -------- | -----: | :----: |
| Default | HelloWorld | 001 | Allen | 28
| Default | HelloWorld | 002 | Curry | 26
| Default | HelloWorld | 003 | Brant | 20
| Default | HelloWorld | 004 | Sean | 31

  • 存儲:表HelloWorld數據量較小,暫時會保存在一個RegionServer的一個Region內;
  • Split:當Region達到最大Region限制(假定256M)后,則會Split成兩個Region,這里假定001和002在RegionA,003和004在RegionB:
image.png

讀流程

HBase表數據所在Region,Region所在RegionServer,均存放在HBase表hbase:meta,由于元數據較小,一個2G的HRegion大概可以支持4PB的數據,所以meta表是不可Split的。Meta表本身是一個HBase表,該表所在RS的地址存放在ZK,于是讀數據的流程如下:

  1. 需求:從HellWorld表讀取002這條記錄;
  2. Client從ZK讀取meta表所在RegionServer地址信息;
  3. Client和Meta表所在RS建立連接,獲取HelloWorld表當中RowKey=002的記錄所在的RS地址;并將該地址緩存在客戶端;
  4. Client和002所在RS建立連接,申請查詢002記錄數據;
  5. 002所在RS根據RowKey獲取Region信息,并初始化Scaner,Scaner優先掃描BlockCache,然后掃描MemStore,然后掃描StoreFile(HFile),直到找到002記錄為止;
  6. RS返回結果數據給Client。

寫流程

  1. 需求:寫入005到HelloWorld表;
  2. Client從ZK去讀Meta表所在RS地址;
  3. Client讀取Meta表數據,確認005應該寫入RS地址;
  4. Client將005數據寫入RS02的HLog,用于災備恢復,然后寫入RegionB的memStore;此刻寫操作已完成,反饋給client;
  5. 當memStore滿足一定條件下會Flush到hdfs,hdfs再根據寫策略復制副本。

Split和Compaction

HBase擴展和負載均衡的基本單位是Region。Region從本質上說是行的集合。當Region的大小達到一定的閾值,該Region會自動分裂(split),或者當該Region的HFile數太多,也會自動合并(compaction)。

對于一張表(HTable)而言,初始時只會有一個Region。表的數據量不斷增加,系統會監控此表以確保數據量不會超過一個配置的閾值。如果系統發現表容量超過了限制,該Region會被一分為二。分裂主要看行鍵(row key),從Region正中的鍵開始分裂,并創建容量大致相等的兩個Region。

根據上述寫流程會發現HBase是一種Log-Structured Merge Tree架構模式,用戶數據寫入先寫WAL,再寫緩存,滿足一定條件后緩存數據會執行flush操作真正落盤,形成一個數據文件HFile。隨著數據寫入不斷增多,flush次數也會不斷增多,進而HFile數據文件就會越來越多。然而,太多數據文件會導致數據查詢IO次數增多,因此HBase嘗試著不斷對這些文件進行合并,這個合并過程稱為Compaction。

BlockCache

上述讀流程中RS會優先掃描BlockCache,BlockCache是一個讀緩存,即“引用局部性”原理(也應用于CPU,分空間局部性和時間局部性,空間局部性是指CPU在某一時刻需要某個數據,那么有很大的概率在一下時刻它需要的數據在其附近;時間局部性是指某個數據在被訪問過一次后,它有很大的概率在不久的將來會被再次的訪問),將數據預讀取到內存中,以提升讀的性能。

HBase數據按照block塊存儲,默認是64K,HBase中Block分為四種類型:

  • Data Block用于存儲實際數據,通常情況下每個Data Block可以存放多條KeyValue數據對;
  • Index Block通過存儲索引數據加快數據查找;
  • Bloom Block通過一定算法可以過濾掉部分一定不存在待查KeyValue的數據文件,減少不必要的IO操作;
  • Meta Block主要存儲整個HFile的元數據。

HBase中提供兩種BlockCache的實現:默認on-heap LruBlockCache和BucketCache(通常是off-heap)。通常BucketCache的性能要差于LruBlockCache,然而由于GC的影響,LruBlockCache的延遲會變的不穩定,而BucketCache由于是自己管理BlockCache,而不需要GC,因而它的延遲通常比較穩定,這也是有些時候需要選用BucketCache的原因。

  • LruBlockCache :HBase默認的BlockCache實現方案。Block數據塊都存儲在 JVM heap內,由JVM進行垃圾回收管理。它將內存從邏輯上分為了三塊:single-access區、mutil-access區、in-memory區,分別占到整個BlockCache大小的25%、50%、25%。一次隨機讀中,一個Block塊從HDFS中加載出來之后首先放入signle區,后續如果有多次請求訪問到這塊數據的話,就會將這塊數據移到mutil-access區。而in-memory區表示數據可以常駐內存,一般用來存放訪問頻繁、數據量小的數據,比如元數據,用戶也可以在建表的時候通過設置列族屬性IN-MEMORY= true將此列族放入in-memory區。很顯然,這種設計策略類似于JVM中young區、old區以及perm區。無論哪個區,系統都會采用嚴格的Least-Recently-Used算法,當BlockCache總量達到一定閾值之后就會啟動淘汰機制,最少使用的Block會被置換出來,為新加載的Block預留空間。
image.png
  • BucketCache :很明顯LruBlockCache的缺陷是GC,當吞吐量徒增,GC不及時則會造成RS因為OOM停止工作;于是BucketCache引入了堆外內存來緩存Block數據,當然BucketCache通過配置可以工作在三種模式下:heap,offheap和file。無論工作在那種模式下,BucketCache都會申請許多帶有固定大小標簽的Bucket,一種Bucket存儲一種指定BlockSize的數據塊,BucketCache會在初始化的時候申請14個不同大小的Bucket,而且即使在某一種Bucket空間不足的情況下,系統也會從其他Bucket空間借用內存使用,不會出現內存使用率低的情況。接下來再來看看不同工作模式,heap模式表示這些Bucket是從JVM Heap中申請,offheap模式使用DirectByteBuffer技術實現堆外內存存儲管理,而file模式使用類似SSD的高速緩存文件存儲數據塊。
image.png
  • CombinedBlockCache :實際實現中,HBase將BucketCache和LRUBlockCache搭配使用,稱為CombinedBlockCache。和DoubleBlockCache不同,系統在LRUBlockCache中主要存儲Index Block和Bloom Block,而將Data Block存儲在BucketCache中。因此一次隨機讀需要首先在LRUBlockCache中查到對應的Index Block,然后再到BucketCache查找對應數據塊。BucketCache通過更加合理的設計極大降低了JVM GC對業務請求的實際影響。
image.png

MemStore

HBase寫入數據會先寫WAL,再寫緩存,WAL是用于RS故障后的數據恢復,而緩存MemStore則是為了提高寫入數據的性能。但MemStore是基于內存,一方面空間有限,一方面數據容易丟失,所以RegionServer會在滿足一定條件下講MemStore數據Flush到HDFS,條件如下:

  1. Memstore級別限制:當Region中任意一個MemStore的大小達到了上限(hbase.hregion.memstore.flush.size,默認128MB),會觸發Memstore刷新。

  2. Region級別限制:當Region中所有Memstore的大小總和達到了上限(hbase.hregion.memstore.block.multiplier * hbase.hregion.memstore.flush.size,默認 2* 128M = 256M),會觸發memstore刷新。

  3. Region Server級別限制:當一個Region Server中所有Memstore的大小總和達到了上限(hbase.regionserver.global.memstore.upperLimit * hbase_heapsize,默認 40%的JVM內存使用量),會觸發部分Memstore刷新。Flush順序是按照Memstore由大到小執行,先Flush Memstore最大的Region,再執行次大的,直至總體Memstore內存使用量低于閾值(hbase.regionserver.global.memstore.lowerLimit * hbase_heapsize,默認 38%的JVM內存使用量);HBase1.0后使用新參數hbase.regionserver.global.memstore.size。

  4. 當一個Region Server中HLog數量達到上限(可通過參數hbase.regionserver.maxlogs配置)時,系統會選取最早的一個 HLog對應的一個或多個Region進行flush

  5. HBase定期刷新Memstore:默認周期為1小時,確保Memstore不會長時間沒有持久化。為避免所有的MemStore在同一時間都進行flush導致的問題,定期的flush操作有20000左右的隨機延時。

  6. 手動執行flush:用戶可以通過shell命令 flush ‘tablename’或者flush ‘region name’分別對一個表或者一個Region進行flush。

HBase客戶端

HBase提供了許多客戶端,例如HBase Shell、JAVA API、REST、Thrift、Avro等等,另外MapReduce、Hive、Spark等分布式計算引擎均有接口讀寫HBase數據,詳細細節參考《HBase In Action》或者《HBase權威指南》。

HBase優化

了解了HBase的基本概念之后,在使用過程中針對每個環節均有優化策略,最常見的優化策略如下。

調整GC策略

HBase內存使用情況參考上文,當吞吐量徒增或者運行一定時長后,大部分內存被BlockCache和MemStore占據,一旦FGC則會“stop the world”,影響RS的正常工作,如果GC時間超過ZK的心跳TimeOut時間,該服務會被ZK標記為BAD狀態。所以合理高效的GC策略非常重要。

GC基本知識請參考《深入理解Java虛擬機》,針對HBase這里給出參考JVM參數:

  • -Xms16g、-Xmx16g:內存大小,根據集群情況設置,建議不要超過18G。
  • -Xmn1g:年輕代大小。
  • -XX:+UseParNewGC:設置年輕代為并發回收
  • -XX:+UseConcMarkSweepGC:啟用CMS算法
  • -XX:CMSInitiatingOccupancyFraction=70:年老代使用了70%時回收內存
  • -Djava.net.preferIPv4Stack=true:禁用IPv6
  • -XX:-CMSConcurrentMTEnabled(-號代表否認),當該標志被啟用時,并發的CMS階段將以多線程執行(因此,多個GC線程會與所有的應用程序線程并行工作)。該標志已經默認開啟,如果順序執行更好,這取決于所使用的硬件,多線程執行可以通過-XX:-CMSConcurremntMTEnabled禁用。
  • -XX:+CMSIncrementalMode. 在增量模式下,CMS 收集器在并發階段,不會獨占整個周期,而會周期性的暫停,喚醒應用線程。收集器把并發階段工作,劃分為片段,安排在次級(minor) 回收之間運行。
-Xms10g -Xmx10g -Xmn1g -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:MaxGCPauseMillis=60000 -XX:CMSInitiatingOccupancyFraction=70 -XX:PermSize=64m -XX:MaxPermSize=256m  -Djava.net.preferIPv4Stack=true -XX:MaxDirectMemorySize=3072m -XX:+CMSIncrementalMode

BucketCache Off-Heap

如果存在批量讀取HBase數據的請求,BlockCache可能會被迅速占滿,GC不及時RS又要悲劇,所以采用BucketCache+Off-Heap是一個不錯的優化點,或者不顧性能問題,關閉BlockCache。

  • BucketCache Off-Heap配置方法:
<property>     
   <name>hbase.bucketcache.size</name>    
   <value>3072</value> 
</property>
<property>
    <name>hbase.bucketcache.ioengine</name>
    <value>offheap</value> 
</property>
CombinedBlockCache策略下還需要設置L1緩存大?。?<property>
    <name>hbase.bucketcache.combinedcache.enabled</name>
    <value>true</value> 
</property>
<property>
 <name>hfile.block.cache.size</name>
 <value>0.2</value>
</property>
  • 內存簡易觀察方案:
jmap -heap pid  -- 內存使用情況
jmap -histo pid -- 內存粗略統計
jmap -F -dump:format=b,file=dump.file pid  -- dump文件
jhat dump.file -- 通過http://dshbase01:7000查看dump文件,但端口白名單導致無法訪問

線程并發數

HBase是一個高并發的分布式數據庫,牽扯到ZK、RegionServer、DataNode等組件,所以性能提升跟這些組件的并發線程數的控制離不開關系。

  • RS線程數:
hbase.regionserver.handler.count=300
hbase.regionserver.metahandler.count=300
該配置定義了每個Region Server上的RPC Handler的數量。Region Server通過RPC Handler接收外部請求并加以處理。所以提升RPC Handler的數量可以一定程度上提高HBase接收請求的能力。
  • ZK線程數:注意線程數增加帶來的內存消耗,相應提升ZK Heap大小;
maxClientCnxns=60
  • DataNode:注意線程數增加帶來的文件描述符的問題,調整最大文件描述符最大限制;
dfs.datanode.max.transfer.threads=8192
dfs.datanode.handler.count=100

Balancer

HBase是一種支持自動負載均衡的分布式KV數據庫,在開啟balance的開關(balance_switch)后,HBase的HMaster進程會自動根據 指定策略 挑選出一些Region,并將這些Region分配給負載比較低的RegionServer上。官方目前支持兩種挑選Region的策略,一種叫做DefaultLoadBalancer,另一種叫做StochasticLoadBalancer。由于HBase的所有數據(包括HLog/Meta/HStoreFile等)都是寫入到HDFS文件系統中的, 因此HBase的Region移動其實非常輕量級。在做Region移動的時候,保持這個Region對應的HDFS文件位置不變,只需要將Region的Meta數據分配到相關的RegionServer即可,整個Region移動的過程取決于RegionClose以及RegionOpen的耗時,這個時間一般都很短。

Region切分

Region Split的依據是最大Region Size,調大該參數可以減少Region數量,同時也減少了MemStore數量和總空間。

hbase.hregion.max.filesize=2G

非本地化部署

HBase讀取數據時會從HDFS上的HFile加載數據到BlockCache,如果是本地數據則非常高效,如果不同機器或不同機架,則會帶來網絡消耗,同樣寫流程當中的Flush過程一樣會本地化的問題,所以建議DataNode和RegionServer部署在同一臺機器,因為RS調用hdfs client去讀寫數據,hdfs client優先會讀寫本地磁盤。另外也可以通過HBase Web UI觀察數據本地化情況。

MemStore Flush

MemStore的頻繁Flush會消耗大量服務器資源,根據Flush條件合理控制Flush次數是一個經常要觀察的優化點。

  • 調大全局MemStore大?。?/li>
<property>
 <name>hbase.regionserver.global.memstore.size</name>
 <value>0.6</value>
</property>
  • 調大HLog個數限制:每個Region均包含一個HLog,所以需要根據Region個數來動態調整;
hbase.regionserver.maxlogs=500

壓縮

壓縮通常會帶來較好的性能,因為CPU壓縮和解壓消耗的時間比從磁盤中讀取和寫入數據消耗的時間更短,HBase支持多種壓縮方式,例如snappy、lzo、gzip等等,不同壓縮算法壓縮比和效率有一定差異。開啟表壓縮的方式如下:

disable 'MILESTONE'
alter 'MILESTONE',{NAME=>'PUSH',COMPRESSION=>'snappy'}
alter 'MILESTONE',{NAME=>'BEHAVIOR',COMPRESSION=>'snappy'}
enable 'MILESTONE'

MSLAB

HBase內存回收策略CMS一定程度上降低了Stop時間,但卻避免不了內存碎片的問題,HBase提供了以下幾個參數,防止堆中產生過多碎片,大概思路也就是參考TLAB,叫做MSLAB,MemStore-Local Allocation Buffer。

一個regionserver的內存里各個region的數據混合在一起,當某個region被flush到磁盤時,就會形成很多堆碎片。其實這跟java中gc模型的假設是沖突的,同一時間創建的對象,會在同一時間消亡。這個問題可以通過Arena Allocation來解決,即每次分配內存時都是在一個更大的叫做arena的內存區域分配。一個典型的實現是TLAB,即每個線程維護自己的arena,每個線程用到的對象都在自己的arena區域分配。其實,jvm早已經實現了TLAB,但是這個對于hbase不適用,因為hbase的regionserver使用一個單獨的線程來處理所有region的請求,就算這個線程用arena方式分配還是會把所有region的數據混在一起。因此hbase自己實現了MSLAB,即每個region的memStore自己實現了arena,使各個region的數據分開,就不會形成太細的碎片。Arena里存放的是KeyValue對象,如果這些KeyValue對象是一樣大的,不會導致嚴重碎片,相反這些KeyValue對象引用的字節數組才是引起碎片的主因,因此要做的就是把這些字節數組分配在一起。

hbase.hregion.memstore.mslab.enabled=true // 開啟MSALB
hbase.hregion.memstore.mslab.chunksize=2m // chunk的大小,越大內存連續性越好,但內存平均利用率會降低
hbase.hregion.memstore.mslab.max.allocation=256K // 通過MSLAB分配的對象不能超過256K,否則直接在Heap上分配,256K夠大了

RowKey設計

HBase是根據Rowkey來進行檢索的,所以合理的Rowkey設計可以提高查詢效率。

  • Rowkey長度原則:Rowkey是一個二進制碼流,Rowkey的長度被很多開發者建議說設計在10~100個字節,不過建議是越短越好,不要超過16個字節。
  1. 數據的持久化文件HFile中是按照KeyValue存儲的,如果Rowkey過長比如100個字節,1000萬列數據光Rowkey就要占用100*1000萬=10億個字節,將近1G數據,這會極大影響HFile的存儲效率;

  2. MemStore將緩存部分數據到內存,如果Rowkey字段過長內存的有效利用率會降低,系統將無法緩存更多的數據,這會降低檢索效率。因此Rowkey的字節長度越短越好。

  3. 目前操作系統是都是64位系統,內存8字節對齊??刂圃?6個字節,8字節的整數倍利用操作系統的最佳特性。

  • Rowkey散列原則:如果Rowkey是按時間戳的方式遞增,不要將時間放在二進制碼的前面,建議將Rowkey的高位作為散列字段,由程序循環生成,低位放時間字段,這樣將提高數據均衡分布在每個Regionserver實現負載均衡的幾率。如果沒有散列字段,首字段直接是時間信息將產生所有新數據都在一個 RegionServer上堆積的熱點現象,這樣在做數據檢索的時候負載將會集中在個別RegionServer,降低查詢效率。

  • Rowkey唯一原則:必須在設計上保證其唯一性,這句是廢話。

  • 不同應用場景RowKey的設計也需要定制化考慮。

客戶端調優

  • Retry:hbase.client.retries.number=20
  • AutoFlush:將HTable的setAutoFlush設為false,可以支持客戶端批量更新。即當Put填滿客戶端flush緩存時,才發送到服務端。默認是true。
  • Scan Caching:scanner一次緩存多少數據來scan(從服務端一次抓多少數據回來scan);默認值是 1,一次只取一條。
  • Scan Attribute Selection:scan時建議指定需要的Column Family,減少通信量,否則scan操作默認會返回整個row的所有數據(所有Coulmn Family)。
  • Close ResultScanners:通過scan取完數據后,記得要關閉ResultScanner,否則RegionServer可能會出現問題(對應的Server資源無法釋放)。
  • Optimal Loading of Row Keys:當你scan一張表的時候,返回結果只需要row key(不需要CF, qualifier,values,timestaps)時,你可以在scan實例中添加一個filterList,并設置 MUST_PASS_ALL操作,filterList中add FirstKeyOnlyFilter或KeyOnlyFilter。這樣可以減少網絡通信量。
  • 啟用Bloom Filter:Bloom Filter通過空間換時間,提高讀操作性能。
  • 批量Get : HBase提供了get(List)批量獲取數據的接口,減少RPC交互,提高性能,但注意需要在客戶端關閉blockCache,否則容易OOM,也會沖掉熱點數據,這樣blockCache則失去了意義。

Hive On HBase

NoSQL種類繁多,各有優勢,HBase其中一個優勢就是和HDFS的集成,想象下如果實時事務數據需要結合歷史統計數據,則需要將Hive離線跑批的數據T+1日導入HBase,這個數據搬遷的過程,針對Redis\MongoDB\cassandra,則需要定制開發ETL工具。對于HBase,Hive提供了HBaseStorageHandle解決方案:

-- KV映射
CREATE TABLE helloWorld(key int, value string)
  STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
  WITH SERDEPROPERTIES ("hbase.columns.mapping" = ":key,cf1:val")
  TBLPROPERTIES ("hbase.table.name" = "helloWorld");
-- 列簇映射
CREATE TABLE helloWorld(key int, cf1 map<string,string>, cf2 map<string, string>)
  STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
  WITH SERDEPROPERTIES ("hbase.columns.mapping" = ":key,cf1:")
  TBLPROPERTIES ("hbase.table.name" = "helloWorld");

然后基于HQL插入數據即可。

Spark On HBase

某些業務場景下面,需要結合多個數據源的數據,比如redis和hbase的數據進行join。第一個念頭則是Spark DataFrame或DataSet,DF支持多種數據源,同時還提供了SparkSQL語法用于統計分析。

// 配置文件
val conf = new SparkConf()
      .setAppName(jobName)
      .setMaster("local[*]")
      .set("redis.host", redisHost)
      .set("redis.port", redisPort)
      .set("redis.db", redisDB)
      .set("redis.timeout", redisTimeOut)
    val sc = new SparkContext(conf)
    val sqlContext = new SQLContext(sc)

// RDD讀取   
var redisRDD = sc.fromRedisSet(groupID)
// 注冊DF
val redisSchemaString = "USER_ID"
val redisSchema = StructType(redisSchemaString.split(" ").map(fieldName => StructField(fieldName, StringType, true)))
val rowRDD = redisRDD.map(p => Row(p.toString.trim))
var redisDF = sqlContext.createDataFrame(rowRDD, redisSchema)
redisDF.registerTempTable(redisDFTableName)
  • 針對HBase,常見的方案有newAPIHadoopRDD、nerdammer、hortonworks-spark/shc、unicredit/hbase-rdd。
  1. newAPIHadoopRDD:Spark官方提供的HDFS文件讀寫接口;
// 配置文件
    val hconf = HBaseConfiguration.create()
    hconf.set("hbase.zookeeper.property.clientPort", zkPort)
    hconf.set("hbase.zookeeper.quorum", zkQuorum)
    
// RDD讀取
    hconf.set(TableInputFormat.INPUT_TABLE, tagTableName)
    hconf.set(TableInputFormat.SCAN_COLUMNS, hbaseColumns)
    val hbaseRDD = sc.newAPIHadoopRDD(hconf, classOf[TableInputFormat],
classOf[org.apache.hadoop.hbase.io.ImmutableBytesWritable],
classOf[org.apache.hadoop.hbase.client.Result])...

// 注冊DF:注冊之前需要將HBaseRDD轉換成RowRDD,此處省略
    val hbaseDF = sqlContext.createDataFrame(hbaseRDD, hbaseStruct)
    hbaseDF.registerTempTable(hbaseDFTableName)
  1. SHC :hortonworks提供的Spark讀寫HBase的方案,具體Demo見Git,SHC只支持spark2.0+版本;
// Schema定義
    def catalog =
      s"""{
         |"table":{"namespace":"default", "name":"tag"},
         |"rowkey":"key",
         |"columns":{
         |"col0":{"cf":"rowkey", "col":"key", "type":"string"},
         |"col1":{"cf":"behavior", "col":"activate", "type":"string"}}
        }""".stripMargin

// 讀取HBase數據
    def withCatalog(cat: String): DataFrame = {
          sqlContext
            .read
            .options(Map(HBaseTableCatalog.tableCatalog -> cat))
            .format("org.apache.spark.sql.execution.datasources.hbase")
            .load()
        }

// 注冊DF
    val df = withCatalog(catalog)
    df.registerTempTable("userBehaviorTime")
  1. nerdammer : 寫入操作必須是tunpleRDD,但Scala的tunple最大長度22,如果需要查詢的HBase列數超過22,則無法使用該方案;
// nerdammer方案讀取HBase數據:結果是tuple,長度有限制22.
val hbaseRDD = sc.hbaseTable[(Option[String], Option[String], Option[String], Option[String], Option[String],
Option[String], Option[String], Option[String], Option[String], Option[String], Option[String], Option[String],
Option[String])]("tag").select(columnStringArray: _*).inColumnFamily("behavior")

// DF只接收RowRDD
    val hbaseRowRDD = hbaseRDD.map(
      i => {
        var record = new ArrayBuffer[String]()
        i.productIterator.foreach { col => {
          record += col.asInstanceOf[Option[String]].get
        }
        }
        Row(record.toArray: _*)
      })

// RowRDD轉成DF
    val hbaseDF = sqlContext.createDataFrame(hbaseRowRDD, hbaseSchema.struct)
    hbaseDF.registerTempTable("userBehaviorTime")
  1. unicredit太小眾,坑太多,不建議使用;

  2. Phoenix : 兩大優勢Sql On HBase,HBase二級索引實現。HBase神器,但無法關聯Redis數據和HBase數據。

以上解決方案均是采用傳統的JOIN方式,這樣會帶來一個問題就是HBase全表掃描,性能低下。解決思路是先將RedisRDD進行repartition操作,針對每個partition形成HBase的List[Get]對象,進行批量讀取。但同樣批量讀取需要考慮內存激增的情況,所以需要結合上面的優化點使用。

Phoenix On HBase

Phoenix是構建在HBase上的一個SQL層,能讓我們用標準的JDBC API而不是HBase客戶端API來增刪查改HBase數據。相對原生HBase接口,Phoenix提供了以下特性:

  • JDBC API
  • 性能優化
  • 二級索引實現
  • SQL引擎
  • 事務
  • UDF
  • 統計信息收集
  • 分頁查詢
  • 散步表
  • 跳躍掃描
  • 視圖
  • 多租戶
  • 動態列
  • 大量CSV數據加載
  • 查詢服務器
  • 追蹤
  • 指標

有興趣者查閱Phoenix官網。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容