HBase RowKey與索引設計


1. HBase的存儲形式

hbase的內部使用KeyValue的形式存儲,其key時rowKey:family:column:logTime,value是其存儲的內容。

其在region內大多以升序的形式排列,唯一的時logTime是以降序的形式進行排列。

所以,rowKey里越靠近左邊的信息越容易被檢索到。其設計時,要考慮把重要的信息放左邊,不重要的信息放到右邊。這樣可以提高查詢數據的速度。最重要的提高索引速度的就是設計合適的rowKey。

在做RowKey設計時,請先考慮業務是讀比寫多,還是讀比寫少,HBase本身是為寫優化的,即便是這樣,也可能會出現熱點問題,而如果我們讀比較多的話,除了考慮以上RowKey設計原則外,還可以考慮HBase的Coprocessor甚至elasticSearch結合的方法,無論哪種方式,都建議做實際業務場景下數據的壓力測試以得到最優結果。

2. RowKey的設計原則

2.1 長度原則

rowKey是一個二進制,RowKey的長度被很多開發者建議說設計在10~100個字節,以byte[]形式保存,最大不能超過64kb建議越短越好,不要超過16個字節

太長的影響有幾點點:

  • 一是HBase的持久化文件HFile是按照KeyValue存儲的,如果RowKey過長,比如說500個字節,1000萬列數據,光是RowKey就要占用500*1000萬=50億個字節,將近1G數據,極大影響了HFile的存儲效率。
  • 二是緩存MemStore緩存部分數據到內存中,如果RowKey字段過長,內存的有效利用率會降低,系統無法緩存更多的數據,降低檢索效率。
  • 目前操作系統都是64位系統,內存8字節對齊,控制在16字節,8字節的整數倍利用了操作系統的最佳特性。

注意:不僅RowKey的長度是越短越好,而且列簇名、列名等盡量使用短名字,因為HBase屬於列式數據庫,這些名字都是會寫入到HBase的持久化文件HFile中去,過長的RowKey、列簇、列名都會導致整體的存儲量成倍增加。

2.2 唯一原則

保證rowKey的唯一性。由於在HBase中數據存儲是Key-Value形式,若HBase中同一表插入相同RowKey,則原先的數據會被覆蓋掉(如果表的version設置為1的話)。

2.3 散列原則

設計的RowKey應均勻分布在各個HBase節點上。如RowKey是按系統時間戳的方式遞增,RowKey的第一部分如果是時間戳的話,將造成所有新數據都在一個RegionServer堆積的熱點現象,也就是通常說的Region熱點問題,熱點發生在大量的client直接訪問集中在個別RegionServer上(訪問可能是讀、寫或者其他操作),導致單個RegionServer機器自身負載過高,引起性能下降甚至Region不可用,常見的是發生jvm full gc或者顯示region too busy異常情況。

3. 在不同訪問模式下設計行健

3.1 為寫優化(解決熱點問題)

當往HBase表寫入大量數據時,需要在RegionServer上分散負載來進行優化。這並不難,但是你可能不得不在讀模式優化上付出代價。比如,時間序列數據的例子,如果你的數據直接使用時間戳做行健,在寫入時在單個region上會遇到熱點問題。

許多使用場景下,並不需要基於單個時間戳訪問數據。你可能要運行一個作業在一個時間區間上做聚合計算,如果對時間延遲不敏感,可以考慮跨多個region做並行掃描來完成任務。但問題是,應該如何把數據分散在多個region上呢?有幾個選項可以考慮,答案取決於你想讓行健包含什么信息。

  1. 散列 。 如果你願意在行健里放棄時間戳信息(每次你做什么事情都要掃描全表,或者每次要讀數據時你都知道精確的鍵,這些情況下也是可行的),使用原始數據的散列值作為行健是一種可能的解決方案:

          

           每次當你需要訪問以這個散列值為鍵的行時,需要精確知道“TheRealMT”。時間序列數據一般不這樣處理。當你訪問數據時,可能記住了一個時間范圍,但不大可能知道精確的時間戳。但是有些情況下,能夠計算散列值從而找到正確的行。為了得到一種跨所有region的、優秀的分布策略,你可以使用MD5、SHA-1或者其他提供隨機分布的散列數。

        2.salting。當你思考行健的構成時,salting是另一種技巧。讓我們考慮之前的時間序列數據例子。假設你在讀取時知道時間范圍,但不想做全表掃描。對時間戳做散列運算然后把散列值作為行健的做法需要做全表掃描,這是很低效的,尤其是在你有辦法限制掃描范圍的時候。使用散列值作為行健在這里不是辦法,但是你可以在時間戳前面加上一個隨機數前綴。

         例如,你可以先計算時間戳的散列碼,然后用RegionServer的數量取模來生成隨機salt數:

         

           取到salt數后,加到時間戳的前面生成行健:

           

   現在行健如下所示:

          

   你可以想到,這些行將會基於鍵的第一部分,也就是隨機salt數,分布在各個region。

   0|timestamp1,0|timestamp5和0|timestamp6將進入一個region,除非發生region拆分(拆分的情況下會分散到兩個region)。1|timestamp2,1|timestamp9進入另一個不同的region,2|timestamp4,2|timestamp8進入第三個region。連續時間戳的數據散列進入了多個region。

   但並非一切都是完美的。現在讀操作需要把掃描命令分散到所有region上來查找相應的行。因為它們不再存儲在一起,所以一個短掃描不能解決問題了。這是一種權衡,為了搭建成功的應用你需要做出選擇。這是一個利用信息的位置來獲得跨region分布的經典例子。

           3. Reverse反轉。針對固定長度的RowKey反轉后存儲,這樣可以使RowKey中經常改變的部分放在最前面,可以有效的隨機RowKey。反轉RowKey的例子通常以手機舉例,可以將手機號反轉后的字符串作為RowKey,這樣就避免了以手機號那樣比較固定開頭導致熱點問題。這樣做的缺點是犧牲了RowKey的有序性。

