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比較簡單,參照安裝步驟即可:
分布式環境
前提條件
- Hadoop集群:hadoop01,hadoop02, hadoop03
- 用戶互信
- HBase安裝包
- JDK
- Zookeeper
安裝部署
- 解壓安裝包:sudo tar -zxf hbase-1.2.4-bin.tar.gz -C /usr/local/hbase/
- 配置環境變量:vi /etc/profile ; export HBASE_HOME=/usr/local/hbase/
- 修改配置文件:$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>
- 修改啟動腳本:$HBASE_HOME/conf/hbase-env.sh; export JAVA_HOME=/usr/java/jdk/
- 修改HMaster配置:$HBASE_HOME/conf/regionservers
hadoop01
hadoop02
hadoop03
- 新增備用HMater: $HBASE_HOME/conf/backup-masters
hadoop02
- 啟動集群:$HBASE_HOME/bin/start-hbase.sh
- 驗證環境:
create 'hello', 'cf'
put 'hello', 'one', 'cf:a', 'b'
get 'hello', 'one'
HBase架構
看圖說話
HBase采用Master/Slave架構搭建集群,它隸屬于Hadoop生態系統,由一下類型節點組成:HMaster節點、HRegionServer節點、ZooKeeper集群,而在底層,它將數據存儲于HDFS中,因而涉及到HDFS的NameNode、DataNode等,總體結構如下:
- HMaster :
- 管理HRegionServer,實現其負載均衡;
- 管理和分配HRegion;實現DDL操作;
- 管理namespace和table的元數據;權限控制;
- HRegionServer :
- 存放和管理本地HRegion;
- 讀寫HDFS,管理Table中的數據;
- Client直接通過HRegionServer讀寫數據;
- ZooKeeper :
- 存放整個 HBase集群的元數據以及集群的狀態信息;
- 實現HMaster主從節點的failover。
- HRegion:
- HBase表數據按行切分成多個HRegion;
- HRegion按照列簇切分成多個Store;
- 每個Store由一個MemStore和多個StoreFile(HFile)組成;
- 每個HRegion還包含一個HLog文件,用于數據恢復;
- Hadoop:
- RegionServer通過DFS Client讀寫HDFS數據;
- RS和DN盡量保證在統一節點,確保數據本地化,提升讀寫性能;
- 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:
讀流程
HBase表數據所在Region,Region所在RegionServer,均存放在HBase表hbase:meta,由于元數據較小,一個2G的HRegion大概可以支持4PB的數據,所以meta表是不可Split的。Meta表本身是一個HBase表,該表所在RS的地址存放在ZK,于是讀數據的流程如下:
- 需求:從HellWorld表讀取002這條記錄;
- Client從ZK讀取meta表所在RegionServer地址信息;
- Client和Meta表所在RS建立連接,獲取HelloWorld表當中RowKey=002的記錄所在的RS地址;并將該地址緩存在客戶端;
- Client和002所在RS建立連接,申請查詢002記錄數據;
- 002所在RS根據RowKey獲取Region信息,并初始化Scaner,Scaner優先掃描BlockCache,然后掃描MemStore,然后掃描StoreFile(HFile),直到找到002記錄為止;
- RS返回結果數據給Client。
寫流程
- 需求:寫入005到HelloWorld表;
- Client從ZK去讀Meta表所在RS地址;
- Client讀取Meta表數據,確認005應該寫入RS地址;
- Client將005數據寫入RS02的HLog,用于災備恢復,然后寫入RegionB的memStore;此刻寫操作已完成,反饋給client;
- 當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預留空間。
- 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的高速緩存文件存儲數據塊。
- CombinedBlockCache :實際實現中,HBase將BucketCache和LRUBlockCache搭配使用,稱為CombinedBlockCache。和DoubleBlockCache不同,系統在LRUBlockCache中主要存儲Index Block和Bloom Block,而將Data Block存儲在BucketCache中。因此一次隨機讀需要首先在LRUBlockCache中查到對應的Index Block,然后再到BucketCache查找對應數據塊。BucketCache通過更加合理的設計極大降低了JVM GC對業務請求的實際影響。
MemStore
HBase寫入數據會先寫WAL,再寫緩存,WAL是用于RS故障后的數據恢復,而緩存MemStore則是為了提高寫入數據的性能。但MemStore是基于內存,一方面空間有限,一方面數據容易丟失,所以RegionServer會在滿足一定條件下講MemStore數據Flush到HDFS,條件如下:
Memstore級別限制:當Region中任意一個MemStore的大小達到了上限(hbase.hregion.memstore.flush.size,默認128MB),會觸發Memstore刷新。
Region級別限制:當Region中所有Memstore的大小總和達到了上限(hbase.hregion.memstore.block.multiplier * hbase.hregion.memstore.flush.size,默認 2* 128M = 256M),會觸發memstore刷新。
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。
當一個Region Server中HLog數量達到上限(可通過參數hbase.regionserver.maxlogs配置)時,系統會選取最早的一個 HLog對應的一個或多個Region進行flush
HBase定期刷新Memstore:默認周期為1小時,確保Memstore不會長時間沒有持久化。為避免所有的MemStore在同一時間都進行flush導致的問題,定期的flush操作有20000左右的隨機延時。
手動執行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個字節。
數據的持久化文件HFile中是按照KeyValue存儲的,如果Rowkey過長比如100個字節,1000萬列數據光Rowkey就要占用100*1000萬=10億個字節,將近1G數據,這會極大影響HFile的存儲效率;
MemStore將緩存部分數據到內存,如果Rowkey字段過長內存的有效利用率會降低,系統將無法緩存更多的數據,這會降低檢索效率。因此Rowkey的字節長度越短越好。
目前操作系統是都是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語法用于統計分析。
- 針對Redis,參考https://github.com/RedisLabs/spark-redis 。
// 配置文件
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。
- 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)
- 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")
- 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")
unicredit太小眾,坑太多,不建議使用;
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官網。