HBase 提供了一個高級特性:計數器(counter)。很多收集統計信息的應用,例如在線廣告的單擊或查看統計,將這些數據收集到日誌文件中用於後期的分析。
利用計數器提供的實時統計,從而放棄延時較高的批處理操作。
2.1 計數器簡介 (Introduction to Counters)
-----------------------------------------------------------------------------------------------------------------------------------------
看看計數器在列級別的工作。下面是一個 shell 示例,創建表,增加計數器 2 次,然後查詢當前值。
hbase(main):001:0> create 'counters', 'daily', 'weekly', 'monthly'
0 row(s) in 1.1930 seconds
hbase(main):002:0> incr 'counters', '20150101', 'daily:hits', 1
COUNTER VALUE = 1
0 row(s) in 0.0490 seconds
hbase(main):003:0> incr 'counters', '20150101', 'daily:hits', 1
COUNTER VALUE = 2
0 row(s) in 0.0170 seconds
hbase(main):04:0> get_counter 'counters', '20150101', 'daily:hits'
COUNTER VALUE = 2
每次調用 incr 增加計時器給定的值,這裏爲 1。最後使用 get_counter 得到如期的當前值。
shell 的 incr 命令格式如下:
incr '<table>', '<row>', '<column>', [<increment-value>]
[<increment-value>] 爲可選項,如果忽略,增量值默認爲 1
● 初始化計數器 (Initializing Counters)
-------------------------------------------------------------------------------------------------------------------------------------
用戶不需要初始化計數器,第一次使用一個新的計時器時,計時器被自動設爲 0,也就是說,列限定符還不存在時,計數器的值爲 0。第一次增加操作
會將計數器值設爲 1 或者設定的值。也可以直接讀取或寫入一個計數器的值,但必須使用以下方法解碼爲值:
Bytes.toLong()
並且使用
Bytes.toBytes(long)
對存儲的值進行編碼。特別是後一種情況,在調用 toBytes() 方法時,必須保證使用長整型的(long)的數字。也可以考慮對變量或數字進行類型轉換來
顯式使用 long 類型,例如:
byte[] b1 = Bytes.toBytes(1L)
byte[] b2 = Bytes.toBytes((long) var)
可以使用 get 調用來訪問計時器:
hbase(main):005:0> get 'counters', '20150101'
COLUMN CELL
daily:hits timestamp=1427485256567, value=\x00\x00\x00\x00\x00\x00\x00\x02
1 row(s) in 0.0280 seconds
這樣的結果可讀性很差,但這表明了一個計數器就是一個與其它列類似的簡單列。
也可以指定一個更大的低增值:
hbase(main):006:0> incr 'counters', '20150101', 'daily:hits', 20
COUNTER VALUE = 22
0 row(s) in 0.0180 seconds
hbase(main):007:0> get_counter 'counters', '20150101', 'daily:hits'
COUNTER VALUE = 22
hbase(main):008:0> get 'counters', '20150101'
COLUMN CELL
daily:hits timestamp=1427489182419, value=\x00\x00\x00\x00\x00\x00\x00\x16
1 row(s) in 0.0200 seconds
通過 get 直接訪問計時器值得到的是字節數組,shell 把每個字節按十六進制數打印。使用 get_counter 可以獲得可讀格式的返回數據。
可以使用 incr 命令來對計時器增加值,也可取回計時器當前值或者減少當前值。
hbase(main):009:0> incr 'counters', '20150101', 'daily:hits'
COUNTER VALUE = 23
0 row(s) in 0.1700 seconds
hbase(main):010:0> incr 'counters', '20150101', 'daily:hits'
COUNTER VALUE = 24
0 row(s) in 0.0230 seconds
hbase(main):011:0> incr 'counters', '20150101', 'daily:hits', 0
COUNTER VALUE = 24
0 row(s) in 0.0170 seconds
hbase(main):012:0> incr 'counters', '20150101', 'daily:hits', -1
COUNTER VALUE = 23
0 row(s) in 0.0210 seconds
hbase(main):013:0> incr 'counters', '20150101', 'daily:hits', -1
COUNTER VALUE = 22
0 row(s) in 0.0200 seconds
利用 incr 命令的最後一個參數,增量值,可以實現下表中所示的行爲:
The increment value and its effect on counter increments
+-------------------+-----------------------------------------------------------------------------------------
| Value | Effect
+-------------------+-----------------------------------------------------------------------------------------
| greater than zero | Increase the counter by the given value.
+-------------------+-----------------------------------------------------------------------------------------
| zero | Retrieve the current value of the counter. Same as using the get_counter shell command.
+-------------------+-----------------------------------------------------------------------------------------
| less than zero | Decrease the counter by the given value
+-------------------+-----------------------------------------------------------------------------------------
顯然,使用 incr 命令只能一次操作一個計時器。也可以使用後面介紹的客戶端 API 來操作計數器。
2.2 單一計數器 (Single Counters)
-----------------------------------------------------------------------------------------------------------------------------------------
第一種增加調用是隻對單一計數器的:需要精確指定要使用的列(column)。方法由 Table 提供:
long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier, long amount) throws IOException;
long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier, long amount, Durability durability) throws IOException;
給出列的座標,以及增加值,這兩個方法區別就在於可選的 durability 參數,與 Put.setDurability() 同樣的方式工作。忽略 durability 參數使用默認
的 Durability.SYNC_WAL 方式工作,意思是使用 write-ahead log.
示例: Example using the single counter increment methods
long cnt1 = table.incrementColumnValue(Bytes.toBytes("20110101"), Bytes.toBytes("daily"), Bytes.toBytes("hits"), 1);
long cnt2 = table.incrementColumnValue(Bytes.toBytes("20110101"), Bytes.toBytes("daily"), Bytes.toBytes("hits"), 1);
long current = table.incrementColumnValue(Bytes.toBytes("20110101"), Bytes.toBytes("daily"), Bytes.toBytes("hits"), 0);
long cnt3 = table.incrementColumnValue(Bytes.toBytes("20110101"), Bytes.toBytes("daily"), Bytes.toBytes("hits"), -1);
輸出:
cnt1: 1, cnt2: 2, current: 2, cnt3: 1
2.3 多計數器 (Multiple Counters)
-----------------------------------------------------------------------------------------------------------------------------------------
另一個增加計數器值的方法是通過 Table 的 increment() 調用。它的工作方式類似於 CRUD 操作,使用如下方法:
Result increment(final Increment increment) throws IOException
必須創建 Increment 實例,並向其填充適合的細節信息,如計數器的座標。Increment 構造器如下:
Increment(byte[] row)
Increment(final byte[] row, final int offset, final int length)
Increment(Increment i)
創建 Increment 時必須提供一個行鍵(row key), 此行鍵設置了包含後續操作中所有的計數器所在的行。下一個變體構造器接受一個更大的數組,以及偏移量
和長度參數來確定其中的行鍵。最後一個是拷貝構造器,利用已有的實例從中複製所有的狀態信息。
一旦確定了更新計數器所在的行,並創建了 Increment 實例,就需要向其添加實際的計數器,也就是要增加計數器的列(column)。
Increment addColumn(byte[] family, byte[] qualifier, long amount)
Increment add(Cell cell) throws IOException
第一個方法接受列座標參數,第二個方法利用已有的 cell. 這是很有用的,如果剛剛已獲取了一個計數器,現在要增加它的值,add() 調用檢查當前給定的
cell 匹配 Increment 實例的行鍵。
這裏不同之處,是與 Put 方法的對比,這裏沒有選項指定一個版本,或者時間戳,在處理增量值的時候,版本是隱式處理的。更有甚者,沒有 addFamily()
方法的等價物,因爲計數器是特定於列的,並且需要明確指定。因此不需要單獨添加列族的方法。
Increment 類的一個特性是具有設置時間範圍的能力:
Increment setTimeRange(long minStamp, long maxStamp) throws IOException
TimeRange getTimeRange()
爲一組計數器增量設置時間範圍實際上是隱式處理版本,時間範圍實際上傳遞給服務器以限制其內部的 get 操作,以獲取當前的計數器值。
示例: Example incrementing multiple counters in one row
Increment increment1 = new Increment(Bytes.toBytes("20150101"));
//Increment the counters with various values
increment1.addColumn(Bytes.toBytes("daily"), Bytes.toBytes("clicks"), 1);
increment1.addColumn(Bytes.toBytes("daily"), Bytes.toBytes("hits"), 1);
increment1.addColumn(Bytes.toBytes("weekly"), Bytes.toBytes("clicks"), 10);
increment1.addColumn(Bytes.toBytes("weekly"), Bytes.toBytes("hits"), 10);
//Call the actual increment method with the above counter updates and receive the results
Result result1 = table.increment(increment1);
for (Cell cell : result1.rawCells()) {
//Print the cell and returned counter value.
System.out.println("Cell: " + cell +
" Value: " + Bytes.toLong(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength()));
}
Increment increment2 = new Increment(Bytes.toBytes("20150101"));
//Use positive, negative, and zero increment values to achieve the wanted counter changes.
increment2.addColumn(Bytes.toBytes("daily"), Bytes.toBytes("clicks"), 5);
increment2.addColumn(Bytes.toBytes("daily"), Bytes.toBytes("hits"), 1);
increment2.addColumn(Bytes.toBytes("weekly"), Bytes.toBytes("clicks"), 0);
increment2.addColumn(Bytes.toBytes("weekly"), Bytes.toBytes("hits"), -5);
Result result2 = table.increment(increment2);
for (Cell cell : result2.rawCells()) {
System.out.println("Cell: " + cell +
" Value: " + Bytes.toLong(cell.getValueArray(),
cell.getValueOffset(), cell.getValueLength()));
}
輸出:
Cell: 20150101/daily:clicks/1427651982538/Put/vlen=8/seqid=0 Value: 1
Cell: 20150101/daily:hits/1427651982538/Put/vlen=8/seqid=0 Value: 1
Cell: 20150101/weekly:clicks/1427651982538/Put/vlen=8/seqid=0 Value: 10
Cell: 20150101/weekly:hits/1427651982538/Put/vlen=8/seqid=0 Value: 10
Cell: 20150101/daily:clicks/1427651982543/Put/vlen=8/seqid=0 Value: 6
Cell: 20150101/daily:hits/1427651982543/Put/vlen=8/seqid=0 Value: 2
Cell: 20150101/weekly:clicks/1427651982543/Put/vlen=8/seqid=0 Value: 10
Cell: 20150101/weekly:hits/1427651982543/Put/vlen=8/seqid=0 Value: 5
Increment 類提供了許多其他方法,有些方法繼承自其父類,例如 Mutation, 查看 Increment API javadoc 獲取更多信息。
參考:
《HBase - The Definitive Guide - 2nd Edition》Early release —— 2015.7 Lars George