天天看點

【轉】HBase用戶端API:管理特性 - 架構(schema)定義

原文連結 http://www.cnblogs.com/songtaohu/archive/2012/04/20/2460722.html

1        架構(schema)定義

在HBase中建立一個表隐含地涉及到表的架構定義,同樣還有表中所包含的列族(column families)的架構(schema)。它們定義了關于表中的資料和列最終是怎樣以及何時被存儲的相關特性。

1.1    表(Table)

存儲在HBase中的每件事物最終被分組到一個或多個表。擁有表的首要理由是能夠控制這個表共享的所有列的某些特性。你将要為一個表定義的典型事物是列族(column families)。Java中的表描述符(table descriptor)的構造函數看起來像這樣:

HTableDescriptor();

HTableDescriptor(string name);

HTableDescriptor(byte[] name);

HTableDescriptor(HTableDescriptor desc);

Writable接口和無參數的構造函數

你将發現API提供的,以及貫穿本章所讨論的大多數類的确擁有一個指定的構造函數,這是一個沒有任何參數的構造函數。這是由于這些類實作Hadoop的Writable接口。

每一個互相分解的遠端子系統之間的通信 – 例如,用戶端要和服務端通信,但是服務端之間也互相通信 – 都是用Hadoop RPC 架構。該架構采用Writable類來表示可以在網絡上被發送的對象。這些對象實作了兩個必需的Writable方法:

void write(DataOutput out) throws IOException;

void readFields(DataInput in)throws IOException;

這兩個方法被架構所調用,将對象的資料寫入到輸出流(ouput stream),随後在接收系統中讀出來。為此,架構在發送方調用write(),序列化對象的字段(fields) – 這期間架構會留心注意對類名以及代表它們的其他細節進行注解。

在接收服務一端,架構讀取中繼資料,然後建立類的一個空執行個體,再調用剛建立的執行個體的readFields()方法。這将讀回字段(field)資料,留給你一個完全可工作的發送對象的初始化拷貝。

由于接收方需要使用反射建立類,這暗示着它必須可以利用比對的,編譯的類。通常就是這樣,服務端和用戶端都使用相同的HBase Java archive檔案,或JAR。

但是如果你開發自己的HBase擴充 – 例如,過濾器(filters)和協同處理程式(coprocessors),正如我們在第4章讨論的 – 你必須確定你的定制類遵守這些準則:

l  在RPC通信通道的兩方,也就是發送方和接收方,都應該是可用的。

l  應實作Writable接口,包括write()和readFields()方法。

l  擁有無參數構造函數,也就是一個沒有任何參數的構造函數。

如果沒有提供這個指定的無參數構造函數,将導緻運作時錯誤。從你的代碼中顯式調用這個構造函數也是徒勞的,因為這會給你生成一個未初始化的執行個體,它的行為絕對不會如你預期的。

作為一個用戶端API開發者,你應該知曉底層對RPC的依賴,以及它怎樣對自身列出清單(manifest)。作為一名對HBase進行擴充的進階開發者,你需要恰當地實作和部署你的定制代碼。160頁上的定制過濾器(Custom Fillters)有一個執行個體和進一步的注解。

你可以用一個名稱或已經存在的描述符(descriptor)建立一個表。無任何參數的構造函數僅用于序列化目的,不應該直接使用。你可以用一個Java String或byte[] 指定表的名稱。HBase Java API中的很多函數有兩種選擇。String版本明顯是為了友善和内部轉換成byte數組表現形式,正如HBase将所有資料都看作byte數組。你可以使用提供的Byte類來實作這一點:

Byte[] name = Buytes.toBytes(“test”);

HTableDescriptor desc = new HTableDescriptor(name);

對于你用來建立表名的字元有一定的限制。表的名稱被用于實際存儲檔案的路徑的一部分,因而遵守檔案命名規則。你可以随後浏覽低級存儲系統 – 例如,HDFS – 甚至當你需要的時候将表看做單獨的目錄。

HBase的面向列的存儲格式允許你存儲很多細節到相同的表中,而在關系型資料庫模型中,将會被分成很多單獨的表。

在HBase中,表的數量很少。

盡管從概念上來說在HBase中一個表是帶有列的行的集合,實體上它們被存儲在稱為regions的單獨的分區中。插圖5-1展示了存儲的資料在邏輯和實體布局上的不同。每一個regsion都正好被一個region server所管理, region server輪流直接為用戶端提供已存儲的值。

插圖5-1 在區塊(regions)中的行(rows)的邏輯和實體布局

1.2    表的屬性