3.2 為讀優化

           時間戳反轉。一個常見的數據處理問題是快速獲取數據的最新版本,使用反轉的時間戳作為RowKey的一部分對這個問題十分有用,可以用Long.Max_Value - timestamp追加到key的末尾。舉例,在設計推帖流表時,你的焦點是為讀優化行健,目的是把推帖流里最新的推帖存儲在一起,以便於它們可以被快速讀取,而不用做開銷很大的硬盤搜索。在推貼流表里,你使用倒序時間戳(Long.MAX_VALUE - 時間戳)然后附加上用戶ID來構成行健。現在你基於用戶ID掃描緊鄰的n行就可以找到用戶需要的n條最新推帖。這里行健的結構對於讀性能很重要。把用戶ID放在開頭有助於你設置掃描,可以輕松定義起始鍵。

4. HBase的RowKey設計應用實例

4.1 設計訂單狀態表

設計模式:反轉+時間戳反轉

RowKey:reverser(order_id) + (Long.MAX_VALUE - timestamp)

這樣設計的好處一是通過reverse訂單號避免Region熱點,二是可以按時間倒排顯示,可以獲取到最新的訂單。

同樣適用於需要保存一個用戶的操作記錄,按照操作時間倒序排序。設計的rowKey為:reverser(userId) + (Long.MAX_VALUE - timestamp)。如果需要查詢某段時間的操作記錄,startRow是[userId反轉][Long.MAX_VALUE - 結束時間],stopRow是[userId反轉][Long.MAX_VALUE - 起始時間]。

4.2 登錄、下單等等統稱事件(event)的臨時存儲

HBase只存儲了最近10分鍾的熱數據

設計模式:salt加鹽

RowKey:兩位隨機數Salt + eventId + Date + kafka的Offset

這樣設計的好處是:設計加鹽的目的是為了增加查詢的並發性,假如Salt的范圍是0~n,那我們在查詢的時候,可以將數據分為n個split同時做scan操作。經過我們的多次測試驗證,增加並發度能夠將整體的查詢速度提升5~20倍以上。隨后的eventId和Date是用來做范圍Scan來使用的。在我們的查詢場景中,大部分都是指定了eventId的,因此我們在eventId放在了第二個位置上,同時呢,通過Salt + eventId的方式可以保證不會形成熱點。把date放在RowKey的第三個位置上可以實現date做scan,批量Scan性能甚至可以做到毫秒級返回。

這樣的RowKey設計能夠很好的支持如下幾個查詢場景:

  1. 全表scan。在這種情況下,我們仍然可以將全表數據切分成n份並發查詢,從而實現查詢的實時響應。
  2. 只按照event_id查詢。
  3. 按照event_id和date查詢。

5. HBase索引設計

數據庫查詢可簡單分解為兩個步驟:1)鍵的查找;2) 數據的查找

因這兩種數據組織方式的不同,在RDBMS領域有兩種常見的數據組織表結構:

索引組織表:鍵與數據存放在一起,查找到鍵所在的位置則意味着查找到數據本身。

堆表:鍵的存儲與數據的存儲是分離的。查找到鍵的位置,只能獲取到數據的物理地址,還需要基於該地址去獲取數據。

HBase數據表其實是一種索引組織表結構:查找到RowKey所在的位置則意味着找到數據本身。因此,RowKey本身就是一種索引

5.1 RowKey查詢的局限性/二級索引需求背景

如果提供的查詢條件能夠盡可能豐富的描述RowKey的前綴信息,則查詢時延越能得到保障。如下面幾種組合條件場景:

  * Name + Phone + ID
  * Name + Phone
       * Name

如果查詢條件不能提供Name信息,則RowKey的前綴條件是無法確定的,此時只能通過全表掃描的方式來查找結果。

