Hbase數據模型與table設計
最近在學習Hbase的使用,並仔細閱讀了一篇官方推薦的博客,在這裏就以一邊翻譯一邊總結的方式和大家一起梳理一下HBase的數據模型和基本的表設計思路。
官方推薦的博客原文地址:https://0b4af6cdc2f0c5998459-c0245c5c937c5dedcca3f1764ecc9b2f.r43.cf2.rackcdn.com/9353-login1210_khurana.pdf點擊打開鏈接
在一定程度上,Hbase又可以看成是以行鍵(Row Key),列標識(column qualifier),時間戳(timestamp)標識的有序Map數據結構的數據庫,具有稀疏,分布式,持久化,多維度等特點。
Base的數據模型介紹
HBase的數據模型也是由一張張的表組成,每一張表裏也有數據行和列,但是在HBase數據庫中的行和列又和關係型數據庫的稍有不同。下麵統一介紹HBase數據模型中一些名詞的概念:
表(Table): HBase會將數據組織進一張張的表裏麵,但是需要注意的是表名必須是能用在文件路徑裏的合法名字,因為HBase的表是映射成hdfs上麵的文件。
行(Row): 在表裏麵,每一行代表著一個數據對象,每一行都是以一個行鍵(Row Key)來進行唯一標識的,行鍵並沒有什麼特定的數據類型,以二進製的字節來存儲。
列族(Column Family): 在定義HBase表的時候需要提前設置好列族, 表中所有的列都需要組織在列族裏麵,列族一旦確定後,就不能輕易修改,因為它會影響到HBase真實的物理存儲結構,但是列族中的列標識(Column Qualifier)以及其對應的值可以動態增刪。表中的每一行都有相同的列族,但是不需要每一行的列族裏都有一致的列標識(Column Qualifier)和值,所以說是一種稀疏的表結構,這樣可以一定程度上避免數據的冗餘。例如:{row1, userInfo: telephone —> 137XXXXX869 }{row2, userInfo: fax phone —> 0898-66XXXX } 行1和行2都有同一個列族userinfo,但是行1中的列族隻有列標識(Column Qualifier):移動電話號碼,而行2中的列族中隻有列標識(Column Qualifier):傳真號碼。
列標識(Column Qualifier): 列族中的數據通過列標識來進行映射,其實這裏大家可以不用拘泥於“列”這個概念,也可以理解為一個鍵值對,Column Qualifier就是Key。列標識也沒有特定的數據類型,以二進製字節來存儲。
單元(Cell): 每一個 行鍵,列族和列標識共同組成一個單元,存儲在單元裏的數據稱為單元數據,單元和單元數據也沒有特定的數據類型,以二進製字節來存儲。
時間戳(Timestamp): 默認下每一個單元中的數據插入時都會用時間戳來進行版本標識。讀取單元數據時,如果時間戳沒有被指定,則默認返回最新的數據,寫入新的單元數據時,如果沒有設置時間戳,默認使用當前時間。每一個列族的單元數據的版本數量都被HBase單獨維護,默認情況下HBase保留3個版本數據。
圖片來自:https://0b4af6cdc2f0c5998459-c0245c5c937c5dedcca3f1764ecc9b2f.r43.cf2.rackcdn.com/9353-login1210_khurana.pdf
有時候,你也可以把HBase看成一個多維度的Map模型去理解它的數據模型。正如下圖,一個行鍵映射一個列族數組,列族數組中的每個列族又映射一個列標識數組,列標識數組中的每一個列標識(Column Qualifier)又映射到一個時間戳數組,裏麵是不同時間戳映射下不同版本的值,但是默認取最近時間的值,所以可以看成是列標識(Column Qualifier)和它所對應的值的映射。用戶也可以通過HBase的API去同時獲取到多個版本的單元數據的值。Row Key在HBase中也就相當於關係型數據庫的主鍵,並且Row Key在創建表的時候就已經設置好,用戶無法指定某個列作為Row Key。
又有的時候,你也可以把HBase看成是一個類似Redis那樣的Key-Value數據庫。如下圖,當你要查詢某一行的所有數據時,Row Key就相當於Key,而Value就是單元中的數據(列族,列族裏的列和列中時間戳所對應的不同版本的值);當深入到HBase底層的存儲機製時,用戶要查詢指定行裏某一條單元數據時,HBase會去讀取一個數據塊,裏麵除了有要查詢的單元數據,可能同時也會獲取到其它單元數據,因為這個數據塊還包含著這個Row Key所對應的其它列族或其它的列信息,這些信息實際也代表著另一個單元數據,這也是HBase的API內部實際的工作原理。
圖片來自:https://0b4af6cdc2f0c5998459-c0245c5c937c5dedcca3f1764ecc9b2f.r43.cf2.rackcdn.com/9353-login1210_khurana.pdf
HBase提供了豐富的API接口讓用戶去操作這些數據。主要的API接口有3個,Put,Get,Scan。Put和Get是操作指定行的數據的,所以需要提供行鍵來進行操作。Scan是操作一定範圍內的數據,通過指定開始行鍵和結束行鍵來獲取範圍,如果沒有指定開始行鍵和結束行鍵,則默認獲取所有行數據。
HBase的表設計中需要注意的問題
當開始設計HBase中的表的時候需要考慮以下的幾個問題:
1. Row Key的結構該如何設置,而Row Key中又該包含什麼樣的信息(這個很重要,下麵的例子會有說明)
2. 表中應該有多少的列族
3. 列族中應該存儲什麼樣的數據
4. 每個列族中存儲多少列數據
5. 列的名字分別是什麼,因為操作API的時候需要這些信息
6. 單元中(cell)應該存儲什麼樣的信息
7. 每個單元中存儲多少個版本信息
在HBase表設計中最重要的就是定義Row-Key的結構,要定義Row-Key的結構時就不得不考慮表的接入樣本,也就是在真真實應用中會對這張表出現什麼樣的讀寫場景。除此之外,在設計表的時候我們也應該要考慮HBase數據庫的一些特性。
1. HBase中表的索引是通過Key來實現的
2. 在表中是通過Row Key的字典序來對一行行的數據來進行排序的,表中每一塊區域的劃分都是通過開始Row Key和結束Row Key來決定的。
3. 所有存儲在HBase表中的數據都是二進製的字節,並沒有數據類型。
4. 原子性隻在行內保證,HBase表中並沒有多行事務。
5. 列族(Column Family)在表創建之前就要定義好
6. 列族中的列標識(Column Qualifier)可以在表創建完以後動態插入數據時添加。
接下來我們考慮一個這樣的場景,我們要設計一張表,用來保存微博上用戶互粉的信息。所以設計表之前,我們要考慮業務中的讀寫場景。
讀場景中我們要考慮:
1. 每個用戶都關注了誰
2. 用戶A有沒有關注用戶B
3. 誰關注了用戶A
寫場景中我們要考慮:
1. 用戶關注了另一個用戶
2. 用戶取消關注某個用戶
下麵我們來看幾種表結構的設計:
第一種表結構設計中,在這種表結構設計中,每一行代表著某個用戶和所有他所關注的其它用戶。這個用戶ID就是Row Key,而每一個列標識(Column Qualifier)就是這個用戶所關注的其他用戶在列族裏的序號,單元數據就是這個用戶所關注的其他用戶的用戶ID。在這種表結構的設計下,“每個用戶都關注了誰”這個問題很好解決,但對於“用戶A有沒有關注用戶B”這個問題在列很多的時候,需要遍曆所有單元數據去找到用戶B,這樣的開銷會十分大。並且當添加新的被關注用戶時,因為不知道給這個新用戶分配什麼樣的列族序號,需要遍曆整個列族中的所有列找出最後一個列,並將最後一個列的序號+1給新的被關注用戶作為列族內的序號,這樣的開銷也十分大。
圖片來自:https://0b4af6cdc2f0c5998459-c0245c5c937c5dedcca3f1764ecc9b2f.r43.cf2.rackcdn.com/9353-login1210_khurana.pdf
所以衍生出了第二種表結構設計,如下圖,添加一個counter記錄列族中所有列的總數量,當添加新的被關注用戶時,這個新用戶的序號就是counter+1。但是當要取消關注某個用戶時,一樣得遍曆所有的列數據,而且最大的問題是在於HBase不支持事務處理,這種通過counter來添加被關注用戶的操作邏輯得寫在客戶端中。
圖片來自:https://0b4af6cdc2f0c5998459-c0245c5c937c5dedcca3f1764ecc9b2f.r43.cf2.rackcdn.com/9353-login1210_khurana.pdf
回想一下,列標識(Column Qualifier)存儲的時候是二進製的字節,所以列標識可以存儲任何數據,而且列標識還是動態增添的,基於這個特性我們再改進表的設計,如下圖。這次以被關注的用戶ID做為列標識(Column Qualifier),然後單元數據可以是任意數字,比如全部統一成1。在這種表結構的設計下,添加新的被關注者,以及取消關注都會變得很簡單。但是對於讀場景中,誰關注了用戶A這個問題,因為HBase數據庫的索引隻建立在Row Key上,這裏不得不掃描全表去統計所有關注了用戶A的用戶數量,所以下麵的這個表結構設計也存在一定的性能問題。這裏也引出一個思路,被關注者需要以某種方式添加索引。
圖片來自:https://0b4af6cdc2f0c5998459-c0245c5c937c5dedcca3f1764ecc9b2f.r43.cf2.rackcdn.com/9353-login1210_khurana.pdf
針對上麵的表結構有三種優化方案,第一種是新建另一張表,裏麵保存某個用戶和所有關注他的用戶。第二種解決方案就是在同一張表中也存儲某個用戶和所有關注他的用戶的信息,並從Row Key中區分開來,比如:Row key為Jame_001_following的這行保存著所有Jame關注的人的信息,而Row_Key為Jame_001_followed的這行保存著所有關注Jame的人的信息。最後一種優化方案就是,如下圖,將Row Key設計成“followerID+followedID”的形式,比如:“Jame+Emma”,這裏的Row Key值就代表著Jame關注了Emma(其實這裏應該是“Jame的ID+Emma的ID”,隻是為了解釋方便而直接用名字),同時包含了關注者和被關注者兩個信息;還需要注意的一點就是列族的名字被設計成隻有一個字母f,這樣設計的好處就是減少了HBase對數據的I/O操作壓力,同時減少了返回到客戶端的數據字節,提高響應速度,因為每一個返回給客戶端的KeyValue對象都會包含列族名字。同時將被關注人的用戶名稱也保存在了表中作為Column Qualifier,這樣做的好處就是節省了去用戶表查找用戶名的資源。在這種表結構設計下,“用戶A取消關注某個用戶B”,“用戶A有沒有關注用戶B?”的業務處理就會變得簡單高效。
圖片來自:https://0b4af6cdc2f0c5998459-c0245c5c937c5dedcca3f1764ecc9b2f.r43.cf2.rackcdn.com/9353-login1210_khurana.pdf
還有一個需要注意的問題,就是在實際的生產環境中,還需要將Row Key使用MD5加密,一方麵是使Row Key的長度都一致,能提高數據的存取性能。這方麵的優化不在本文的討論範圍內。
圖片來自:https://0b4af6cdc2f0c5998459-c0245c5c937c5dedcca3f1764ecc9b2f.r43.cf2.rackcdn.com/9353-login1210_khurana.pdf
總結:
整篇文章概述了HBase的數據模型和基本的表設計思路。下麵是HBase一些關鍵特性的總結:
1. Row Key是HBase表結構設計中很重要的一環,它設計的好壞直接影響程序和HBase交互的效率和數據存儲的性能。
2. Base的表結構比傳統關係型數據庫更靈活,你能存儲任何二進製數據在表中,而且無關數據類型。
3. 在相同的列族中所有數據都具有相同的接入模式
4. 主要是通過Row Key來建立索引
5. 以縱向擴張為主設計的表結構能快速簡單的獲取數據,但犧牲了一定的原子性,就比如上文中最後一種表結構;而以橫向擴張為主設計的表結構,也就是列族中有很多列,比如上文中第一種表結構,能在行裏麵保持一定的原子性。
6. HBase並不支持事務,所有盡量在一次API請求操作中獲取到結果
7. 對Row Key的Hash優化能獲得固定長度的Row Key並使數據分布更加均勻一些,而不是集中在一台服務器上,但是也犧牲了一定的數據排序和讀取性能。
8. 可以利用列標識(Column Qualifier)來存儲數據。
9. 列標識(Column Qualifier)名字的長度和列族名字的長度都會影響I/O的讀寫性能和發送給客戶端的數據量,所以它們的命名應該簡潔!
最後更新:2017-09-20 17:03:39