表的描述符提供了getters和setters[1]來設定表的其它選項。在實踐中,很多選項不經常使用,但是重要的是要知道所有這些選項,它們可能被用于微調表的性能。

名稱(Name)

構造函數已經有一個參數來指定表名。Java API有一個額外的方法來通路表名或修改它。

Byte[]  getName();

String  getNameAsString();

void  setName(byte[] name);

表名必須不以“.”(句點)或“-”(連字元)開頭。此外,它隻能包含拉丁字母或數字,還有“_”(下劃線),“-”(連字元),或“.”(句點)。在正規表達式文法中,這可以被表示為[a-zA-Z_0-9-.]。

例如,.testtable是錯誤的,但是test.table是允許的。

更多細節詳見212頁的列族“Column Families”,插圖5-2展示了一個關于表名是怎樣被用于形成檔案系統路徑的一個例子。

列族(Column families)

這是關于定義一個表的最重要的部分。你需要指定用于正在建立的表的column families。

void  addFamily(HColumnDescriptor family);

Boolean  hasFamily(byte[] c);

HColumnDescriptor  getColumnFamilies();

HColumnDescriptor  getFamily(byte[]  column);

HColumnDescriptor  removeFamily(byte[]  column);

你可以選擇添加一個列族,基于它的名稱來檢查它是否存在,擷取一個所有已知的列族的清單,擷取或移除一個指定的列族。更多關于定義一個需要的HColumnDescriptor的内容在212頁的“Column Families”中解釋。

最大檔案尺寸(Maximum file size)

這個參數是指定在一個表内部的region能夠增長到的最大尺寸。用位元組數指定,使用以下方法讀取和設定:

long getMaxFileSize();

void setMaxFileSize(long maxFileSize);

最大檔案尺寸實際上是用詞不當,因為它實際上是關于每一個存儲(store)的最大尺寸,所有的檔案歸屬于每一個列族(column family)。如果一個單獨的列族超出了這個最大尺寸,region就會分裂。因而在實踐中,這涉及到多個檔案,更好的    命名是maxStoreSize。

最大檔案尺寸(maximum  size)有助于當區塊(region)達到配置的尺寸時系統對區塊進行分裂(split)操作。正如在第16頁讨論過的“構造塊(Building  Blocks)”,在HBase中的可擴充和負載平衡的單元是區塊(region)。雖然你需要決定這個尺寸的一個合适的值,但預設設定是256MB,對于大多數使用場景都是合适的,但是當你擁有大量資料時可能需要一個更大的值。

請注意,這或多或少是一個理想中的最大尺寸,在給定某些條件的情況下,這個尺寸可能被超越,實際上是沒有影響地完全呈現。作為一個例子,你可以設定最大檔案尺寸(maximum  size)為10MB,插入到某行中一個20MB的單元格(cell)中。由于一行不能被跨區塊(region)地分裂(split),你最終得到一個至少20MB的區塊(region),系統對此不會采取任何動作。

隻讀(Read-only)

預設情況下,所有的表都是可寫的(writable),但是對于特定的表指定一個read-only選項可能是有意義的。如果标志被設定為true,你就隻能從表中讀取資料,而根本不能修改它。标志是通過這些方法來設定和讀取:

Boolean  isReadOnly();

Void  setReadONly(Boolean  readOnly);

記憶體存儲重新整理大小(Memstore flush size)

我們先前讨論了存儲模型,識别了HBase是怎樣在使用一個稱為flush的操作中将值作為一個新的存儲檔案寫到磁盤之前,使用一個記憶體存儲來緩存這些值。這個參數控制了何時這個動作要發生,以及用數組大小來指定。它被以下調用來控制:

Long  getMemStoreFlushSize();

Void  setMemStoreFlushSize(long  memstoreFlushSize);

當你要利用上述的最大檔案尺寸(maximum  size)時,當你将這個值設定為超過預設的64MB時,你需要檢查需求。一個更大的值意味着你正在生成更大的存儲檔案,這很好。另一方面,如果區塊伺服器(region server)不能持續重新整理(flushing)新增的資料,你可能陷入到更長的阻塞(blocking)周期這樣一個問題當中。同時,這也增加了當伺服器崩潰掉,所有記憶體更新(in-memory updates)丢失以後根據預寫日志(write-ahead log)(the WAL)恢複資料時所需要的時間。

延遲日志重新整理(Deferred log flush)

