HBase權(quán)威指南(中文版)——第三章(第一部分)
六15
by
admin on 2013 年 6 月 15 日at 下午 5:35 Posted In:
HBase,
文檔翻譯第三章 客戶端API: 基礎(chǔ)篇本章將介紹HBase客戶端API,首先要說明的是,HBase雖然是用Java開發(fā)的,其原生的API采用了Java,但它也支持其它的編程語言,在第6章會(huì)詳細(xì)的介紹如何使用其它的編程語言。
概述操作HBase最主要的API放在源碼的org.apache.hadoop.hbase.client包下,它提供給用戶訪問(讀、寫、刪除等)HBase數(shù)據(jù)庫(kù)的功能性接口。在詳細(xì)列舉Hbase令人眼花繚亂的接口之前,我們不妨先整體上看看它的使用情況。
HBase以每個(gè)row為單元保證數(shù)據(jù)操作的原子性,這一點(diǎn)保證了所有讀寫線程對(duì)同一個(gè)row的數(shù)據(jù)訪問的一致性。換句話說,對(duì)于某個(gè)線程來說,它并不在意別的線程是否在讀、寫這一條記錄:它或者取到一個(gè)最后修改的一致性版本,或者在接受最終被修改的數(shù)據(jù)前一直被掛起,在第8章將詳細(xì)介紹這一點(diǎn)。
在正常的操作和負(fù)載下,一個(gè)讀取客戶端不會(huì)受到另一個(gè)更新者對(duì)該行更新操作的影響,因?yàn)?,它們之間的競(jìng)爭(zhēng)關(guān)系是可以忽略不計(jì)的。因此,對(duì)于大量客戶端在同一時(shí)間更新同一行的操作,可以嘗試將更新操作合并,批量執(zhí)行。
一次修改一行中的多個(gè)列同樣受到上述原子性的保障。另外,創(chuàng)建HTable的實(shí)例并不是沒有開銷的,任何一次實(shí)例化的操作都意味著對(duì).META表的一次掃描,以確認(rèn)這個(gè)表是否存在,是否可操作(enable)。因此,我們建議每個(gè)線程僅創(chuàng)建一個(gè)HTable實(shí)例,在整個(gè)Application的生命周期內(nèi)進(jìn)行復(fù)用。
當(dāng)你考慮擁有多個(gè)HTable實(shí)例時(shí),不妨使用類HTablePool(可查看HTablePool一節(jié))靈活便捷的復(fù)用HTable實(shí)例。
小結(jié)
1. 通常在程序啟動(dòng)時(shí)創(chuàng)建HTable對(duì)象,且僅創(chuàng)建一次。
2. 每個(gè)線程持有自己的HTable實(shí)例,或者使用HTablePool。
3. 對(duì)于行的更新屬于原子操作。
CRUD操作CRUD是一組數(shù)據(jù)庫(kù)操作的集合,即“Create,Read,Update,Delete”。HBase在HTable類中同樣提供了這些操作,后文將介紹到它們。
以下的大部分函數(shù)看起來都是通俗易懂的,但是詳細(xì)的說明看起來很相近。這意味著在對(duì)它們介紹的模式將相同,這一點(diǎn),后文將不再說明。
示例中將列出部分源代碼,完整的源碼可以在GitHub上下載,地址為
https://github.com/larsgeorge/hbase-book,在“build the examples”一節(jié),將介紹如何編譯這些代碼。如果對(duì)列出的部分代碼有疑問,可以下載查閱完整的代碼。
Put操作這組方法可以分為兩類:插入一行和插入多行,后者略復(fù)雜一些。我們將分別介紹這兩組接口。
單行插入毫無疑問,您最想了解的接口肯定是如何將數(shù)據(jù)存放到HBase數(shù)據(jù)中,下面的接口將讓你實(shí)現(xiàn)它:
void put(Put put)
throws IOExeption
Put對(duì)象通過以下創(chuàng)造函數(shù)進(jìn)行構(gòu)造:
Put(byte[] row)
Put(byte[] row,
RowLock rowLock)
Put(byte[] row,
long ts)
Put(byte[] row,
long ts, RowLock rowLock)
您需要提供row來構(gòu)造一個(gè)Put對(duì)象,HBase中的一個(gè)row通過唯一的key和它的多個(gè)值組成。它們都以字節(jié)數(shù)組(byte[])的方式存儲(chǔ),因此,您可以將任意類型的對(duì)象當(dāng)作key。第9章介紹了如何設(shè)計(jì)rowkey,現(xiàn)在,我們假定key可以為任意的對(duì)象,它們代表了真實(shí)世界中的某個(gè)個(gè)體,比如username和order ID,它們可以是簡(jiǎn)單的數(shù)字,也可以是UUID字符串等等。
HBase非常友好地提供了工具類,用來將Java的各種類型轉(zhuǎn)化為byte[],示例3-1給出Bytes類的一轉(zhuǎn)函數(shù),這些函數(shù)能夠方便的將Java類型轉(zhuǎn)化為字節(jié)數(shù)組。
示例3-1 Bytes類的轉(zhuǎn)換函數(shù)列舉
當(dāng)你創(chuàng)建了Put對(duì)象,便可以向其中插入數(shù)據(jù)??梢酝ㄟ^使用如下的方法添加數(shù)據(jù):
通過調(diào)用一次add方法,可以加入一列數(shù)據(jù),也可以加入一個(gè)指定的時(shí)間戳。如果沒有顯示地指定時(shí)間戳,add方法會(huì)使用ts默認(rèn)的構(gòu)造函數(shù),直到Region Server將時(shí)間戳的值填入。
高級(jí)使用者在學(xué)會(huì)獲取、創(chuàng)建內(nèi)部的Cell對(duì)象后,可以調(diào)用接收KeyValue參數(shù)的add函數(shù)。一個(gè)Cell對(duì)象,代表了一個(gè)單獨(dú)、唯一的單元,類似于帶有坐標(biāo)軸的系統(tǒng),您可以使用rowkey,column family,
column qualifier和timestamp唯一的確定一個(gè)三維坐標(biāo)軸系統(tǒng)中的點(diǎn)。
使用與add方法對(duì)應(yīng)的方法get,可以從put對(duì)象中獲取到KeyValue結(jié)構(gòu)。
上述的兩個(gè)方法,第一個(gè)可以根據(jù)family和qualifier取出對(duì)應(yīng)的KeyValue值,getFamilyMap方法可以取出Put對(duì)象中的所有的column family信息。
每個(gè)KeyValue結(jié)構(gòu)都擁有一份包整的信息,包括:rowkey,column
family,qualifier,timestamp和具體的數(shù)據(jù)。它是HBase中最小的存儲(chǔ)結(jié)構(gòu)。在后文文介紹存儲(chǔ)的一節(jié)中將具體介紹。如果要詳細(xì)了解KeyValue類,請(qǐng)查看后面關(guān)于KeyValue類的具體介紹。
除了遍歷List<KeyValue>結(jié)構(gòu)查詢具體的cell,您還可以調(diào)用如下的方法:
通過限定條件,查詢是否存在相應(yīng)的Cell對(duì)象。
表3-1列出了Put類中提供的方法,要注意的是,所有的get方法只能取出Put實(shí)例中設(shè)置的Cell,因此,他們很少被使用。比如,您在一個(gè)私有方法中為Put對(duì)象設(shè)置了一些值,而在另外的地方想取出這些值。
表3-1 Put類方法一覽
Method
Description
getRow()
Returns the row key as specified when creating the Put instance.
getLockId()
Returns the optional lock ID handed into the constructor using
the rowLock parameter. Will be -1L if not set.
setWriteToWAL()
Allows you to disable the default functionality of writing the
data to the server-side write-ahead log.
getWriteToWAL()
Indicates if the data will be written to the write-ahead log.
getTimeStamp()
Retrieves the associated timestamp of the Put instance. Can be
optionally set using the constructor’s ts parameter. If not set, may return
Long.MAX_VALUE.
heapSize()
Computes the heap space required for the current Put instance.
This includes all contained data and space needed for internal structures.
isEmpty()
Checks if the family map contains any KeyValue instances.
numFamilies()
Convenience method to retrieve the size of the family map,
containing all KeyValue instances.
size()
Returns the number of KeyValue instances that will be added with
this Put.
示例3-2給出了如何使用put和get方法。
示例 3-2 向HBase數(shù)據(jù)庫(kù)中插入數(shù)據(jù)
上述代碼首先創(chuàng)建一個(gè)配置對(duì)象,然后初始化一個(gè)新的HTable對(duì)象,創(chuàng)建Put實(shí)例,插入一個(gè)名為colfam1:qual1的列,其值為val1,再插入一個(gè)colfam2:qual2的例,其值為val2,最后將這個(gè)值插入到HBase的表中。
對(duì)上述代碼示例,我們進(jìn)行了逐行詳細(xì)的介紹,后面的示例將更多地關(guān)注重點(diǎn)行進(jìn)行解釋。
您也可以使用命令行的方式來確定插入操作是否生效:
hbase(main):001:0> list
TABLE
testtable
1 row(s) in 0.0400 seconds
hbase(main):002:0> scan
‘testtable’
ROW COLUMN+CELL
row1 column=colfam1:qual1,
timestamp=1294065304642, value=val1
1
row(s) in 0.2050 seconds
timestamp(時(shí)間戳)的存在保證了HBase中的值可以有不同的版本。
當(dāng)您不指定timestamp,HBase將會(huì)把Region Server上的當(dāng)前時(shí)間插入到對(duì)應(yīng)的行上。
Put類除了默認(rèn)的構(gòu)造函數(shù),還可以帶有一個(gè)可選的參數(shù)rowLock。在”Row Lock”一節(jié)將會(huì)講到它,它使你可以在外部對(duì)一個(gè)行加鎖(譯者注:在最新的HBase版本中,該類已經(jīng)過期!)。通過使用該對(duì)象,您可以對(duì)行加鎖,訪止其它客戶端對(duì)象的訪問,從而達(dá)到對(duì)行獨(dú)占的效果。
KeyValue類您的代碼可能會(huì)直接訪問到KeyValue對(duì)象,正如前文中提到的,KeyValue定義了坐標(biāo)系(由rowkey,column
family,column qualifier和timestamp組成)中的一個(gè)確定的點(diǎn),這個(gè)類提供了很多的創(chuàng)造函數(shù)形式,下面給出一種參數(shù)比較全面的構(gòu)造函數(shù)形式。
值得注意的是,KeyValue類以及和它相關(guān)的比較方法更多的用在HBase內(nèi)部。很少有客戶端API直接訪問原生的字節(jié)數(shù)據(jù),它們常被用來做字節(jié)層面的比較等操作。
所有的數(shù)據(jù)以及坐標(biāo)軸信息都采用了byte數(shù)據(jù)的形式存儲(chǔ),這種低層面的存儲(chǔ)方式也保證了最高的存儲(chǔ)效率,將內(nèi)部的數(shù)據(jù)結(jié)構(gòu)壓縮到最小。由于是字節(jié)數(shù)組,KeyValue構(gòu)造函數(shù)中的參數(shù)很多都由byte[]和偏移量offset組成。這種形式允許你直接以字節(jié)層面?zhèn)鬟f已經(jīng)存在的字節(jié)數(shù)組。
KeyValue中所有成員對(duì)象,都擁有g(shù)et方法獲取對(duì)應(yīng)的字節(jié)數(shù)組,包括偏移量offset和長(zhǎng)度length。當(dāng)然也可以直接返回對(duì)應(yīng)的字節(jié)數(shù)據(jù):
通過KeyValue對(duì)象的接口,可以取出內(nèi)部存儲(chǔ)的全部字節(jié)數(shù)據(jù),但很少有應(yīng)用需要這樣做,當(dāng)然也不能排除您有這樣的需求。
有兩個(gè)非常有趣的函數(shù):
byte []
getRow()
byte []
getKey()
常常有人會(huì)感到疑問,row和key有什么區(qū)別呢?在后文的“存儲(chǔ)”一節(jié),您將找到更詳細(xì)的答案。在這里,您只需要記住,row是row key的另一種叫法,也就是Put構(gòu)造函數(shù)中接受的參數(shù)row; 而key是前面介紹的可以唯一確定一個(gè)Cell的坐標(biāo)軸位置,將它用原生的byte字節(jié)表示,便是key。因此,您會(huì)很少調(diào)用到getKey()函數(shù)。更多的時(shí)候,您更習(xí)慣于使用getRow()來取得row的值。
KeyValue類也提供一組用來做比較操作的內(nèi)部類。您也可以在自己代碼中使用這些類,比如您使用客戶端API從HBase中取出了一組KeyValue對(duì)象,要對(duì)它們進(jìn)行比較、排序等處理時(shí),您便可以使用它們。
表3-2 KeyValue類中提供的比較器
Method
Description
KeyComparator
Compares two KeyValue keys,
i.e., what is returned by the getKey() method, in their raw, byte array format.
KVComparator
Wraps the raw KeyComparator,
providing the same functionality based on two given Key Value instances.
RowComparator
Compares the row key (returned
by getRow()) of two KeyValue instances.
MetaKeyComparator
Compares two keys of .META.
entries in their raw, byte array format.
MetaComparator
Special version of the
KVComparator class for the entries in the .META. catalog table. Wrapsthe
MetaKeyComparator.
RootKeyComparator
Compares two keys of -ROOT-
entries in their raw, byte array format.
RootComparator
Special version of the
KVComparator class for the entries in the -ROOT- catalog table. Wraps the RootKeyComparator.
KeyValue類以靜態(tài)對(duì)象的方法向外提供這些比較器。例如,一個(gè)public成員變量KEY_COMPARATOR,提供了KeyComparator比較器的實(shí)例。COMPARATOR成員變量實(shí)例化了應(yīng)用很廣的KVComparator類。您不必自己再為這些比較器單獨(dú)創(chuàng)建實(shí)例,可以直接使用KeyValue對(duì)象提供的這些靜態(tài)實(shí)例。比較,您要?jiǎng)?chuàng)建一個(gè)保存KeyValue對(duì)象的集合,同時(shí)保證集合中KeyValue的排列順序與HBase內(nèi)部一致,您可以使用如下的代碼:
TreeSet<KeyValue> set = new TreeSet<KeyValue>(KeyValue.COMPARATOR)
KeyValue實(shí)例中存儲(chǔ)了一個(gè)附加屬性type,來標(biāo)志一個(gè)Cell的狀態(tài)。表3-3列舉出了不同的type值。
表3-3 KeyValue實(shí)例中type屬性的取值
Type
Description
Put
The KeyValue instance
represents a normal Put operation.
Delete
This instance of KeyValue
represents a Delete operation, also known as a tombstone marker.
DeleteColumn
This is the same as Delete,
but more broadly deletes an entire column.
DeleteFamily
This is the same as Delete,
but more broadly deletes an entire column family, including all contained
columns.
您可以使用KeyValue實(shí)例的toString()方法打印出它的值,來查看它的type取值。打印結(jié)果以如下的格式輸出:
<row-key>/<family>:<qualifier>/<version>/<type>/<value-length>
本書中的一些示例代碼會(huì)使用這個(gè)方法來查看取出的數(shù)據(jù)的元數(shù)據(jù)信息。
KeyValue類中還提供了其它一些非常便捷的方法,比如:用來比較數(shù)據(jù)的部分字段,判斷它的type取值,取出它的heap大小, clone和copy信息等。KeyValue類也有一些可以創(chuàng)建比較器實(shí)例的靜態(tài)方法。您可以查詢HBase的Java Doc獲得更詳細(xì)的介紹,也可以閱讀本書的“存儲(chǔ)”一節(jié)了解更詳細(xì)的原生二進(jìn)制存儲(chǔ)格式。
客戶端寫緩存每個(gè)Put操作都從客戶端到服務(wù)器端傳遞數(shù)據(jù)的一個(gè)RPC操作。這對(duì)于少量的操作是沒有問題的,如果一個(gè)應(yīng)用程序每秒要向HBase數(shù)據(jù)庫(kù)中插入成千上萬條記錄,這樣調(diào)用顯然是不行的。
RPC的往返調(diào)用時(shí)間是指客戶端發(fā)送請(qǐng)求給服務(wù)器,再收到服務(wù)器應(yīng)答的過程中,在網(wǎng)絡(luò)上消耗的時(shí)間,并不包括真正的數(shù)據(jù)在網(wǎng)絡(luò)上傳輸?shù)臅r(shí)間。簡(jiǎn)單地講,就是傳輸數(shù)據(jù)的額外開銷。一般情況下,在一個(gè)局域網(wǎng)中,RPC調(diào)用一次需要1ms,這意味著在一秒內(nèi),即使什么操作也不做,RPC也只能進(jìn)行1000次。
另一個(gè)重要的因素就是消息的大小:如果傳輸?shù)臄?shù)據(jù)很大時(shí),您花在RPC調(diào)用過程中的時(shí)間比例就比較低,因?yàn)榇蟛糠謺r(shí)間都在傳輸數(shù)據(jù)。以計(jì)數(shù)器為例,消息的長(zhǎng)度都很小,若一次把若干個(gè)消息合并起來更新,您會(huì)發(fā)現(xiàn)性能可以提高不少。
HBase API在客戶端內(nèi)建了一個(gè)緩存區(qū),它將若干個(gè)Put操作緩存在一起,通過一次RPC調(diào)用發(fā)給服務(wù)器端??梢酝ㄟ^如下代碼打開個(gè)屬性:
void
setAutoFlush(boolean autoFlush)
boolean
isAutoFlush()
在默認(rèn)設(shè)置下,客戶端緩存是不啟用的,您可以通過將autoFlush設(shè)置為false,從而啟用緩存策略。
table.setAutoFlush(false)
可以通過isAutuFlush判斷該策略是否生效。一旦客戶端緩存生效,你在單條Put時(shí),就不用擔(dān)心每次都會(huì)產(chǎn)生RPC操作,因?yàn)镻ut操作被緩存在了客戶端一側(cè)的緩存中。當(dāng)您想到數(shù)據(jù)寫入到HBase數(shù)據(jù)庫(kù)中時(shí),您可以調(diào)用另一個(gè)API:
void
flushCommits() throws IOException
這個(gè)方法將所有的修改操作發(fā)送給服務(wù)器,緩存區(qū)的Put操作可以覆蓋到很多的戶??蛻舳藭?huì)非常智能的將他們進(jìn)行分類并發(fā)送到多臺(tái)Region Server上去,對(duì)外看來,跟調(diào)用單次的Put操作,沒有什么區(qū)別。您并不需要擔(dān)心數(shù)據(jù)數(shù)據(jù)存放在哪里,客戶端緩存區(qū)對(duì)外部調(diào)用者來說是透明的。圖3-1給出了客戶端如何將緩區(qū)的Put操作進(jìn)行分類,并對(duì)每個(gè)Region Server進(jìn)行一次單獨(dú)的RPC操作。
圖3-1 客戶端將緩區(qū)的Put操作分類發(fā)送到不同的RegionServer上
您可以強(qiáng)制調(diào)用flush操作,但事實(shí)上這是沒有必要的??蛻舳藭?huì)記錄現(xiàn)在緩區(qū)存中的數(shù)據(jù)所占的堆棧大小,包括緩區(qū)中存儲(chǔ)數(shù)據(jù)的額外開銷,比如一些HBase內(nèi)部用到的數(shù)據(jù)結(jié)構(gòu)。當(dāng)這個(gè)大小超過了一個(gè)閥值時(shí),客戶端會(huì)隱式地發(fā)送flush命令。您可以通過如下的命令獲取和設(shè)置客戶端緩存區(qū)的大?。?div style="height:15px;">
bytes),如果您存放的數(shù)據(jù)所占的空間很大時(shí),可以通過設(shè)置緩存區(qū)大小來保證一次RPC調(diào)用所攜帶的數(shù)據(jù)總數(shù)。