團隊內部要分享HBase的知識,之前研究了一段時間,知識比較零散,這一次就係統化的整理一番,之後在想到Hbase的時候,看著一篇就夠了。
概覽
特性
Hbase是一種NoSQL資料庫,這意味著它不像傳統的RDBMS資料庫那樣支援SQL作為查詢語言。Hbase是一種分散式儲存的資料庫,技術上來講,它更像是分散式儲存而不是分散式資料庫,它缺少很多RDBMS系統的特性,比如列型別,輔助索引,觸發器,和高階查詢語言等待。那Hbase有什麼特性呢?如下:
- 強讀寫一致,但是不是“最終一致性”的資料儲存,這使得它非常適合高速的計算聚合
- 自動分片,通過Region分散在叢集中,當行數增長的時候,Region也會自動的切分和再分配
- 自動的故障轉移
- Hadoop/HDFS整合,和HDFS開箱即用,不用太麻煩的銜接
- 豐富的“簡潔,高效”API,Thrift/REST API,Java API
- 塊快取,布隆過濾器,可以高效的列查詢優化
- 操作管理,Hbase提供了內建的web介面來操作,還可以監控JMX指標
什麼時候用Hbase?
Hbase不適合解決所有的問題:
- 首先資料庫量要足夠多,如果有十億及百億行資料,那麼Hbase是一個很好的選項,如果只有幾百萬行甚至不到的資料量,RDBMS是一個很好的選擇。因為資料量小的話,真正能工作的機器量少,剩餘的機器都處於空閒的狀態
- 其次,如果你不需要輔助索引,靜態型別的列,事務等特性,一個已經用RDBMS的系統想要切換到Hbase,則需要重新設計系統。
- 最後,保證硬體資源足夠,每個HDFS叢集在少於5個節點的時候,都不能表現的很好。因為HDFS預設的複製數量是3,再加上一個NameNode。
Hbase在單機環境也能執行,但是請在開發環境的時候使用。
內部應用
- 儲存業務資料:車輛GPS資訊,司機點位資訊,使用者操作資訊,裝置訪問資訊。。。
- 儲存日誌資料:架構監控資料(登入日誌,中介軟體訪問日誌,推送日誌,簡訊郵件傳送記錄。。。),業務操作日誌資訊
- 儲存業務附件:UDFS系統儲存影像,視訊,文件等附件資訊
不過在公司使用的時候,一般不使用原生的Hbase API,使用原生的API會導致訪問不可監控,影響系統穩定性,以致於版本升級的不可控。
Hbase架構
- Zookeeper,作為分散式的協調。RegionServer也會把自己的資訊寫到ZooKeeper中。
- HDFS是Hbase執行的底層檔案系統
- RegionServer,理解為資料節點,儲存資料的。
- Master RegionServer要實時的向Master報告資訊。Master知道全域性的RegionServer執行情況,可以控制RegionServer的故障轉移和Region的切分。
架構細化
-
HMaster是Master Server的實現,負責監控叢集中的RegionServer例項,同時是所有metadata改變的介面,在叢集中,通常執行在NameNode上面,這裡有一篇更細的HMaster介紹
- HMasterInterface暴露的介面,Table(createTable, modifyTable, removeTable, enable, disable),ColumnFamily (addColumn, modifyColumn, removeColumn),Region (move, assign, unassign)
- Master執行的後臺執行緒:LoadBalancer執行緒,控制region來平衡叢集的負載。CatalogJanitor執行緒,週期性的檢查hbase:meta表。
-
HRegionServer是RegionServer的實現,服務和管理Regions,叢集中RegionServer執行在DataNode
- HRegionRegionInterface暴露介面:Data (get, put, delete, next, etc.),Region (splitRegion, compactRegion, etc.)
- RegionServer後臺執行緒:CompactSplitThread,MajorCompactionChecker,MemStoreFlusher,LogRoller
-
Regions,代表table,Region有多個Store(列簇),Store有一個Memstore和多個StoreFiles(HFiles),StoreFiles的底層是Block。
儲存設計
在Hbase中,表被分割成多個更小的塊然後分散的儲存在不同的伺服器上,這些小塊叫做Regions,存放Regions的地方叫做RegionServer。Master程式負責處理不同的RegionServer之間的Region的分發。在Hbase實現中HRegionServer和HRegion類代表RegionServer和Region。HRegionServer除了包含一些HRegions之外,還處理兩種型別的檔案用於資料儲存
- HLog, 預寫日誌檔案,也叫做WAL(write-ahead log)
- HFile 真實的資料儲存檔案
HLog
-
MasterProcWAL:HMaster記錄管理操作,比如解決衝突的伺服器,表建立和其它DDLs等操作到它的WAL檔案中,這個WALs儲存在MasterProcWALs目錄下,它不像RegionServer的WALs,HMaster的WAL也支援彈性操作,就是如果Master伺服器掛了,其它的Master接管的時候繼續操作這個檔案。
-
WAL記錄所有的Hbase資料改變,如果一個RegionServer在MemStore進行FLush的時候掛掉了,WAL可以保證資料的改變被應用到。如果寫WAL失敗了,那麼修改資料的完整操作就是失敗的。
- 通常情況,每個RegionServer只有一個WAL例項。在2.0之前,WAL的實現叫做HLog
- WAL位於*/hbase/WALs/*目錄下
- MultiWAL: 如果每個RegionServer只有一個WAL,由於HDFS必須是連續的,導致必須寫WAL連續的,然後出現效能問題。MultiWAL可以讓RegionServer同時寫多個WAL並行的,通過HDFS底層的多管道,最終提升總的吞吐量,但是不會提升單個Region的吞吐量。
-
WAL的配置:
// 啟用multiwal <property> <name>hbase.wal.provider</name> <value>multiwal</value> </property> 複製程式碼
HFile
HFile是Hbase在HDFS中儲存資料的格式,它包含多層的索引,這樣在Hbase檢索資料的時候就不用完全的載入整個檔案。索引的大小(keys的大小,資料量的大小)影響block的大小,在大資料集的情況下,block的大小設定為每個RegionServer 1GB也是常見的。
探討資料庫的資料儲存方式,其實就是探討資料如何在磁碟上進行有效的組織。因為我們通常以如何高效讀取和消費資料為目的,而不是資料儲存本身。
Hfile生成方式
起初,HFile中並沒有任何Block,資料還存在於MemStore中。
Flush發生時,建立HFile Writer,第一個空的Data Block出現,初始化後的Data Block中為Header部分預留了空間,Header部分用來存放一個Data Block的後設資料資訊。
而後,位於MemStore中的KeyValues被一個個append到位於記憶體中的第一個Data Block中:
注:如果配置了Data Block Encoding,則會在Append KeyValue的時候進行同步編碼,編碼後的資料不再是單純的KeyValue模式。Data Block Encoding是HBase為了降低KeyValue結構性膨脹而提供的內部編碼機制。
讀寫簡流程
Hbase單機模式安裝
這一次來部署一個單機版的Hbase,單獨的Hbase daemon(Master,RegionServers和ZooKeeper)執行在同一個JVM程式中,然後持久化儲存到檔案系統中。這是最簡單的部署,但是卻能幫助我們更好的理解Hbase。安裝完成之後,我們在演示一下hbase命令列的用法。
環境
- CentOS 7
- Hbase 1.2.8
安裝單機
- 確保安裝了jdk,在Linux上使用自帶的包管理器直接安裝就好,使用二進位制也是一個不錯的選擇,我用的是CentOS
yum install java-1.8.0-openjdk* -y
複製程式碼
- 下載Hbase的二進位制包,下載地址位於http://mirror.bit.edu.cn/apache/hbase/hbase-1.2.8/,然後解壓到系統的目錄。
tar -xf hbase-1.2.8-bin.tar.gz
cd hbase-1.2.8
複製程式碼
- 配置hbase的環境變數,修改JAVA_HOME。注意看下自己的JAVA_HOME在什麼位置
vim conf/hbase-env.sh
// 注意這個是在CentOS上的java位置
export JAVA_HOME=/etc/alternatives/java_sdk_1.8.0/
複製程式碼
- 配置onf/hbase-site.xml,這個是Hbase的主配置檔案,你可以指定hbase和ZooKeeper資料寫入的目錄,當然也可以指定hbase的根目錄在哪個位置。
我將hbase的目錄放在hadoop使用者家目錄的hbase目錄下。我們不用事先建立好hbase的data目錄,hbase會自動幫我們建立好的,如果已經存在了data目錄,hbase會將存在的目錄進行遷移。
useradd -s /sbin/nologin -m hadoop
vim conf/hbase-site.xml
<configuration>
<property>
<name>hbase.rootdir</name>
<value>file:///home/hadoop/hbase</value>
</property>
<property>
<name>hbase.zookeeper.property.dataDir</name>
<value>/home/hadoop/zookeeper</value>
</property>
<property>
<name>hbase.unsafe.stream.capability.enforce</name>
<value>false</value>
<description>
Controls whether HBase will check for stream capabilities (hflush/hsync).
Disable this if you intend to run on LocalFileSystem, denoted by a rootdir
with the 'file://' scheme, but be mindful of the NOTE below.
WARNING: Setting this to false blinds you to potential data loss and
inconsistent system state in the event of process and/or node failures. If
HBase is complaining of an inability to use hsync or hflush it's most
likely not a false positive.
</description>
</property>
</configuration>
複製程式碼
- Hbase二進位制包下有start-hbase指令碼,可以方便的啟動hbase,如果我們的配置是正確的,那麼會正常啟動。
./bin/start-hbase.sh
複製程式碼
如果啟動之後,可以開啟http://localhost:16010檢視Hbase的Web UI
使用Hbase
我們可以先用Hbase提供的命令列工具,位於hbase的/bin/目錄下
- 連線Hbase
./hbase shell
複製程式碼
- 檢視幫助資訊, 敲
>help
複製程式碼
- 建立一個表,必須要指定表名稱和列簇名
hbase(main):003:0> create 'test', 'cf'
0 row(s) in 1.6320 seconds
=> Hbase::Table - test
複製程式碼
- 列出關於你的表的資訊,list 'sometable'
- 檢視錶更為詳細的資訊,使用describe命令
- 把資料放到表中
- 檢視錶中的所有資料
- 獲取單行的資料
-
其餘的命令可以自行嘗試
-
退出shell,使用
quit
這裡演示了下單機版的hbase如何安裝,瞭解hbase shell的基本用法,關於Hbase更深入的東西,可以瞭解下官方文件。
Hbase資料模型
在Hbase中,有一些術語需要提前瞭解。如下:
- Table:Hbase的table由多個行組成
- Row:一個行在Hbase中由一個或多個有值的列組成。Row按照字母進行排序,因此行健的設計非常重要。這種設計方式可以讓有關係的行非常的近,通常行健的設計是網站的域名反轉,比如(org.apache.www, org.apache.mail, org.apache.jira),這樣的話所有的Apache的域名就很接近。
- Column:列由列簇加上列的標識組成,一般是“列簇:列標識”,建立表的時候不用指定列標識
- Column Family:列簇在物理上包含了許多的列與列的值,每個列簇都有一些儲存的屬性可配置。例如是否使用快取,壓縮型別,儲存版本數等。在表中,每一行都有相同的列簇,儘管有些列簇什麼東西也沒有存。
- Column Qualifier:列簇的限定詞,理解為列的唯一標識。但是列標識是可以改變的,因此每一行可能有不同的列標識
- Cell:Cell是由row,column family,column qualifier包含時間戳與值組成的,一般表達某個值的版本
- Timestamp:時間戳一般寫在value的旁邊,代表某個值的版本號,預設的時間戳是你寫入資料的那一刻,但是你也可以在寫入資料的時候指定不同的時間戳
HBase 是一個稀疏的、分散式、持久、多維、排序的對映,它以行鍵(row key),列鍵(column key)和時間戳(timestamp)為索引。
Hbase在儲存資料的時候,有兩個SortedMap,首先按照rowkey進行字典排序,然後再對Column進行字典排序。
測試資料
create 'user','info','ship';
put 'user', '524382618264914241', 'info:name', 'zhangsan'
put 'user', '524382618264914241', 'info:age',30
put 'user', '524382618264914241', 'info:height',168
put 'user', '524382618264914241', 'info:weight',168
put 'user', '524382618264914241', 'info:phone','13212321424'
put 'user', '524382618264914241', 'ship:addr','beijing'
put 'user', '524382618264914241', 'ship:email','sina@sina.com'
put 'user', '524382618264914241', 'ship:salary',3000
put 'user', '224382618261914241', 'info:name', 'lisi'
put 'user', '224382618261914241', 'info:age',24
put 'user', '224382618261914241', 'info:height',158
put 'user', '224382618261914241', 'info:weight',128
put 'user', '224382618261914241', 'info:phone','13213921424'
put 'user', '224382618261914241', 'ship:addr','chengdu'
put 'user', '224382618261914241', 'ship:email','qq@sina.com'
put 'user', '224382618261914241', 'ship:salary',5000
put 'user', '673782618261019142', 'info:name', 'zhaoliu'
put 'user', '673782618261019142', 'info:age',19
put 'user', '673782618261019142', 'info:height',178
put 'user', '673782618261019142', 'info:weight',188
put 'user', '673782618261019142', 'info:phone','17713921424'
put 'user', '673782618261019142', 'ship:addr','shenzhen'
put 'user', '673782618261019142', 'ship:email','126@sina.com'
put 'user', '673782618261019142', 'ship:salary',8000
put 'user', '813782218261011172', 'info:name', 'wangmazi'
put 'user', '813782218261011172', 'info:age',19
put 'user', '813782218261011172', 'info:height',158
put 'user', '813782218261011172', 'info:weight',118
put 'user', '813782218261011172', 'info:phone','12713921424'
put 'user', '813782218261011172', 'ship:addr','xian'
put 'user', '813782218261011172', 'ship:email','139@sina.com'
put 'user', '813782218261011172', 'ship:salary',10000
put 'user', '510824118261011172', 'info:name', 'yangyang'
put 'user', '510824118261011172', 'info:age',18
put 'user', '510824118261011172', 'info:height',188
put 'user', '510824118261011172', 'info:weight',138
put 'user', '510824118261011172', 'info:phone','18013921626'
put 'user', '510824118261011172', 'ship:addr','shanghai'
put 'user', '510824118261011172', 'ship:email','199@sina.com'
put 'user', '510824118261011172', 'ship:salary',50000
複製程式碼
Hbase表(Schema)設計要點
只要是資料庫都存在,模式設計的問題,關係型中有模式設計的正規化,Hbase作為列式儲存資料庫,其模式設計也非常重要。
設計時需要關注的屬性,如何設計這些屬性等
Hbase與關係型資料庫對比
屬性 | Hbase | RDBMS |
---|---|---|
資料型別 | 只有字串 | 豐富的資料型別 |
資料操作 | 增刪改查,不支援join | 各種各樣的函式與表連線 |
儲存模式 | 基於列式儲存 | 基於表結構和行式儲存 |
資料保護 | 更新後仍然保留舊版本 | 替換 |
可伸縮性 | 輕易增加節點 | 需要中間層,犧牲效能 |
Hbase設計時要考慮的因素
Hbase關鍵概念:表,rowkey,列簇,時間戳
- 這個表應該有多少列簇
- 列簇使用什麼資料
- 每個列簇有有多少列
- 列名是什麼,儘管列名不必在建表時定義,但讀寫資料是要知道的
- 單元應該存放什麼資料
- 每個單元儲存多少時間版本
- 行健(rowKey)結構是什麼,應該包含什麼資訊
設計要點
行健設計
關鍵部分,直接關係到後續服務的訪問效能。如果行健設計不合理,後續查詢服務效率會成倍的遞減。
- 避免單調的遞增行健,因為Hbase的行健是有序排列的,這樣可能導致一段時間內大部分寫入集中在某一個Region上進行操作,負載都在一臺節點上。可以設計成: [metric_type][event_timestamp],不同的metric_type可以將壓力分散到不同的region上
- 行健短到可讀即可,因為查詢短鍵不必長鍵效能好多少,所以設計時要權衡長度。
- 行健不能改變,唯一可以改變的方式是先刪除後插入
列簇設計
列簇是一些列的集合,一個列簇的成員有相同的字首,以冒號(:)作為分隔符。
-
現在Hbase不能很好處理2~3個以上的列簇,所以儘可能讓列簇少一些,如果表有多個列簇,列簇A有100萬行資料,列簇B有10億行,那麼列簇A會分散到很多的Region導致掃描列簇A的時候效率底下。
-
列簇名的長度要儘量小,一個為了節省空間,另外加快效率,比如d表示data,v表示value
列簇屬性配置
- HFile資料塊,預設是64KB,資料庫的大小影響資料塊索引的大小。資料塊大的話一次載入進記憶體的資料越多,掃描查詢效果越好。但是資料塊小的話,隨機查詢效能更好
> create 'mytable',{NAME => 'cf1', BLOCKSIZE => '65536'}
複製程式碼
- 資料塊快取,資料塊快取預設是開啟的,如果一些比較少訪問的資料可以選擇關閉快取
> create 'mytable',{NAME => 'cf1', BLOCKCACHE => 'FALSE'}
複製程式碼
- 資料壓縮,壓縮會提高磁碟利用率,但是會增加CPU的負載,看情況進行控制
> create 'mytable',{NAME => 'cf1', COMPRESSION => 'SNAPPY'}
複製程式碼
Hbase表設計是和需求相關的,但是遵守表設計的一些硬性指標對效能的提升還是很有幫助的,這裡整理了一些設計時用到的要點。
Java API操作
Hbase有多種不同的客戶端,如REST客戶端,Thift客戶端,ORM框架Kundera等等。 Hbase也提供了Java的API來操作表與列簇等資訊,它的shell就是對Java的API做了一層封裝。
Hbase的Java API提供了很多高階的特性:
- 後設資料管理,列簇的資料壓縮,region分隔
- 建立,刪除,更新,讀取 rowkey
我們還是直接看程式碼這樣理解的更容易
環境
- Hbase 0.98
- Java 1.8
- Zookeeper 3.4.6
- Mac OS
案例
Hbase的客戶端版本不一致實驗結果很容易出現問題,儘量採用同樣的版本。因為服務端實驗的是Hbase0.98,客戶端也用0.98,另外由於Hadoop 2.x的版本現對於1.x做了很大的提升,建議採用Hbase-hadoop 2.x的客戶端。
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>0.98.24-hadoop2</version>
</dependency>
複製程式碼
建立連線
-
直接新建HTable("tableName"),但是這種每次建立表的時候因為都要查詢.meta表,來判斷表是不是存在,導致建立表的過程會有點慢,所以不建議每個請求都建立一個Htable
-
使用HTablePool,它和HTable的建立方式很像,但是如果採用連線池的話,它就不會給每個請求都單獨建立一個Htable了。
在建立Htable或者HtablePool的時候都可以指定更詳細的配置資訊。
HTablePool hTablePool = new HTablePool();
hTablePool.getTable("user");
複製程式碼
增刪改查
rowkey是代表Hbase中表的唯一一個行,同時像列簇 ,時間戳等用來定位表中的部分資料,Java的API對Hbas的CURD提供瞭如下的類:
- Put
- Get
- Delete
- Scan
- Increment
我們詳細的討論幾個類,剩餘的可以舉一反三。
寫資料
當寫請求收到的時候,預設資料同步的寫到Hlog中和MemStore,同時在兩個地方寫是為了保證資料的永續性,Memstore最終會持久化到磁碟中的Hfile中。每次MemStore進行Flush的時候,就會建立一個新的Hfile。
Put類用於向Hbase的表中儲存資料,儲存資料時,Put的例項必須要指定Rowkey
建立完Put例項後,再向其中新增資料
public void put() throws IOException {
// 獲取預設的配置
Configuration conf = HBaseConfiguration.create();
// 獲取Table例項
HTable table = new HTable(conf, "tab1");
// 建立Put例項,並且指定rowKey
Put put = new Put(Bytes.toBytes("row-1"));
// 新增一個 column,值為 "Hello",在 "cf1:greet" 列中
put.add(Bytes.toBytes("cf1"), Bytes.toBytes("greet"), Bytes.toBytes("Hello"));
// 新增一個 column,值為 "John",在 "cf1:person" 列中
put.add(Bytes.toBytes("cf1"), Bytes.toBytes("person"), Bytes.toBytes("John"));
table.put(put);
table.close();
}
複製程式碼
資料也可以批量的進行插入:
// table物件可以傳入List引數 table.put(final List puts)
執行結果:
讀資料
Hbase使用LRU快取讀取資料。Htable物件使用下面的方法讀取資料
而Get例項的構造方法和Put很像,構造方法要指定一個rowkey。
如果要查詢特定的cell,就是特定列的資料,可以採用額外的方法進行更加精細的調控。
看一下如下的案例程式碼:
public void get() throws IOException {
// 獲取預設的配置
Configuration conf = HBaseConfiguration.create();
// 獲取Table例項
HTable table = new HTable(conf, "tab1");
// 建立Put例項,並且指定rowKey
Get get = new Get(Bytes.toBytes("row-1"));
//
get.addColumn(Bytes.toBytes("cf1"), Bytes.toBytes("greet"));
// 新增一個 column,值為 "John",在 "cf1:person" 列中
Result result = table.get(get);
byte[] value = result.getValue(Bytes.toBytes("cf1"), Bytes.toBytes("greet"));
System.out.println("獲取到的值" + new String(value));
table.close();
}
複製程式碼
執行結果
更新資料
更新資料與寫資料基本一致,只是在Put例項賦值的時候,在相同的列上設定不同的值,操作的時候就會更新為新的值。
程式碼如下:
public void update() throws IOException {
Configuration conf = HBaseConfiguration.create();
// 獲取Table例項
HTable table = new HTable(conf, "tab1");
// 建立Put例項,並且指定rowKey
Put put = new Put(Bytes.toBytes("row-1"));
// 新增一個 column,值為 "Hello",在 "cf1:greet" 列中
put.add(Bytes.toBytes("cf1"), Bytes.toBytes("greet"), Bytes.toBytes("Good Morning"));
// 新增一個 column,值為 "John",在 "cf1:person" 列中
// put.add(Bytes.toBytes("cf1"), Bytes.toBytes("person"), Bytes.toBytes("John"));
table.put(put);
table.close();
}
複製程式碼
執行結果:
刪除資料
Delete命令只是標記當前的資料為刪除狀態,而不是立刻的刪除,也就是先進行邏輯刪除。實際上的刪除是在Hfile進行壓縮的時候,這些被標記的記錄就會被刪除掉。
Delete物件與Put和Get也很像
構造Delete例項
如果想要進行更加詳細的指定,可以再指定具體的列等資訊
看下面的案例程式碼:
public void delete() throws IOException {
Configuration conf = HBaseConfiguration.create();
// 獲取Table例項
HTable table = new HTable(conf, "tab1");
// 建立Delete例項,並且指定rowKey
Delete delete = new Delete(Bytes.toBytes("row-1"));
// 刪除 column "cf1:greet"
delete.deleteColumn(Bytes.toBytes("cf1"), Bytes.toBytes("greet"));
table.delete(delete);
table.close();
}
複製程式碼
執行結果:連續執行兩次刪除
操作優化
一個系統上線之後,開發和調優將一直貫穿系統的生命週期中,HBase也不列外。這裡主要說一些Hbase的調優
Hbase查詢優化
作為NoSQL資料庫,增刪改查是其最基本的功能,其中查詢是最常用的一項。
設定Scan快取
HBase中Scan查詢可以設定快取,方法是setCaching(),這樣可以有效的減少服務端與客戶端的互動,更有效的提升掃描查詢的效能。
/**
* Set the number of rows for caching that will be passed to scanners.
* If not set, the default setting from {@link HTable#getScannerCaching()} will apply.
* Higher caching values will enable faster scanners but will use more memory.
* @param caching the number of rows for caching
* 設定scanners快取的行數
*/
public void setCaching(int caching) {
this.caching = caching;
}
複製程式碼
顯示的指定列
當使用Scan或者GET獲取大量的行時,最好指定所需要的列,因為服務端通過網路傳輸到客戶端,資料量太大可能是瓶頸。如果能有效過濾部分資料,能很大程度的減少網路I/O的花費。
/**
* Get all columns from the specified family.
* <p>
* Overrides previous calls to addColumn for this family.
* @param family family name
* @return this
* 獲取指定列簇的所有列
*/
public Scan addFamily(byte [] family) {
familyMap.remove(family);
familyMap.put(family, null);
return this;
}
/**
* Get the column from the specified family with the specified qualifier.
* <p>
* Overrides previous calls to addFamily for this family.
* @param family family name
* @param qualifier column qualifier
* @return this
* 獲取指定列簇的特定列
*/
public Scan addColumn(byte [] family, byte [] qualifier) {
NavigableSet<byte []> set = familyMap.get(family);
if(set == null) {
set = new TreeSet<byte []>(Bytes.BYTES_COMPARATOR);
}
if (qualifier == null) {
qualifier = HConstants.EMPTY_BYTE_ARRAY;
}
set.add(qualifier);
familyMap.put(family, set);
return this;
}
複製程式碼
一般用: scan.addColumn(...)
關閉ResultScanner
如果在使用table.getScanner之後,忘記關閉該類,它會一直和服務端保持連線,資源無法釋放,從而導致服務端的某些資源不可用。
所以在用完之後,需要執行關閉操作,這點與JDBS操作MySQL類似
scanner.close()
禁用塊快取
如果批量進行全表掃描,預設是有快取的,如果此時有快取,會降低掃描的效率。
scan.setCacheBlocks(true|false);
對於經常讀到的資料,建議使用預設值,開啟塊快取
快取查詢結果
對於頻繁查詢HBase的應用場景,可以考慮在應用程式和Hbase之間做一層快取系統,新的查詢先去快取查,快取沒有再去查Hbase。
寫入優化
寫也是Hbase常有的操作之一,並且Hbase在寫入操作上有著其他NoSQL無法比擬的優勢,下面講如何優化寫入操作
關閉寫WAL日誌
一般為了保證系統的高可用性,WAL日誌預設是開啟狀態,WAL主要用於災難恢復的,如果應用可以容忍一定的資料丟失風險,可以在寫資料的時候,關閉寫WAL。
風險: 當RegionServer當機時,寫入的資料出現丟失,且無法恢復
設定AutoFlush
Htable有一個屬性是AutoFlush,該屬性用於支援客戶端的批量更新,預設是true,當客戶端每收到一條資料,立刻傳送到服務端,如果設定為false,當客戶端提交put請求時候,先將該請求在客戶端快取,到達閾值的時候或者執行hbase.flushcommits(),才向RegionServer提交請求。
風險 在請求未傳送到RegionServer之前客戶端崩潰,資料也會丟失
table.setAutoFlush(false);
table.setWriteBufferSize( 12 * 1024 * 1024 );
複製程式碼
預建立Region
一般表剛開始只有一個Region,插入該表的資料都會儲存在此Region中,插入該表的所有塑化劑都會儲存在該Region中,當到達一定的閾值時,才發生分裂。 這樣開始時刻針對該表的寫操作都集中在某臺伺服器上,造成這臺伺服器的壓力很緊張,同時對整個叢集資源的浪費
建議剛開始的時候預建立Region,可以使用Hbase自帶的RegionSplitter
延遲日誌flush
預設寫入操作,首先寫入WAL,並且在1S內寫入HDFS,這個時間預設是1S,可以通過引數配置
hbase.regionserver.optionallogflushinterval
可以配置大一點的值,比如5s,這段時間資料會保留在記憶體中,直到RegionServer週期性的執行flush操作。
Scan的重要引數
Scan是操作Hbase中非常常用的一個操作,雖然前面的Hbase API操作簡單的介紹了Scan的操作,但不夠詳細,由於Scan非常常用,關於其詳細的整理也是很有必要的。
Scan
HBase中的資料表通過劃分成一個個的Region來實現資料的分片,每一個Region關聯一個RowKey的範圍區間,而每一個Region中的資料,按RowKey的字典順序進行組織。
正是基於這種設計,使得HBase能夠輕鬆應對這類查詢:"指定一個RowKey的範圍區間,獲取該區間的所有記錄", 這類查詢在HBase被稱之為Scan。
1 . 構建Scan,指定startRow與stopRow,如果未指定的話會進行全表掃描 2 . 獲取ResultScanner 3 . 遍歷查詢結果 4 . 關閉ResultScanner
public void stringFilter() throws IOException {
Configuration conf = HBaseConfiguration.create();
// 獲取Table例項
HTable table = new HTable(conf, "user");
// 構建Scan
Scan scan = new Scan();
scan = scan.setStartRow(Bytes.toBytes("startRowxxx")).setStopRow(Bytes.toBytes("StopRowxxx"));
RowFilter filter = new RowFilter(
CompareFilter.CompareOp.EQUAL,
new BinaryComparator(Bytes.toBytes("224382618261914241"))
);
scan.setFilter(filter);
// 獲取resultScanner
ResultScanner scanner = table.getScanner(scan);
Result result = null;
// 處理結果
while ((result = scanner.next()) != null) {
byte[] value = result.getValue(Bytes.toBytes("ship"), Bytes.toBytes("addr"));
if (value == null || value.length == 0) {
continue;
}
System.out.println(
new String(value)
);
System.out.println("hello World");
}
// 關閉ResultScanner
scanner.close();
table.close();
}
複製程式碼
其它的設定引數
Caching: 設定一次RPC請求批量讀取的Results數量
下面的示例程式碼設定了一次讀取回來的Results數量為100:
scan.setCaching(100);
複製程式碼
Client每一次往RegionServer傳送scan請求,都會批量拿回一批資料(由Caching決定過了每一次拿回的Results數量),然後放到本次的Result Cache中:
應用每一次讀取資料時,都是從本地的Result Cache中獲取的。如果Result Cache中的資料讀完了,則Client會再次往RegionServer傳送scan請求獲取更多的資料。
Batch: 設定每一個Result中的列的數量
下面的示例程式碼設定了每一個Result中的列的數量的限制值為3:
scan.setBatch(3);
複製程式碼
該引數適用於一行資料過大的場景,這樣,一行資料被請求的列會被拆成多個Results返回給Client。
舉例說明如下:
假設一行資料中共有十個列: {Col01,Col02,Col03,Col04,Col05,Col06,Col07,Col08,Col09, Col10} 假設Scan中設定的Batch為3,那麼,這一行資料將會被拆成4個Results返回:
Result1 -> {Col01,Col02,Col03}
Result2 -> {Col04,Col05,Col06}
Result3 -> {Col07,Col08,Col09}
Result4 -> {Col10}
複製程式碼
關於Caching引數,我們說明了是Client每一次從RegionServer側獲取到的Results的數量,上例中,一行資料被拆成了4個Results,這將會導致Caching中的計數器被減了4次。結合Caching與Batch,我們再列舉一個稍複雜的例子:
假設,Scan的引數設定如下:
final byte[] start = Bytes.toBytes("Row1"); final byte[] stop = Bytes.toBytes("Row5"); Scan scan = new Scan(); scan.withStartRow(start).withStopRow(stop); scan.setCaching(10); scan.setBatch(3);
待讀取的資料RowKey與所關聯的列集如下所示:
Row1: {Col01,Col02,Col03,Col04,Col05,Col06,Col07,Col08,Col09,Col10}
Row2: {Col01,Col02,Col03,Col04,Col05,Col06,Col07,Col08,Col09,Col10,Col11}
Row3: {Col01,Col02,Col03,Col04,Col05,Col06,Col07,Col08,Col09,Col10}
再回顧一下Caching與Batch的定義:
Caching: 影響一次讀取返回的Results數量。
Batch: 限定了一個Result中所包含的列的數量,如果一行資料被請求的列的數量超出Batch限制,那麼這行資料會被拆成多個Results。
那麼, Client往RegionServer第一次請求所返回的結果集如下所示:
Result1 -> Row1: {Col01,Col02,Col03} Result2 -> Row1: {Col04,Col05,Col06} Result3 -> Row1: {Col07,Col08,Col09} Result4 -> Row1: {Col10} Result5 -> Row2: {Col01,Col02,Col03} Result6 -> Row2: {Col04,Col05,Col06} Result7 -> Row2: {Col07,Col08,Col09} Result8 -> Row2: {Col10,Col11} Result9 -> Row3: {Col01,Col02,Col03} Result10 -> Row3: {Col04,Col05,Col06}
Limit: 限制一次Scan操作所獲取的行的數量
同SQL語法中的limit子句,限制一次Scan操作所獲取的行的總量:
scan.setLimit(10000);
注意:Limit引數是在2.0版本中新引入的。但在2.0.0版本中,當Batch與Limit同時設定時,似乎還存在一個BUG,初步分析問題原因應該與BatchScanResultCache中的numberOfCompletedRows計數器邏輯處理有關。因此,暫時不建議同時設定這兩個引數。
CacheBlock: RegionServer側是否要快取本次Scan所涉及的HFileBlocks
scan.setCacheBlocks(true);
e) Raw Scan: 是否可以讀取到刪除標識以及被刪除但尚未被清理的資料
scan.setRaw(true);
MaxResultSize: 從記憶體佔用量的維度限制一次Scan的返回結果集
下面的示例程式碼將返回結果集的最大值設定為5MB:
scan.setMaxResultSize(5 * 1024 * 1024);
Reversed Scan: 反向掃描
普通的Scan操作是按照字典順序從小到大的順序讀取的,而Reversed Scan則恰好相反:
scan.setReversed(true);
帶Filter的Scan
Filter可以在Scan的結果集基礎之上,對返回的記錄設定更多條件值,這些條件可以與RowKey有關,可以與列名有關,也可以與列值有關,還可以將多個Filter條件組合在一起,等等。
最常用的Filter是SingleColumnValueFilter,基於它,可以實現如下類似的查詢:
"返回滿足條件{列I:D的值大於等於10}的所有行"
示例程式碼如下:
Filter豐富了HBase的查詢能力,但使用Filter之前,需要注意一點:Filter可能會導致查詢響應時延變的不可控制。因為我們無法預測,為了找到一條符合條件的記錄,背後需要掃描多少資料量,如果在有效限制了Scan範圍區間(通過設定StartRow與StopRow限制)的前提下,該問題能夠得到有效的控制。這些資訊都要求使用Filter之前應該詳細調研自己的業務資料模型。
最後
本文有點長,作為參考吧