我們将在第333頁的預寫日志“Write-Ahead Log”中更詳細地讨論日志重新整理(log flushing),在那裡解釋這個選項。現在,請注意HBase 使用這兩種方法中的其中一個來儲存預寫日志(write-ahead-log)條目到磁盤上。你可以使用延遲日志重新整理(deferred log flushing),也可以不使用。這是一個布爾選項,預設設定為false。這裡是怎樣通過Java API通路這個參數的代碼:

synchronized boolean isDeferredLogFlush();

void setDeferredLogFlush(boolean isDeferredLogFlush);

各種雜項

除了已經提到的之外,還有幾個方法讓你可以設定任意的鍵/值(key/value)對:

byte[] getValue(byte[] key) {

String getValue(String key)

Map<ImmutableBytesWritable, ImmutableBytesWritable> getValues()

void setValue(byte[] key, byte[] value)

void setValue(String key, String value)

void remove(byte[] key)

這些值和表的定義一起被存儲,當需要的時候可以擷取它們。在HBase中的一個實際的用例是協同處理程式(coprocessors)的加載,這将在第179頁的“協同處理程式的加載(Coprocessor Loading)”中詳細說明。在怎樣指定鍵值對這方面,你有一些選擇,或者使用String,或者使用byte數組。在内部它們都被作為ImmutableBytesWritable存儲,這是為了序列化的目的而産生的需要(見第207頁的“Writable接口和無參數的構造函數”)。

1.3    列族(column families)

我們剛才看到了HTableDescriptor是怎樣暴露方法來添加column families到表中。與此類似的是一個稱為HColumnDescriptor的類,它将每一個column family的設定封裝到一個專用的Java類中。在其它程式設計語言中,你可以找到某些概念或一些指定列族(column family)屬性的其它方式。

Java中的類有一些誤拼。一個更合适的名稱是HColumnFamilyDescriptor,它指出它的目的是定義列族(column family)參數而不是實際的列(column)。

列族(column families)定義了應用到其中的所有列的共享特性。用戶端可以在系統運作過程中通過簡單地使用new column qualifiers來建立任意數量的列。列是用列族(column family)名和列限定符(column qualifier)的組合來稱呼的,兩部分用一個冒号分開:

family:qualifier

列族(column family)名稱必須由可列印字元組成:限定符(qualifier)可以由任意二進制字元組成。回憶早先提到的Bytes類,你可以用它将你標明的名稱轉換為byte數組。列族名稱必須為可列印字元的理由是因為在列族名稱被低級别的存儲層用于作為存儲目錄名的一部分。列族名稱被添加到路徑中,并且必須遵守檔案命名标準。好處是你當你有一個可閱讀格式的名稱時,可以在檔案系統層級很容易地通路列族。

列限定符(qualifier),又被稱為列的鍵(key),它唯一辨別這個列族中的某一列。

你也應該知道空列(empty column)限定符。你可以簡單地忽略限定符,而隻指定列族名。HBase然後建立一個帶有特殊的空限定符的列。你可以像讀寫任何其它列一樣讀寫那一列,但是明顯地,這樣的列隻能有一個,你必須命名其他所有列以便差別它們。

當你建立一個列族(column family)時,你可以指定各種參數來控制它的所有特性。Java類有很多構造函數允許你在建立一個執行個體時指定大多數參數。這裡有可選的形式:

HColumnDescriptor();

HColumnDescriptor(String familyName),

HColumnDescriptor(byte[] familyName);

HColumnDescriptor(HColumnDescriptor desc);

HColumnDescriptor(byte[] familyName, int maxVersions, String compression,

boolean inMemory, boolean blockCacheEnabled, int timeToLive,

String bloomFilter);

HColumnDescriptor(byte [] familyName, int maxVersions, String compression,

boolean inMemory, boolean blockCacheEnabled, int blocksize,

int timeToLive, String bloomFilter, int scope);

第一個是無參數構造函數,僅用于内部反序列化。接下來兩個簡單地接收一個String或byte[]作為名稱,通常的位元組數組我們已經見過多次了。另一個接收一個已經存在的HColumnDescriptor,最後兩個列出了所有可用的參數。

取代使用構造函數,你也可以使用getter和setter來指定各種細節。我們現在讨論它們。

名稱(Name)

每一個列族有一個名稱,你可以使用以下方法從一個已經存在的HColumnDescriptor執行個體來擷取它:

Byte[]  getName();

Sting  getNameAsString();

列族不能被重命名。重命名一個列族的通常途徑是使用API建立一個有着期望名稱的新的列族,然後将資料複制過去。

你不能設定名稱,但是你可以使用這些構造函數接收名稱。記住對于名稱的要求是可列印(printable)字元。

列族的名稱不能從一個“.”(英文句号)開始,也不能包含“:”(英文冒号),“/”(斜杠),或者ISO控制字元,換句話說,如果它的編碼在\u0000到\u001F或者在\u007F到\u009F之間,這屬于ISO控制字元,是不允許的。

最大版本(Maximum versions)

對于每一個列族,你可以指定你希望在每一個值上面保持多少版本。回顧一下早先提到的HBase的内部管理中移除超過設定的最大版本的那些值,即稱之為predicate deletion(預告删除)的操作。使用以下API調用可以擷取和設定最大版本。

int  getMaxVersions();

void  setMaxVersions(int  maxVersions);

預設值是3,但是你可以減少到1,例如,當你确信你将不再需要檢視哪些舊的值時。

壓縮(Compression)

HBase有可插入的壓縮算法支援(你可以在424頁的“壓縮”中找到關于這一主題的更多資訊),允許你對于存儲在特定列族中的選擇最好的壓縮 – 或者不壓縮。可能的算法列出在表5.1中。

清單5.1。支援的壓縮算法

Value Description
NONE 禁用壓縮 (預設地) 。
GZ 使用Java提供的或原生的GZip壓縮。
LZO 啟用LZO壓縮;必須被單獨安裝。
SNAPPY 啟用Snappy壓縮;二進制檔案必須被單獨安裝。

預設值NONE – 也就是說,當你建立一個列族(column family)時不啟用任何壓縮。一旦你處理Java API和列描述符(column  descriptor),你可以使用這些方法來改變值:

Compression.Algorithm getCompression();

Compression.Algorithm getCompressionType();

void setCompressionType(Compression.Algorithm type);

Compression.Algorithm getCompactionCompression();

Compression.Algorithm getCompactionCompressionType();

void setCompactionCompressionType(Compression.Algorithm type);

請注意這些值不是一個String,而是一個與清單5-1中暴露相同值的Compression.Algorithm枚舉。HColumnDescriptor的構造函數采用相同值的字元串作為參數。

我們觀察到有兩套方法,其中一個是通常的壓縮設定,另一個是壓實(compaction)壓縮設定。同時,每一組有一個getCompress()和getCompressionType()(或者是getCompactionCompression和getCompactionCompressionType()),傳回值是相同類型的。它們的确是備援的,你可以使用任何一組來擷取目前壓縮算法類型。[2]

我們将在第424頁的“壓縮(Compression)”中更詳細地研究這個主題。

塊尺寸(Block Size)

HBase中所有的存儲檔案被分成更小的塊(block),這些塊在get或scan操作期間被加載,這類似于關系資料庫關系系統(RDBMSes)中的頁(pages)。塊尺寸預設設定為64KB,可以使用以下方法調整:

synchronized int getBlocksize();

void setBlocksize(int s);

該值是用byte數組指定的,可以被用于在檢索以及由于随後的通路而緩存在記憶體中時,控制HBase需要從存儲檔案讀取多少資料。在第436頁的“配置(Configuration)”可以找到這個參數是怎樣被用于對你的設定程式進行精細調整。

在列族塊尺寸(column family block size),或HFile block size以及在HDFS層級指定的塊尺寸(block size)之間有一個重要差別。Hadoop,特别是HDFS,使用塊尺寸 – 預設地 – 64MB,來為了分布式的,使用MapReduce架構的并行處理。對于HBase來說,HFile block size預設是64KB,或者是HDFS塊尺寸的1/1024。HBase使用的存儲檔案使用這個更細粒度的尺寸來在塊操作(block operations)時更加有效地加載和緩存資料。它不依賴于HDFS塊尺寸,而僅僅在HBase内部使用。更多細節詳見第319頁的“存儲(Storage)”,特别是插圖8-3,展示了兩個不同的塊類型。

塊緩存(Block Cache)

為了高效的I/O使用,HBase讀取全部的資料塊(block),它将這些塊(block)保留在記憶體中,這樣後續的讀取就不需要任何磁盤操作了。預設是true,為每一次讀操作啟用塊緩存。但是如果你的用例永遠是僅在特定的列族上順序讀取資料,那麼建議你通過将塊緩存(block cache)标志設定為false來禁止它。這裡有用于改變這個标志的API:

boolean isBlockCacheEnabled();

void setBlockCacheEnabled(boolean blockCacheEnabled);

還有一些用于影響塊緩存的使用的其他選項,例如,在scan操作期間。這在全表掃描(full table scan)期間是有用的,這樣你就不會在緩存上引起頻繁填充(major churn)。關于這一特性的更多特性請見“配置(Configuration)”。

生存時間(Time-to-live)

HBase在為每個值保持的版本數上支援斷言删除(predicate deletions),但是也可以是指定的次數。生存時間(time-to-live或TTL)設定一個基于值的時間戳的閥值,并且如果一個值超過了它的TTL,内部的處理機制就會來自動檢查。如果是那種情況,在主壓實(major compactions)期間會被丢棄。API提供了以下的getter和setter來讀寫TTL:

int getTimeToLive();

void setTimeToLive(int timeToLive);

值是用秒為機關指定的,預設設定為Interger.MAX_VALUE或2,147,483,647秒。預設值也被看作是永久保持值的這種特殊情況,也就是,任何小于預設值的正數值都會啟用這個特性。

駐留記憶體(In-memory)

我們提到了塊緩存(block cache)以及HBase為了高效地順序通路資料是怎樣使用塊緩存将資料的所有塊都保持在記憶體中。駐留(in-memory)标志預設為false,但是可以被以下方法修改:

boolean isInMemory();

void setInMemory(boolean inMemory);

設定為true不保證一個列族的所有塊都加載到記憶體中,也不保證它們會保持在那裡。将這個設定值看作一個承諾,或提升的優先權,隻要它們在正常的擷取操作中被加載,就将它們保持在記憶體中,一直到堆(基于Java的伺服器程序的有效記憶體)上的壓力過高為止,那是就需要強行忽略這個設定。

通常,這一設定對于隻有少數幾個值的小型列族(column family)是好的,諸如使用者表的密碼,這樣登入(login)就能被很快處理。

布隆過濾器(Bloom filter)

HBase中一個可用的進階特性是布隆過濾器(Bloom filters),為你提供一種特定的通路模式,允許你改進查找次數(詳情請見第377頁的“布隆過濾器(Bloom filters)”)。由于它們在存儲和記憶體方面增加了開銷,預設是關閉的。表5-2展示了可能的選項。

表5-2 支援的布隆過濾器類型

Type Description
NONE Disables the filter (default)
ROW Use the row key for the filter
ROWCOL Use the row key and column key (family+qualifier) for the filter

最後一個選項,ROWCOL,由于有比行更多的列(除非在每一行中僅有一列),需要最大數量的空間。雖然,這更加細粒度,由于它知道每一個行/列的組合,而不是僅知道行。

布隆過濾器可以用這些調用改變和擷取:

StoreFile.BloomType getBloomFilterType();

void setBloomFilterType(StoreFile.BloomType bt);

對于壓縮的值,這些方法采用一個StoreFile.BloomType類型,列描述符的構造函數讓你将上述類型作為一個string來指定。後面的轉換不重要,是以你可以使用“row”。“Bloom Filters”這一節中有關于怎樣才能最好地使用它們的内容。

複制範圍(Replication scope)

随HBase提供的另外一個更加進階的特性是複制(replication)。它讓你可以有多個群集,跨網絡地傳送本地更新,是以這些更新被應用到遠端拷貝。

預設的,複制是禁用的,複制範圍(replication scope)設定為0,意味着它是禁用的。你可以用這些功能改變範圍:

int getScope();

void setScope(int scope);

唯一的其他支援的值(在編寫本書時)是1,這啟用複制到遠端群集。在未來可能有更多的範圍值。支援的值的清單詳見表5-3。

表5-3 支援的複制範圍

Scope Description
Local scope, i.e., no replication for this family (default)
1 Global scope, i.e., replicate family to a remote cluster

全部細節可以在第462頁的“複制(replication)”中找到。

最後,Java類有一個helper方法來檢驗列族名是否是合法的:

static byte[] isLegalFamilyName(byte[] b);

在你的程式中使用它來校驗符合規範的使用者提供的輸入,這些輸入需要提供列名。它不傳回一個布爾标志,但是當名稱有缺陷時,會抛出一個IllegalArgumentException。否則,它傳回的就是你提供的那個參數,沒有任何改變。完全專用的構造函數展示了在内部更早地使用這個方法,來校驗給出的名稱;在這種情況下,你不需要事先調用這個方法。

[1] Java中的Getters和Setters是以一種受控的方式暴露出來的類的方法。它們通常像字段一樣被命名,用get和set作為字首 – 例如,getName()和setName()。

[2]畢竟,這是開源産品,并且像這樣的備援經常是由于先前的遺留代碼引起的。請盡管清理掉這些并且把這個清理工作貢獻給HBase項目。

轉載于:https://www.cnblogs.com/ihongyan/p/4722167.html