1. hbase數據模型 1.1. HBase數據模型術語 Table HBase表由多行組成。 Row HBase中的一行由一個行鍵和一個或多個列組成,列的值與這些列相關聯。存儲行時,按行鍵按字母順序排列。因此,行鍵的設計非常重要。目標是以這樣一種方式存儲數據,即相關的行彼此接近。常見的行鍵模式是 ...
1. hbase數據模型
1.1. HBase數據模型術語
Table
HBase表由多行組成。
Row
HBase中的一行由一個行鍵和一個或多個列組成,列的值與這些列相關聯。存儲行時,按行鍵按字母順序排列。因此,行鍵的設計非常重要。目標是以這樣一種方式存儲數據,即相關的行彼此接近。常見的行鍵模式是網站域。如果您的行鍵是域,您可能應該反向存儲它們(org.apache.www, org.apache.mail, org.apache.jira)。這樣,所有Apache域都在表中彼此接近,而不是基於子域的第一個字母展開。
Column
HBase中的列由列族和列限定符組成,列限定符由:(冒號)字元分隔。
Column Family
列族物理地混合了一組列及其值,通常是出於性能原因。每個列族都有一組存儲屬性,比如它的值是否應該緩存在記憶體中,它的數據是如何壓縮的,或者它的行鍵是如何編碼的,等等。表中的每一行都有相同的列族,儘管給定的行可能不會在給定的列族中存儲任何內容。
Column Qualifier
列限定符被添加到列族中,以提供給定數據塊的索引。給定一個列族內容,一個列限定符可能是content:html,另一個可能是content:pdf。雖然列族在創建表時是固定的,但是列限定符是可變的,並且在行之間可能會有很大的不同。
Cell
單元格是行、列族和列限定符的組合,並且包含一個值和時間戳,其中時間戳表示值(value)的版本。
Timestamp
時間戳與每個值一起寫入,是值的給定版本標識符。預設情況下,時間戳表示寫入數據時RegionServer上的時間,但在將數據放入單元格時可以指定不同的時間戳值。
1.2. 概念視圖
下麵的例子是BigTable論文第2頁的一個稍微修改過的形式。有一個名為webtable的表,它包含兩行(com.cn .www和com.example.www)和三個名為contents、anchor和people的列族。在本例中,對於第一行(com.cn.www), anchor包含兩列(anchor:cssnsi.com, anchor:my.look.ca), contents包含一列(contents:html)。這個示例包含帶有row key com.cn.www的行的5個版本,以及帶有row key com.example.www的行的一個版本。contents:html列限定符包含給定網站的全部html。anchor列族的限定詞每個都包含外部站點,該站點鏈接到由行表示的站點,以及在其鏈接的錨中使用的文本。people列族代表與站點相關的人員。
1 Column Names: 2 按照慣例,列名由列族首碼和限定符組成。例如,contents:html由列族contents和html限定符組成。冒號(:)將列族從列族限定詞中分隔開。
Table webtable
Row Key |
Time Stamp |
ColumnFamily contents |
ColumnFamily anchor |
ColumnFamily people |
"com.cnn.www" |
t9 |
anchor:cnnsi.com = "CNN" |
||
"com.cnn.www" |
t8 |
anchor:my.look.ca = "CNN.com" |
||
"com.cnn.www" |
t6 |
contents:html = "<html>…" |
||
"com.cnn.www" |
t5 |
contents:html = "<html>…" |
||
"com.cnn.www" |
t3 |
contents:html = "<html>…" |
||
"com.example.www" |
t5 |
contents:html = "<html>…" |
people:author = "John Doe" |
表中看起來為空的單元格在HBase中不占用空間,實際上也不存在。這就是HBase“稀疏”的原因。表格視圖並不是查看HBase數據的唯一方法,甚至也不是最精確的方法。以下表示的信息與多維地圖相同。這隻是為了說明目的而做的一個模型,可能並不完全準確。
1 { 2 "com.cnn.www": { 3 contents: { 4 t6: contents:html: "<html>..." 5 t5: contents:html: "<html>..." 6 t3: contents:html: "<html>..." 7 } 8 anchor: { 9 t9: anchor:cnnsi.com = "CNN" 10 t8: anchor:my.look.ca = "CNN.com" 11 } 12 people: {} 13 } 14 "com.example.www": { 15 contents: { 16 t5: contents:html: "<html>..." 17 } 18 anchor: {} 19 people: { 20 t5: people:author: "John Doe" 21 } 22 } 23 }
1.3. 物理視圖
雖然在概念級別的表可以看作是稀疏的行集,但是它們是由列族物理存儲的。一個新的列限定符(column_family:column_qualifier)可以在任何時候添加到現有的列。
ColumnFamily anchor
Row Key |
Time Stamp |
Column Family anchor |
"com.cnn.www" |
t9 |
anchor:cnnsi.com = "CNN" |
"com.cnn.www" |
t8 |
anchor:my.look.ca = "CNN.com" |
ColumnFamily contents
Row Key |
Time Stamp |
ColumnFamily contents: |
"com.cnn.www" |
t6 |
contents:html = "<html>…" |
"com.cnn.www" |
t5 |
contents:html = "<html>…" |
"com.cnn.www" |
t3 |
contents:html = "<html>…" |
概念視圖中顯示的空單元格根本不存儲。因此,對content:html列的請求在戳記t8時不會返回任何值。類似地,請求一個anchor:my.look.ca在時間戳t9上的值不會返回任何值。但是,如果沒有提供時間戳,則返回特定列的最新值。由於時間戳是按降序存儲的,所以對於多個版本,最近的版本也是第一個找到的版本。因此,請求一個行為com.cnn.www的所有列的值,如果沒有指定時間戳那麼為:contents:html的值來自時間戳t6,anchor:cnnsi.com的值來自時間戳t9,anchor:my.look.ca的值來自時間戳t8。
1.4. Table
表在模式定義時預先聲明。
1.5. Row
行鍵是未解釋的位元組。行按字典順序排序,表中第一個出現的順序是最低的。空位元組數組用於表示表名稱空間的開始和結束。
1.6. Column Family
Apache HBase中的列被分組為列族。列族的所有列成員具有相同的首碼。例如,列courses:history和courses:math都是列族courses系列的成員。冒號(:)將列族從列族限定詞中分隔開。列族首碼必須由可列印字元組成。限定尾(列族限定符)可以由任意位元組組成。列族必須在模式定義時預先聲明,而列不需要在模式定義時定義,但可以在表啟動並運行時動態添加。
物理上,所有列族成員都存儲在文件系統中。由於調優和存儲規範是在列族級別執行的,因此建議所有列族成員具有相同的一般訪問模式和大小特征。
1.7. Cells
一個{row, column, version}元組在HBase中確切地指定一個單元格。單元格內容是未解釋的位元組
1.8. Time Stamp
HBASE 中通過rowkey和columns確定的為一個存貯單元稱為cell。每個 cell都保存 著同一份數據的多個版本。版本通過時間戳來索引。時間戳的類型是 64位整型。時間戳可以由HBASE(在數據寫入時自動 )賦值,此時時間戳是精確到毫秒的當前系統時間。時間戳也可以由客戶顯式賦值,如果應用程式要避免數據版本衝突,就必須自己生成具有唯一性的時間戳。每個cell中,不同版本的數據按照時間倒序排序,即最新的數據排在最前面。
為了避免數據存在過多版本造成的的管理 (包括存貯和索引)負擔,HBASE提供了兩種數據版本回收方式。一是保存數據的最後n個版本,二是保存最近一段時間內的版本(比如最近七天)。用戶可以針對每個列族進行設置。
1.9. Versions
一個{row, column, version}元組在HBase中確切地指定一個單元格。它可以有無限數量的單元格,其中行和列是相同的,但單元格地址僅在版本維度上不同。
雖然行和列鍵表示為位元組,但是使用長整數指定版本。通常如此長的時間包含時間實例,例如java.util.Date.getTime()或System.currentTimeMillis()返回的時間,即當前時間與1970年1月1日午夜之間的差值(以毫秒為單位)。
HBase版本維度以遞減順序存儲,因此在從存儲文件中讀取數據時,首先找到最近的值。
在HBase中,對於單元版本的語義有很多混淆。特別是:
- 如果對一個單元格的多次寫入具有相同的版本,那麼只有最後一次寫入是可讀取的。
- 以非遞增的版本順序編寫單元格是可以的。
下麵我們將描述當前HBase中的版本維度是如何工作的。有關HBase版本的討論,請參閱HBase -2406。在HBase中彎曲時間可以很好地讀取HBase的版本或時間維度。它比這裡提供的更詳細地介紹了版本控制。
1.9.1. Specifying the Number of Versions to Store
為給定列存儲的最大版本數是列模式的一部分,在創建表時指定,或者通過alter命令指定,通過HColumnDescriptor.DEFAULT_VERSIONS。在HBase 0.96之前,預設保留的版本數量為3個,但是在0.96和更新版本中更改為1個。
Modify the Maximum Number of Versions for a Column Family
1 這個例子使用HBase Shell來保持列族f1中所有列的最多5個版本。還可以使用HColumnDescriptor。 2 hbase> alter 't1', NAME => 'f1', VERSIONS => 5
示例:
修改表user的info1列族VERSIONS信息
hbase(main):010:0> describe 'user' Table user is ENABLED user COLUMN FAMILIES DESCRIPTION {NAME => 'info1', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => 'false', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536'} {NAME => 'info2', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => 'false', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536'} {NAME => 'info3', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => 'false', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536'} 3 row(s) Took 0.1262 seconds hbase(main):014:0* alter 'user' ,NAME => 'info1', VERSIONS => 5 # 註意:大小寫敏感 Updating all regions with the new schema... 1/1 regions updated. Done. Took 2.3592 seconds hbase(main):015:0> describe 'user' Table user is ENABLED user COLUMN FAMILIES DESCRIPTION {NAME => 'info1', VERSIONS => '5', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => 'false', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536'} {NAME => 'info2', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => 'false', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536'} {NAME => 'info3', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => 'false', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536'} 3 row(s) Took 0.0814 seconds
添加數據並查看結果
1 hbase(main):019:0> put 'user','1234','info1:name','zhang' 2 hbase(main):019:0> put 'user','1234','info1:name','zhang1' 3 hbase(main):019:0> put 'user','1234','info1:name','zhangsan' 4 hbase(main):019:0> put 'user','1234','info1:name','zhangsan2' 5 hbase(main):019:0> put 'user','1234','info1:name','lisi' 6 hbase(main):019:0> put 'user','1234','info1:name','zhaoliu' 7 ### 產看結果 8 hbase(main):042:0> get 'user','1234',{COLUMN=>'info1',VERSIONS=>5} # 註意:即使VERSIONS=>6,也只顯示5個,因為VERSIONS => '5' 9 ### 也可以指定列 get 'user','1234',{COLUMN=>'info1:name',VERSIONS=>5} 10 COLUMN CELL 11 info1:name timestamp=1534221416212, value=zhaoliu 12 info1:name timestamp=1534217862282, value=lisi 13 info1:name timestamp=1534217857274, value=zhangsan2 14 info1:name timestamp=1534217598847, value=zhangsan 15 info1:name timestamp=1534145642332, value=zhang1 16 1 row(s) 17 Took 0.0215 seconds
Modify the Minimum Number of Versions for a Column Family
1 你還可以指定每個列族存儲的版本的最小數量。預設情況下,該值被設置為0,這意味著該特性被禁用。下麵的示例通過HBase Shell將列族f1中所有列的最低版本數設置為2。你也可以使用HColumnDescriptor。 2 hbase> alter 't1', NAME => 'f1', MIN_VERSIONS => 2
從HBase 0.98.2開始,可以通過設置hbase.column.max.version為所有新創建的列保持最大版本數,指定全局預設值在hbase-site.xml中配置。參見hbase.column.max.version。
1.9.2. Delete
有三種不同類型的內部刪除標記。
- Delete:一個列的指定版本
- Delete column:一個列的所有版本
- Delete family: 用於特定ColumnFamily的所有列
當刪除整個行時,HBase將在內部為每個ColumnFamily創建一個墓碑(而不是每一列)。
通過創建墓碑標記刪除工作。例如,假設我們要刪除一行。為此,您可以指定一個版本,或者預設使用currentTimeMillis。這意味著刪除所有版本小於或等於這個版本的單元格。HBase從不在修改數據,因此例如delete不會立即刪除(或標記為已刪除)與delete條件對應的存儲文件中的條目。相反,會寫一個所謂的墓碑,將會掩蓋刪除的值。當HBase進行一次大的壓實時,將對墓碑進行處理,實際地除去那些死值,以及墓碑本身。如果刪除一行時指定的版本大於行中任何值的版本,則你可以認為刪除完整的行。
1.9.3. Major compactions change query results
在t1、t2和t3上創建三個單元格版本,最大版本設置為2。因此,在獲得所有版本時,只返回t2和t3處的值。但是如果刪除t2或t3的版本,t1的版本將再次出現。很明顯,一旦一個重要的壓縮運行,這樣的行為將不再是這樣了…(參見HBase中彎曲時間的垃圾收集)。
1.10. Sort Order
所有數據模型操作HBase都以排序的順序返回數據。首先是row,然後是ColumnFamily,然後是column qualifier,最後是timestamp(反向排序,所以首先返回最新的記錄)。
1.11. Column Metadata
ColumnFamily實例沒有存儲的列元數據之外的內部KeyValue信息。因此,雖然HBase可以支持每行有大數量的列,但是多行之間的列差異,是你的責任去保持跟蹤列名。
獲得一個ColumnFamily的完整列集的唯一方法是處理所有的行。有關HBase如何在內部存儲數據的更多信息,請參閱keyvalue。
1.12. Joins
HBase是否支持連接是列表中常見的問題,答案很簡單:它不支持連接,至少不支持RDBMS連接的方式(例如,SQL中的等連接或外連接)。如本章所示,HBase中的讀取數據模型操作是Get和Scan。
然而,這並不意味著應用程式中不支持等效連接功能,但是您必須自己完成。