目錄
1.3 ?HBase數據模型
1.3.1 ?兩類數據模型
1.3.2 ?數據模型的重要概念
1.3.3 ?數據模型的操作
1.3.4 ?數據模型的特殊屬性
1.3.5 ?CAP原理與最終一致性
1.3.6 ?小結
本文章參考、總結于學校教材課本《HBase開發與應用》
1.3 ?HBase數據模型
????????在開始學習HBase之前非常有必要先學習HBase的特性,因此本節將介紹HBase的邏輯模型、物理模型和訪問HBase的方法等。和傳統的關系型數據庫類似,HBase以表(Table)的方式組織數據,應用程序將數據存入HBase的表中。HBase的表由行(row)和列(Column)共同構成,與關系型數據庫不同的是HBase有一個列族(Column Family)的概念,它將一列或者多列組織在一起,HBase的列必須屬于某一個列族。
????????行和列的交叉點稱為單元格(Cell),?單元格是版本化的。單元格的內容也就是列的值是不可分割的字節數組,以二進制形式存儲。HBase沒有數據類型,任何列值都被轉換成字節數組進行存儲。HBase表中的行是通過行鍵(Rowkey)進行區分的,行鍵也是用來唯一確定一行的標識,不同的行鍵代表不同的行,行鍵也是一段字節數組,不論是字符串還是數字,最終都會被轉換成字節數組進行存儲。HBase表中的行是按Rowkey排序的,排序方式采用字典順序,所有表中的行都必須要有Rowkey。同時HBase是一種面向列的分布式的數據庫,其物理模型和邏輯模型與傳統的關系型數據庫有很大的不同。下面我們將詳細講述HBase數據模型中的一些重要概念。
1.3.1 ?兩類數據模型
????????本節將從邏輯模型和物理模型兩方面來了解HBase的數據模型,表是HBase表達數據的邏輯組織方式,而基于列的存儲則是數據在底層的組織方式。本節將首先學習關于邏輯模型的一些重要概念及基本操作以及HBase實際存儲數據的一些特點,為后面的學習打好基礎。
1.3.1.1 ?邏輯模型
????????HBase是一個類似GoogleBigTable的開源分布式數據庫,大部分特性和BigTable相同,可以理解為是一個稀疏的、長期存儲的、多維度的和排序的映射表,表中的每一行可以有不同的列。與關系型數據庫不同,關系型數據庫要求表在被創建時明確定義列以及列的數據類型,而HBase的同一個表的記錄可以有不一樣的列。
????????HBase中最基本的一單位是列,一列或者多列構成了行,行有行鍵(Rowkey),每一行的行鍵都是唯一的,相同行鍵的插入操作被認為是對同一行的操作,也就是說如果做了兩次寫入操作,而行鍵是同一個,那么后面的操作可以認為是對該行的某些列的更新操作。
????????HBase中的一個表有若干行,每行有很多列,列中的值有多個版本,每個版本的值稱為一個單元格,每個單元存儲的是不同時刻該列的值。圖?1.3.1是Google的Big Table論文中的webtable表的邏輯模型,由于HBase被認為是BigTable的開源實現,所以該圖對HBase完全適用,表名為Webtable,包含兩個列族:contents和anchor。在該實例中,列族anchor:有兩個列(anchor:cssnsi.com和anchor:my.look.ca),列族contents僅有一個列contents:html。
????????其中,列名是由列族前綴和修飾符(Quallfier)連接而成,分隔符是英文冒號。例如,列anchor:my.look.ca是列族anchor前綴和修飾符my.look.ca組成。所以在提到HBase的列的時候應該用“列族前綴+修飾符”的方式才準確。
????????如圖?1.3.1所示,在表Webtable的邏輯模型中,所有的列族和列都緊湊在一起,其中并沒有附帶物理存儲方式的概念。該邏輯視圖是為了使讀者更好地、更直觀地理解HBase的數據模型,并不代表實際的數據存儲也是這種格式。
圖?1.3.1?Webtable的邏輯模型
如果熟悉java語言里的Map數據結構,可以把HBase理解為這種結構的無限嵌套版本。
1.3.1.2 ?物理模型
????????雖然在邏輯模型中,表可以被看成一個稀疏的行的集合。但在物理上,表是按列分開存儲的。HBase的列是按列族分組的,HFile是面向列的,存放行的不同列的物理文件,一個列族的數據存放在多個HFile中,最重要的是一個列族的數據會被同一個Region管理,物理上存放在一起。Region是管理HFile的一種機制,這個將會在后面討論。這種物理上存儲的不同可以從下面的物理視圖中直觀看出,如表?1.3.1和表?1.3.2所示。表?1.3.1中展示了列族anchor的集中存儲,表?1.3.2中展示了列族contents的集中存儲。
表?1.3.1?列族anchor存儲模型
行鍵 | 時間戳 | 列族和單元格值 |
com.cnn.www | t9 | anchor:cnnsi.com=”CNN” |
com.cnn.www | t8 | anchor:my.look.ca=”CNN.com” |
表?1.3.2?列族contents存儲模型
行鍵 | 時間戳 | 列族和單元格值 |
com.cnn.www | t6 | contents:html=”<html>…” |
com.cnn.www | t5 | contente:html=”<html>…” |
com.cnn.www | t3 | contente:html=”<html>…” |
????????HBase的表被設計成可以不禁用表而隨時加入新的列,因此可以將新列直接加入一個列族而無須聲明。
1.3.2 ?數據模型的重要概念
????????HBase是一種列式存儲的分布式數據庫,其核心概念是表(Table)。與傳統的關系型數據庫一樣,表由行和列組成,但HBase同一列可以存儲不同時刻的值,同時多個列可以組成一個列族(Column Family),這種組織形式是出于存取性能考慮的。理解HBase的這些概念是很重要的,因為數據模型設計的好壞將直接影響你的查詢性能。本節我們將討論最重要的概念。
1.3.2.1 ?表
????????在HBase中數據以表的形式存儲。使用表的主要原因是把某些列組織起來一起訪問,同一個表中的數據通常是相關的,通過列族進一步把一些列組織在一起進行訪問。用戶可以通過命令行或Java API來創建表。表名通常使用Java string類型或byte[](二進制數組)表示,表名作為HDFS存儲路徑的一部分來使用,因此必須要符合文件名規范,所以構成表名的字符是有限制的。可以直接查看底層存儲系統,在HDFS中可以看到每個表的表名都作為獨立的目錄結構,在某些情況下,用戶可能需要查看這部分信息。
????????HBase列式存儲格式允許用戶存儲大量的信息到相同的表中,而在RDBMS模型中,大量信息則需要切分成多個表存儲。通常的數據庫規范規則不適用于HBase,因此HBase中表的數量相對較少。
????????雖然理論上HBase的表是由行和列組成的,但是從物理結構上看,表存儲在不同的分區,即不同的Region。每個Region只在一個RegionServer中提供服務,而Region直接向客戶端提供存儲和讀取服務。
????????與傳統的關系型數據庫一樣,HBase提供了命令行創建表,創建表時只需要指定表名和至少一個列族。列族影響表的物理存儲結構,創建表后列族還可以更改,但是比較麻煩。這就是HBase表中的全部,和傳統關系型數據庫不同,HBase的表沒有列定義,沒有類型,這就是HBase被稱為無模式數據庫的原因。
????????如何與HBase建立連接呢?可以使用Shell,或者通過Java API,與使用JDBC或ODBC訪問關系型數據庫不同,訪問HBase不需要用戶名和密碼,沒有Schema;將HBase-site.xml配置文件復制一份到自己的工程中,HBase API會讀取配置文件完成對HBase的連接。創建連接是一項非常消耗資源的工作,HBase為我們提供了一個連接池,可以更好地管理資源重用。
1.3.2.2 ?行鍵
????????行鍵,即Rowkey,是HBase中最為重要的概念之一,在本書1.4.2節會詳細講解關于行鍵的設計原則。行鍵是不可分割的字節數組。行鍵是按字典排序由低到高存儲在表中的,以一個空的數組來標識表空間的起始或者結尾。圖?1.3.2展示了行鍵的排列規則。
圖?1.3.2?行鍵按照字典進行排序
????????這可能和你預期的順序不太一樣,在字典序中,數據按照二進制字節從左至右逐一對比形成最終的次序。例如row-1小于row-2,無論后面如何,row-1都排在row-2之前。
在HBase中行鍵是唯一的索引,不過在新版本的HBase中考慮了對輔助索引的支持。
????????為了高效檢索數據,應該仔細設計Rowkey以獲得最高的查詢性能:首先Rowkey被冗余存儲,所以長度不宜過長,過長的Rowkey將會占用大量的空間同時會降低檢索效率;其次Rowkey應該盡量分布均勻,這樣不會產生熱點現象;最后是Rowkey唯一原則,必須在設計上保證其唯一性。
在1.4.2節將學習Rowkey的設計,針對不同的場景Rowkey需要有不同的設計以提高檢索的效率。
1.3.2.3 ?列族
????????HBase中的列族是一些列的集合。一個列族中所有列成員有著相同的前綴。比如,列courses:history和courses:math都是列族courses的成員。冒號(:)是列族的分隔符,用來區分列族前綴和列名。Column前綴必須是可打印的字符,剩下的部分(稱為Column Qualifier),可以由任意字節數組組成。
??????????在物理上,一個列族的成員在文件系統上都是存儲在一起的。因為存儲優化都是針對列族級別的,這就意味著,一個列族的所有成員是通過相同的方式訪問的。
????????在創建表的時候至少要指定一個列族,新的列族可以隨后按需、動態地加入,但是修改列族要先停用表。應該將經常一起查詢的列放在一個列族中,合理劃分列族將減少查詢時加載到緩存的數據,提高查詢的效率,但也不要有太多的列族,因為跨列族訪問是非常低效的。
1.3.2.4 ?單元格
????????HBase中的一單元格由行鍵、列族、列、時間戳唯一確定。一單元格的內容是不可分割的字節數組。每個單元格都保存著同一份數據的多個版本,不同時間版本的數據按照時間順序倒序排序,最新時間的數據排在最前面,時間戳是64位的整數,可以由客戶端在寫入據時賦值,也可以由RegionServer自動賦值。
1.3.3 ?數據模型的操作
????????HBase對數據模型的4個主要操作包括Get、Put、Scan和Delete。通過HTable實例進行操作,用戶可以完成向HBase存儲和檢索數據,以及刪除無效數據之類的操作。所有修改數據的操作都保證行級別的原子性,多個客戶端或線程對同一行的讀寫操作都不會影響該行數據的原子性,要么讀到最新的數據,要么等待系統允許寫入行的修改。創建HTable實例是有代價的。每個實例都需要掃描HBase:meta表,以檢查該表是否存在,是否可用。此外還有一些其他操作,這些檢查和操作導致實例調用非常耗時。因此,推薦用戶只創建一次HTable實例,而且是每個線程創建一個,如果用戶需要使用多個HTable實例,應考慮使用HTablePool類,它可以復用多個HTable實例。
1.3.3.1 ?讀Get
????????Get操作是指從客戶端API中獲取已存儲數據的方法。HTable類中提供了get()方法,同時還有與之對應的Get類,Get操作返回一行或多行數據。Get()方法默認一次取回該行全部列的數據,我們可以限定只取回某個列族對應的列的數據,或者進一步限定只取回某些列的數據,之前也說過HBase的列的數據是多版本的,我們可以限定取回該列全部版本的數據或者限定只取回最新的一個或幾個版本的數據。
????????當用戶使用get()方法獲取數據時,HBase返回的結果包含所有匹配的一單元格數據,這些數據將被封裝在一個Result實例中返回給用戶。用Result類提供的方法,可以從服務器端獲取匹配指定行的特定返回值,這些值包括列族、列限定符和時間戳等。
1.3.3.2 ?寫Put
????????Put操作要么向表增加新行(如果Key是新的)或更新行(如果Key已經存在)。可以一次向表中插人一行數據,也可以一次操作一個集合,同時向表中寫入多行數據。如果要頻繁修改某些行的數據,用戶應該創建一個RowLock實例來防止其他用戶對該行數據進行修改。
????????Put操作每次都會發起一次到服務器的RPC操作,如果有大量的數據要寫入表中,就會有數千次RPC操作,這樣效率很低。HBase客戶端有一個緩沖區,負責將數據批量地僅通過一次RPC操作發送到服務端,這樣可大大提高寫入性能,默認客戶端寫緩沖區是關閉的,需要顯式打開該選項。
????????當將一個Put集合提交到服務端的時候,可能會出現部分成功部分失敗的情況,失敗的數據會被保存到緩存區中進行重試。
????????HBase還提供了一個compare-and-set操作,這個操作先進行檢查,條件滿足后再執行,這個操作對于行是原子性的。
????????HBase沒有Update操作,是通過Put操作完成對數據的修改的。
1.3.3.3 ?掃描Scan
????????Scan操作允許多行特定屬性迭代,其使用與get()方法非常類似。工作方式類似于迭代器,可以指定startRow參數來定義掃描讀取HBase表的起始行鍵,同時可選stopRow參數來限定讀取到何處停止。
????????在創建Scan實例之后,用戶可以增加更多的限定條件,沒有參數將掃描整個表,包括所有列族以及所有列,可以用多種方法限定讀取的數據。
????????掃描操作執行后將得到執行結果, 此結果被封裝在一個ResultScanner實例中。下面是一個基于HTable表實例的示例:假設表有幾個行鍵值為“row1”、“row2”、“row3",還有一些行鍵值為“abc1”、“abc2”和“abc3”。下面的代碼展示了startRow和stopRow可以應用到一個Scan實例,以返回“row”開頭的行。
Configution conf = HBaseConfiguration.create();
HTable htable =?new?HTable(conf, “table1”);??????????//instantiate HTable
scan scan =?new?Scan();
scan.addColumn(Bytes.toBytes(“cf”),Bytes.toBytes(“attr”));
scan.setStartRow(Bytes.toBytes(“row”));??????????????//start key is inclusive
scan.setStopRow(Bytes.toBytes(“row” +?(char)0));??//stop key is exclusive
ResultScanner rs = htable.getScanner(scan);
try?{
??for?(Result r = rs.next(); r !=?null; r = rs.next()) {
??// process result…
}?finally?{
?rs.close();???????????????????????????????????????????//always close the ResultScanner!
}
1.3.3.4 ?刪除Delete
????????Delete用于從表中刪除數據。HTable除了提供刪除方法delete()外,還有一個與之對應的類Delete,用戶可以通過多種方法限定要刪除的列。
????????與關系型數據庫的Delete操作不同,HBase的Delete操作可以指定刪除某個列族或者某個列,或者指定某個時間戳,刪除比這個時間早的數據。
????????HBase的Delete操作并不是真正地從磁盤刪除數據。而是通過創建墓碑(tombstones)標志進行處理。這些墓碑標記的值和小于該時間版本的單元格在大合并(major compact)時被清除。
1.3.4 ?數據模型的特殊屬性
????????討論完HBase的基本概念,我們來學習一下它特有的功能。這些常常使人感到困惑,對使用過關系型數據庫的讀者來說尤為如此。HBase的這些特性使得完成某些功能更加方便,同時它也存在一些有待完善的地方,這也正是HBase在大力改進的地方。
1.3.4.1 ?版本
????????Rowkey、column(列族和列)、version組合在一起稱為HBase中的一個單元格。有可能會有很多單元的行和列是相同的,要區分不同的單元可以使用版本。Rowkey和Column的值是用字節數組表示的,Version則是用一個長整型表示的。這個長整型值是使用java.util.Date.getTime()或者System.currentTimeMillis()產生的,這就意味著它的含義是“當前時間和1970-01-01UTC的時間差,單位毫秒”。
????????在HBase中,版本是按倒序排列的,因此當讀取這個文件的時候,最先找到的是最近的版本。關于版本的一些常見問題主要有如下兩個:
- ???如果有多個包含版本的寫操作同時發起,HBase會保存全部還是會保持最新的一個?
- ???可以發起包含版本的寫操作,但是它們的版本順序和操作順序相反嗎?
下面介紹一下在HBase中版本是如何工作的。
1.??含版本的操作
這里詳細講解如何在HBase的各個主要操作中使用版本屬性。
(1)??Get/Scan
????????Get是在Scan的基礎上實現的。Get同樣可以用Scan來描述。在默認情況下,如果沒有指定版本,一旦使用Get操作,會返回最近版本的Cell(該Cell可能是最新寫入,但不能保證一定是)。默認的操作可以這樣修改:
????????如果想要返回兩個以上的版本,可以使用Get類的setMax Versions(),如果想要返回的版本不只是最近的,可以使用Get類的setTimeRange()。
????????要想查詢的最新版本小于或等于給定的這個值,這就意味著給定的“最近”的值可以是某一個時間點。可以使用0到想要的時間來設置,還要把MaxVersionS設置為1。
默認Get例子如下,其中的Get操作會只獲得最新的一個版本。
Get get =?new?Get(Bytes.toBytes("row1));
Result r = htable.get(get);
byte[] b = r.getValue(Bytes.toBytes("cf"), Bytes.toBytes(“attr”));
????????//returns current version of value
含有版本的Get例子如下,其中的Get操作會獲得最近的3個版本。
Get get =?new?Get(Bytes.toBytes(“row1”));
get.setMaxVersions(3);?????????????????????
//will return last 3 versions of row
Result r = htable.get(get);
Byte[] b = r.getVable(Bytes.toBytes(“cf”), Bytes.toBytes(“attr”));
????//returns current version of value
List<KeyValue> kv = r.getColumn(Bytes.toBytes(”cf”), Bytes.toBytes(“attr”));
????//return all versions of this column
(2)??Put操作
????????一個Put操作會為一個Cell創建一個版本,默認使用當前時間戳,當然也可以自己設置時間戳,這就意味著可以把時間設置在過去或者未來,或者隨意使用一個長整型值。覆蓋一個現有的值,就意味著新寫入Cell的Rowkey、ColumnFamily、columnQulifier和version必須和原來的完全相同。
下面是不指明版本的例子,其中的Put操作不指明版本,所以HBase會將當前時間作為版本。
Put put = newPut(Bytes.toBytes(row));
put.add(Bytes.toBytes("cf"),Bytes.toBytes("attr1"),Bytes.toBytes(data));
htable.put(put);
下面是指明版本的例子,其中的Put操作指明了版本。
Put put = newPut(Bytes.toBytes(row));
long?explicitTimelnMs = 555;????//just an example
put.add(Bytes.toBytes("cf'"), Bytes.toBytes("attr1"), explicitTimeInMs,Bytes.toBytes(data));
htable.put(put);
(3)??Delete
內部刪除標記有三種不同類型:
- Delete:刪除列的指定版本。
- DeleteColumn:刪除列的所有版本。
- DeleteFamily:刪除特定列族所有列。
當刪除一行時,HBase將內部為每個列族創建墓碑(非每個單獨列)標記。
????????刪除操作的實現是創建一個墓碑標記。例如,要刪除一個版本,HBase不會去改那些數據,數據不會立即從文件中刪除。它使用刪除標記來屏蔽掉這些值。如果被標記的是最新一個版本的數據,就意味著這一行中的所有數據都會被刪除。
2.??現有的限制
關于版本還有一些未實現的功能,計劃在以后的版本中實現。
(1)??刪除標記后錯誤讀取新數據
????????刪除標記操作可能會標記其后Put的數據。注意,在寫下一個墓碑標記后,只有下一個主合并(major ompact)操作發起之后,墓碑標記才會清除。假設刪除所有小于等于時間T的數據,但之后又執行了一個Put操作,其時間戳小于等于T,那么就算這個Put發生在刪除操作之后,該數據也會被打上墓碑標記。這個Put并不會失敗,但你做Get操作時,則無法查詢剛剛Put進去的數據。只有一個主合并(major compact)執行后,一切才會恢復正常。所以使用一系列時間戳一直增長的Put操作就不會發生該問題。
(2)??主合并改變查詢的結果
????????一個Cell有三個版本數據t1、t2和t3,maxVersion設置為2,當請求獲取全部版本的時候,只會返回兩個:t2和t3。如果將t2和t3刪除,就會返回t1。但是如果在刪除之前,發生了major compaction操作,t1的值將會從磁盤上被徹底刪除,結果是什么值都不會返回了。
?
1.3.4.2 ?排序
????????Get和Scan操作返回的是經過排序的數據。列在服務器端也是字典排序的,所以按照名稱的字典序返回。總體來說,返回的數據首先按行字典序排序,其次是列族,然后是列修飾符(column qualifier),最后是時間戳反向排序,最新的在最前面。
1.3.4.3 ?列的元數據
????????對于HBase表中的列族,除了KeyValue實例以外,沒有關于元數據的描述,KeyValue對象表示HBase的最小單位是Cell。HBase的表不僅在一行中支持很多列,而且支持行之間有不同的列,所以需要單獨維護行和列之間的關系。獲取列族的完整列名的唯一方法是處理所有行。
1.3.4.4 ?連接查詢
????????HBase是否支持連接查詢,即Join查詢,是一個常見問題。簡單來說是不支持,至少不像傳統RDBMS那樣支持。正如前面描述的,讀數據模型只有Get和Scan兩種操作。但這并不表示Join查詢不能在應用程序中使用,只是必須用戶自己實現。一般來講,實現方法有兩種:要么寫入HBase的時候已經做好連接;要么查詢并在應用層實現連接。哪個更好?依賴于準備做什么,所以沒有一個準確的答案適合所有情況。
1.3.4.5 ?計數器
????????Increment Columnvalue(簡稱ICV)是HBase的級計數器,可以使用它完成一些諸如計算頁面瀏覽量(PV)的操作。如果沒有ICV,則需要首先讀出HBase單元格中的值,然后加1再存入。ICV操作發生在RegionServer上,而不是在客戶端,所以速度快,使用方式也沒有那么煩瑣。當其他客戶端也在訪問同一個單元格時,可以避免出現不一致的情況。可以把ICV等同于Java的AtomicLong.addAndGet()方法。
1.3.4.6 ?原子操作
????????類似Java的原子類,HTbleInterface接口也提供checkAndPut()和checkAndDelete()方法,它們可以在維持原子語義的同時提供更精細的控制。可以用checkAndPut()來實現上一節提到的incrementColumnValue()方法:
Configuration conf = HBaseConfiguration.create();
HTable htable =?new?HTable(conf,"table1");???//instantiate HTable
Get g =?new?Get(Bytes.toBytes(rowkey));
Resultr = htable.get(g);
long?curVal = Bytes.tolong(r?getColumnLatest(Bytes.toBytes(family),
Bytes.toBytes(qualifier)).getValue());
long?incVal = curVal+1;
Put P =?new?Put(Bytes.toBytes(rowkey));
P.add(Bytes.toBytes(family), Bytes.toBytes(qualifier), Bytes.toBytes(incVal));
htable.checkAndPut(Bytes.toBytes(rowkey),Bytes.toBytes(family),
Bytes.toBytes(qualifier), Bytes.toBytes(curVal),P);
上面的代碼雖然有點長,但可以試試。使用checkAndDelete()的方式與此類似。
1.3.4.7 ?事務特性ACID
????????傳統的SQL數據庫的事務通常都是支持ACID的強事務機制。而HBase這種NoSQL數據庫僅提供對行級別的原子性保證,也就是說同時對同一個Key下的數據進行的兩個操作,在實際執行的時候是會串行的執行,保證了每一個KeyValue對不會被破壞。
????????之前版本的HBase提供行級的事務,不過每次事務只能執行一個寫操作,假如連續地執行一系列Put、Delete操作,那么這些操作是單獨一個個的事務,其整體并不是原子性執行的。而在0.94.*版本中,可以實現Put、Delete在同一個事務中一起原子性執行。
????????同一Region有多行原子性,因此對一個多Region表來說,還是無法保證每次修改都能封裝為一個事務。HBase不是一個具備完整ACID特性的數據庫,它只實現了某些屬性。
????????HBase的ACID操作是復雜的,下面總結了ACID操作的一些主要原則,這些原則可以由點及面,逐步了解HBaseACID的特征。
HBase中考慮了事務(ACID)特性的數據操作包含以下這些:
- ???獲取數據的API: Get、Scan
- ???修改數據的API: Put、Batch put、Delete
- ???多項操作在一起的API: IncrementColumnValue、CheckAndPut
1)??關于HBaseACID設計原則如下:對于同一行所有列的修改是原子性的,對于該行的Put操作要么整體成功要么整體失敗。
2)??一個返回“成功”標志的操作肯定是完全成功的。
3)??一個返回“失敗”標志的操作肯定是完全失敗的。
4)??超時的操作可能成功也可能失敗。但也不會是部分成功或失敗。
5)??對于同一行跨多個列族的操作也遵循上面的原則。
6)??多行操作不能保證原子性,例如:對a、b、c三行進行操作,一些可能有返回,一些可能沒有,在這種情況下,API會返回一個對這三行操作的結果列表,包括成功、失敗或者超時。
7)??CheckAndPut()操作就像許多編程語言的CompareAndset()操作一樣是原子性的。
8)??所有修改操作是保證順序的,例如:如果一個寫操作將數據修改成”a=1,b=1,c=1",另一個修改成,“a=2,b=2,c=2",那么行的狀態肯定是”a=1,b=1,c=1”或者”a=2,b=2,c=2",不可能出現”a=1,b=2,c=1”這種狀態。
9)??請注意批量修改操作不能跨越多行。
10)?一致性和隔離性。通過API得到的行的數據是一個完整的行,數據由表中某個時刻的數據構成。
11)?持久性。所有可以讀取到的數據保證都是已經被持久化到磁盤上的。也就是說不會讀到沒有寫到磁盤上的數據。所有返回成功的操作的數據都是處于持久化到磁盤上的。返回失敗的都沒有持久化。
關于HBase的ACID語義詳情可以參見:http://HBase.apache.org/acid-semantics.html。
1.3.4.8 ?行鎖
????????HBase API中put()、delete()、check AndPut()等修改操作是獨立執行的,這意味著在一個串行方式的執行中,對于每一行必須保證行級別的操作是原子性的。RegionServer提供了一個行鎖特性,這個特性保證了只有一個客戶端能獲取一行數據相應的鎖,同時對該行進行修改。
1.3.4.9 ?自動分區
????????HBase中一個表的數據會被劃分成很多的Region,Region可以動態擴展并且HBase保證Region的負載均衡。Region實際上是行鍵排序后的按規則分割的連續的存儲空間。如果Region太大,會被動態拆分,相反,多個Region會合并成一個較大的Region,以減少HDFS上存儲文件的數量。這兩個過程就是HBase的split和compaction。
????????剛剛創建的表只有一個Region,隨著數據的寫入,達到Region上限配置值時,Region會按照中間鍵自動地拆分成兩個大致相等的Region,每個Region由一個RegionServer管理,一個RegionServer處理器管理著許多的Region。圖?1.3.3展示了多個Region是如何分布在不同RegionServer上的,注意每個Region包含起始Rowkey的記錄,不包含結束Rowkey的記錄。
圖?1.3.3?Region物理分布
????????每個RegionServer管理多少個Region合適?每個Region大小是多少合適?按照現在主流硬件的配置,每個RegionServer可以管理大約100~1000個Region,每個Region的大小可以是1~20GB。
????????Region的拆分和轉移是由HBase自動完成的,用戶感知不到,當一個RegionServer服務器發生故障時,Region可以快速地被轉移到其他服務器上,Region的拆分過程也是瞬間完成的。當Region進行拆分時,首先要將該Region下線(offline),拆分完成后新的Region再上線(online),下線的Region暫時不可用,不過由于速度極快,通常不會對數據的讀寫造成影響。
1.3.5 ?CAP原理與最終一致性
CAP原理是數據庫軟件的理論基礎,它指出對于一個數據庫系統來說,不可能同時滿足以下三點:
- ???一致性(Consistency):所有節點在同一時間具有相同的數據。
- ???可用性(Availability):保證每個請求不管成功或者失敗都有響應。
- ???分區容忍性(Partition tolerance):系統中任意信息的丟失或失敗不會影響系統的繼續運作。
????????分布式數據庫系統也只能滿足三項中的兩項。而由于當前的網絡硬件肯定會出現延遲丟包等問題,所以分區容忍性是我們必須需要實現的,因此只能在一致性和可用性之間進行權衡,大多數的分布式數據庫系統選擇了犧牲一致性提高可用性。圖?1.3.3所示是不同數據庫系統在這三方面的側重點。
圖?1.3.4?CAP模型及各種數據庫分類
????????HBase的設計基于這樣一些方面考慮,首先不要求嚴格的數據庫事務,保證數據最終一致即可;其次數據庫的寫入可能在幾秒之后讀取出來用戶也是能夠忍受的,也就是說不能實時地讀取剛剛寫入的數據,另外就是復雜SQL的查詢在產品設計階段就避免了,更多的查詢集中在針對主鍵的查詢。
1.3.6 ?小結
????????HBase作為NoSQL數據庫的一個代表和傳統關系型數據庫在概念上有相似之處,但也有非常大的差別,HBase更多的是為了擴展性和性能考慮,弱化了事務性,廣大讀者在學習時需要帶著一個全新的思維方式來認識HBase,本節對HBase最重要的概念全部進行了概述,在后面的章節將分別針對各個方面進行詳細描述。鼓勵大家將HBase應用于實際的項目中,經過實踐的積累會越發地感受到HBase在解決特定問題時所帶來的優勢。