Hbase 提供了種類豐富的過濾器(filter)來提升數據處理的效率,用戶能夠經過內置或自定義的過濾器來對數據進行過濾,全部的過濾器都在服務端生效,即謂詞下推(predicate push down)。這樣能夠保證過濾掉的數據不會被傳送到客戶端,從而減輕網絡傳輸和客戶端處理的壓力。html
Filter 接口中定義了過濾器的基本方法,FilterBase 抽象類實現了 Filter 接口。全部內置的過濾器則直接或者間接繼承自 FilterBase 抽象類。用戶只須要將定義好的過濾器經過 setFilter
方法傳遞給 Scan
或 put
的實例便可。java
setFilter(Filter filter)
// Scan 中定義的 setFilter @Override public Scan setFilter(Filter filter) { super.setFilter(filter); return this; }
// Get 中定義的 setFilter @Override public Get setFilter(Filter filter) { super.setFilter(filter); return this; }
FilterBase 的全部子類過濾器以下:git
說明:上圖基於當前時間點(2019.4)最新的 Hbase-2.1.4 ,下文全部說明均基於此版本。github
HBase 內置過濾器能夠分爲三類:分別是比較過濾器,專用過濾器和包裝過濾器。分別在下面的三個小節中作詳細的介紹。正則表達式
全部比較過濾器均繼承自 CompareFilter
。建立一個比較過濾器須要兩個參數,分別是比較運算符和比較器實例。shell
public CompareFilter(final CompareOp compareOp,final ByteArrayComparable comparator) { this.compareOp = compareOp; this.comparator = comparator; }
比較運算符均定義在枚舉類 CompareOperator
中數據庫
@InterfaceAudience.Public public enum CompareOperator { LESS, LESS_OR_EQUAL, EQUAL, NOT_EQUAL, GREATER_OR_EQUAL, GREATER, NO_OP, }
注意:在 1.x 版本的 HBase 中,比較運算符定義在
CompareFilter.CompareOp
枚舉類中,但在 2.0 以後這個類就被標識爲 @deprecated ,並會在 3.0 移除。因此 2.0 以後版本的 HBase 須要使用CompareOperator
這個枚舉類。數組
全部比較器均繼承自 ByteArrayComparable
抽象類,經常使用的有如下幾種:網絡
Bytes.compareTo(byte [],byte [])
按字典序比較指定的字節數組。EQUAL
和 NOT_EQUAL
操做。EQUAL
和 NOT_EQUAL
操做。BinaryPrefixComparator
和 BinaryComparator
的區別不是很好理解,這裏舉例說明一下:ide
在進行 EQUAL
的比較時,若是比較器傳入的是 abcd
的字節數組,可是待比較數據是 abcdefgh
:
BinaryPrefixComparator
比較器,則比較以 abcd
字節數組的長度爲準,即 efgh
不會參與比較,這時候認爲 abcd
與 abcdefgh
是知足 EQUAL
條件的;BinaryComparator
比較器,則認爲其是不相等的。比較過濾器共有五個(Hbase 1.x 版本和 2.x 版本相同),見下圖:
前四種過濾器的使用方法相同,均只要傳遞比較運算符和運算器實例便可構建,而後經過 setFilter
方法傳遞給 scan
:
Filter filter = new RowFilter(CompareOperator.LESS_OR_EQUAL, new BinaryComparator(Bytes.toBytes("xxx"))); scan.setFilter(filter);
DependentColumnFilter
的使用稍微複雜一點,這裏單獨作下說明。
能夠把 DependentColumnFilter
理解爲一個 valueFilter 和一個時間戳過濾器的組合。DependentColumnFilter
有三個帶參構造器,這裏選擇一個參數最全的進行說明:
DependentColumnFilter(final byte [] family, final byte[] qualifier, final boolean dropDependentColumn, final CompareOperator op, final ByteArrayComparable valueComparator)
這裏舉例進行說明:
DependentColumnFilter dependentColumnFilter = new DependentColumnFilter( Bytes.toBytes("student"), Bytes.toBytes("name"), false, CompareOperator.EQUAL, new BinaryPrefixComparator(Bytes.toBytes("xiaolan")));
首先會去查找 student:name
中值以 xiaolan
開頭的全部數據得到 參考數據集
,這一步等同於 valueFilter 過濾器;
其次再用參考數據集中全部數據的時間戳去檢索其餘列,得到時間戳相同的其餘列的數據做爲 結果數據集
,這一步等同於時間戳過濾器;
最後若是 dropDependentColumn
爲 true,則返回 參考數據集
+結果數據集
,若爲 false,則拋棄參考數據集,只返回 結果數據集
。
專用過濾器一般直接繼承自 FilterBase
,適用於範圍更小的篩選規則。
基於某列(參考列)的值決定某行數據是否被過濾。其實例有如下方法:
SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter( "student".getBytes(), "name".getBytes(), CompareOperator.EQUAL, new SubstringComparator("xiaolan")); singleColumnValueFilter.setFilterIfMissing(true); scan.setFilter(singleColumnValueFilter);
SingleColumnValueExcludeFilter
繼承自上面的 SingleColumnValueFilter
,過濾行爲與其相反。
基於 RowKey 值決定某行數據是否被過濾。
PrefixFilter prefixFilter = new PrefixFilter(Bytes.toBytes("xxx")); scan.setFilter(prefixFilter);
基於列限定符(列名)決定某行數據是否被過濾。
ColumnPrefixFilter columnPrefixFilter = new ColumnPrefixFilter(Bytes.toBytes("xxx")); scan.setFilter(columnPrefixFilter);
可使用這個過濾器實現對結果按行進行分頁,建立 PageFilter 實例的時候須要傳入每頁的行數。
public PageFilter(final long pageSize) { Preconditions.checkArgument(pageSize >= 0, "must be positive %s", pageSize); this.pageSize = pageSize; }
下面的代碼體現了客戶端實現分頁查詢的主要邏輯,這裏對其進行一下解釋說明:
客戶端進行分頁查詢,須要傳遞 startRow
(起始 RowKey),知道起始 startRow
後,就能夠返回對應的 pageSize 行數據。這裏惟一的問題就是,對於第一次查詢,顯然 startRow
就是表格的第一行數據,可是以後第二次、第三次查詢咱們並不知道 startRow
,只能知道上一次查詢的最後一條數據的 RowKey(簡單稱之爲 lastRow
)。
咱們不能將 lastRow
做爲新一次查詢的 startRow
傳入,由於 scan 的查詢區間是[startRow,endRow) ,即前開後閉區間,這樣 startRow
在新的查詢也會被返回,這條數據就重複了。
同時在不使用第三方數據庫存儲 RowKey 的狀況下,咱們是沒法經過知道 lastRow
的下一個 RowKey 的,由於 RowKey 的設計多是連續的也有多是不連續的。
因爲 Hbase 的 RowKey 是按照字典序進行排序的。這種狀況下,就能夠在 lastRow
後面加上 0
,做爲 startRow
傳入,由於按照字典序的規則,某個值加上 0
後的新值,在字典序上必定是這個值的下一個值,對於 HBase 來講下一個 RowKey 在字典序上必定也是等於或者大於這個新值的。
因此最後傳入 lastRow
+0
,若是等於這個值的 RowKey 存在就從這個值開始 scan,不然從字典序的下一個 RowKey 開始 scan。
25 個字母以及數字字符,字典排序以下:
'0' < '1' < '2' < ... < '9' < 'a' < 'b' < ... < 'z'
分頁查詢主要實現邏輯:
byte[] POSTFIX = new byte[] { 0x00 }; Filter filter = new PageFilter(15); int totalRows = 0; byte[] lastRow = null; while (true) { Scan scan = new Scan(); scan.setFilter(filter); if (lastRow != null) { // 若是不是首行 則 lastRow + 0 byte[] startRow = Bytes.add(lastRow, POSTFIX); System.out.println("start row: " + Bytes.toStringBinary(startRow)); scan.withStartRow(startRow); } ResultScanner scanner = table.getScanner(scan); int localRows = 0; Result result; while ((result = scanner.next()) != null) { System.out.println(localRows++ + ": " + result); totalRows++; lastRow = result.getRow(); } scanner.close(); //最後一頁,查詢結束 if (localRows == 0) break; } System.out.println("total rows: " + totalRows);
須要注意的是在多臺 Regin Services 上執行分頁過濾的時候,因爲並行執行的過濾器不能共享它們的狀態和邊界,因此有可能每一個過濾器都會在完成掃描前獲取了 PageCount 行的結果,這種狀況下會返回比分頁條數更多的數據,分頁過濾器就有失效的可能。
List<Long> list = new ArrayList<>(); list.add(1554975573000L); TimestampsFilter timestampsFilter = new TimestampsFilter(list); scan.setFilter(timestampsFilter);
FirstKeyOnlyFilter
只掃描每行的第一列,掃描完第一列後就結束對當前行的掃描,並跳轉到下一行。相比於全表掃描,其性能更好,一般用於行數統計的場景,由於若是某一行存在,則行中必然至少有一列。
FirstKeyOnlyFilter firstKeyOnlyFilter = new FirstKeyOnlyFilter(); scan.set(firstKeyOnlyFilter);
包裝過濾器就是經過包裝其餘過濾器以實現某些拓展的功能。
SkipFilter
包裝一個過濾器,當被包裝的過濾器遇到一個須要過濾的 KeyValue 實例時,則拓展過濾整行數據。下面是一個使用示例:
// 定義 ValueFilter 過濾器 Filter filter1 = new ValueFilter(CompareOperator.NOT_EQUAL, new BinaryComparator(Bytes.toBytes("xxx"))); // 使用 SkipFilter 進行包裝 Filter filter2 = new SkipFilter(filter1);
WhileMatchFilter
包裝一個過濾器,當被包裝的過濾器遇到一個須要過濾的 KeyValue 實例時,WhileMatchFilter
則結束本次掃描,返回已經掃描到的結果。下面是其使用示例:
Filter filter1 = new RowFilter(CompareOperator.NOT_EQUAL, new BinaryComparator(Bytes.toBytes("rowKey4"))); Scan scan = new Scan(); scan.setFilter(filter1); ResultScanner scanner1 = table.getScanner(scan); for (Result result : scanner1) { for (Cell cell : result.listCells()) { System.out.println(cell); } } scanner1.close(); System.out.println("--------------------"); // 使用 WhileMatchFilter 進行包裝 Filter filter2 = new WhileMatchFilter(filter1); scan.setFilter(filter2); ResultScanner scanner2 = table.getScanner(scan); for (Result result : scanner1) { for (Cell cell : result.listCells()) { System.out.println(cell); } } scanner2.close();
rowKey0/student:name/1555035006994/Put/vlen=8/seqid=0 rowKey1/student:name/1555035007019/Put/vlen=8/seqid=0 rowKey2/student:name/1555035007025/Put/vlen=8/seqid=0 rowKey3/student:name/1555035007037/Put/vlen=8/seqid=0 rowKey5/student:name/1555035007051/Put/vlen=8/seqid=0 rowKey6/student:name/1555035007057/Put/vlen=8/seqid=0 rowKey7/student:name/1555035007062/Put/vlen=8/seqid=0 rowKey8/student:name/1555035007068/Put/vlen=8/seqid=0 rowKey9/student:name/1555035007073/Put/vlen=8/seqid=0 -------------------- rowKey0/student:name/1555035006994/Put/vlen=8/seqid=0 rowKey1/student:name/1555035007019/Put/vlen=8/seqid=0 rowKey2/student:name/1555035007025/Put/vlen=8/seqid=0 rowKey3/student:name/1555035007037/Put/vlen=8/seqid=0
能夠看到被包裝後,只返回了 rowKey4
以前的數據。
以上都是講解單個過濾器的做用,當須要多個過濾器共同做用於一次查詢的時候,就須要使用 FilterList
。FilterList
支持經過構造器或者 addFilter
方法傳入多個過濾器。
// 構造器傳入 public FilterList(final Operator operator, final List<Filter> filters) public FilterList(final List<Filter> filters) public FilterList(final Filter... filters) // 方法傳入 public void addFilter(List<Filter> filters) public void addFilter(Filter filter)
多個過濾器組合的結果由 operator
參數定義 ,其可選參數定義在 Operator
枚舉類中。只有 MUST_PASS_ALL
和 MUST_PASS_ONE
兩個可選的值:
@InterfaceAudience.Public public enum Operator { /** !AND */ MUST_PASS_ALL, /** !OR */ MUST_PASS_ONE }
使用示例以下:
List<Filter> filters = new ArrayList<Filter>(); Filter filter1 = new RowFilter(CompareOperator.GREATER_OR_EQUAL, new BinaryComparator(Bytes.toBytes("XXX"))); filters.add(filter1); Filter filter2 = new RowFilter(CompareOperator.LESS_OR_EQUAL, new BinaryComparator(Bytes.toBytes("YYY"))); filters.add(filter2); Filter filter3 = new QualifierFilter(CompareOperator.EQUAL, new RegexStringComparator("ZZZ")); filters.add(filter3); FilterList filterList = new FilterList(filters); Scan scan = new Scan(); scan.setFilter(filterList);
HBase: The Definitive Guide _> Chapter 4. Client API: Advanced Features
更多大數據系列文章能夠參見 GitHub 開源項目: 大數據入門指南