一種業務模型的用戶數據RowKey,只能采用單一結構設計。但事實上,查詢場景可能是多緯度的。例如,在上面的場景基礎上,還需要單獨基於Phone列進行查詢。這是HBase二級索引出現的背景。即,二級索引是為了讓HBase能夠提供更多緯度的查詢能力。

注:HBase原生並不支持二級索引方案,但基於HBase的KeyValue數據模型與API,可以輕易的構建出二級索引數據。Phoenix提供了兩種索引方案,而一些大廠家也都提供了自己的二級索引實現。

5.2 HBase 二級索引方案

5.2.1 基於Coprocessor方案

從0.94版本,HBase官方文檔已經提出了HBase上面實現二級索引的一種路徑:

  • 基於Coprocessor(0.92版本引入,達到支持類似傳統RDBMS的觸發器的行為)。
  • 開發自定義數據處理邏輯,采用數據“雙寫”策略,在有數據寫入同時同步到二級索引表。

5.2.1.1 開源方案:

業界比較知名的基於Coprocessor的開源方案:

  • 華為的hindex:基於0.94版本,但版本比較舊,github上幾年都沒更新過。
  • Apache Phoenix:功能圍繞SQL On HBase,支持和兼容多個hbase版本,二級索引只是其中一塊功能。二級索引的創建和管理直接有SQL語法支持,適用起來簡便,該項目目前社區活躍度和版本更新迭代情況都比較好。

Apache Phoenix在目前開源的方案中,是一個比較優的選擇,主打SQL On HBase,基於SQL能完成HBase的CRUD操作,支持JDBC協議。

5.2.1.2 Phoenix二級索引特點:

  • Covered Indexes(覆蓋索引):把關注的數據字段也附在索引表上,只需要通過索引表就能返回所要查詢的數據(列),所以索引的列必須包含所需查詢的列(SELECT的列和WHERE的列)。
  • Functional Indexes(函數索引):索引不局限於列,支持任意的表達式來創建索引。
  • Global Indexes(全局索引):適用於讀多寫少場景。通過維護全局索引表,所有的更新和寫操作都會引起索引的更新,寫入性能受到影響。在讀數據時,Phoenix SQL會基於索引字段,執行快速查詢。
  • Local Indexes(本地索引):適用於寫多讀少場景。在數據寫入時,索引數據和表數據都會存儲在本地。在數據讀取時,由於無法預先確定region的位置,所以在讀取數據時需要檢查每個region(以找到索引數據),會帶來一定性能(網絡)開銷。

5.2.2 非Coprocessor方案

選擇不基於Coprocessor開發,自行在外部構建和維護索引關系也是另外一種方式。

常見的是采用底層基於Apache Lucene的ElasticSearch(下面簡稱ES)或Apache Solr,來構建強大的索引能力、搜索能力,例如支持模糊查詢、全文檢索、組合查詢、排序等。

其實對於在外部自定義構建二級索引的方式,有自己的大數據團隊的公司一般都會針對自己的業務場景進行優化,自行構建ES/Solr的搜索集群。例如數說故事企業內部的百億級數據全量庫,就是基於ES構建海量索引和檢索能力的案例。主要有優化點包括:

  • 對企業的索引集群面向的業務場景和模式定制,對通用數據模型進行抽象和平台話復用
  • 需要針對多業務、多項目場景進行ES集群資源的合理划分和運維管理
  • 查詢需要針對多索引集群、跨集群查詢進行優化
  • 共用集群場景需要做好防護、監控、限流

下面顯示了數說基於ES做二級索引的兩種構建流程,包含:

  • 增量索引:日常持續接入的數據源,進行增量的索引更新
  • 全量索引:配套基於Spark/MR的批量索引創建/更新程序,用於初次或重建已有HBase庫表的索引。

數據查詢流程:

6. HBase表設計關注點

HBase表設計通常可以是寬表(wide table)模式,即一行包括很多列。同樣的信息也可以用高表(tall table)形式存儲,通常高表的性能比寬表要高出50%以上,所以推薦大家使用高表來完成表設計。表設計時,我們也應該要考慮HBase數據庫的一些特性:

  1. 在HBase表中是通過RowKey的字典序來進行數據排序的。
  2. 所有存儲在HBase表中的數據都是二進制的字節。
  3. 原子性只在行內保證,HBase不支持跨行事務。
  4. 列簇(Column Family)在表創建之前就要定義好
  5. 列簇中的列標識(Column Qualifier)可以在表創建完以后動態插入數據時添加。

總結

參考資料:

《HBase實戰》

https://www.cnblogs.com/parent-absent-son/p/10200202.html

https://blog.csdn.net/wangshuminjava/article/details/80575864

https://www.cnblogs.com/yuguoshuo/p/6265649.html

http://www.nosqlnotes.com/technotes/hbase/hbase-rowkey/

https://zhuanlan.zhihu.com/p/43972378


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM