最近使用HBase存儲數據比較多,看了一些資料哮笆,這里記錄一下筆記来颤。HBase是Google開源項目bigtable的開源實現汰扭,在其基礎上加入了更多的功能。本篇博客只是博主自己的學歷過程的總結福铅,如有錯誤還請不吝指出萝毛。
HBase簡介
從典型的RDMBMS的角度來看,HBase并不是一個列式存儲的數據庫滑黔,但是它利用了磁盤上的列式存儲格式笆包,這也是RDBMS和HBase最大的相似之處,因為HBase以列式存儲格式在磁盤上存儲數據略荡。HBase保存多個版本的數據庵佣,按照時間排序,每次都可以取到時間最新的數據版本撞芍。
HBase的數據是以列作為最小單位的秧了。多列組成一行跨扮,每行都需要指定一個行鍵作為key序无,一行的數據作為value。這樣就組成了一個完整的key-value的數據結構衡创,我們稱這個結構為cell帝嗡,也就是一個單元格。另外除了通過行鍵來查找數據之外璃氢,HBase還提供給列直接分類的能力哟玷,可以根據列的屬性指定該列屬于哪個列族,一般列族不宜過多一也。過個cell就組成了表巢寡。這些就是我們在使用HBase所需要知道的基本結構。
- 列(column)
- 行(row)
- 行鍵(row key)
- 單元格(cell)
- 列族(column family)
- 表 (table)
可以看一下如下的邏輯示意圖:
稍微解釋一下這張圖椰苟,我們存儲了三列數據抑月,分別是data、meta:mimetype舆蝴、meta:size谦絮。我們看到data這列的數據有多個版本,如果我們不指定時間長度的話洁仗,那么默認取得就是排序在最前面的數據层皱。
列式存儲
列式存儲數據庫以列為單位聚合數據,然后將列值順序地存入磁盤赠潦,這種存儲方法不同于行式存儲的傳統(tǒng)數據庫叫胖,行式存儲數據庫連續(xù)地存儲整行。圖1-1形象地展示了列式存儲和行式存儲的不同物理結構她奥。列式存儲的出現主要基于這樣一種假設:對于特定的查詢瓮增,不是所有的值都是必需的疲陕。尤其是在分析型數據庫里,這種情形很常見钉赁,因此需要選擇一種更為合適的存儲模式蹄殃。在這種新型的設計中,減少I/O只是眾多主要因素之一你踩,它還有其他的優(yōu)點:因為列的數據類型天生是相似的诅岩,即便邏輯上每一行之間有輕微的不同,但仍舊比按行存儲的結構聚集在一起的數據更利于壓縮带膜,因為大多數的壓縮算法只關注有限的壓縮窗口吩谦。像增量壓縮或前綴壓縮這類的專業(yè)算法,是基于列存儲的類型定制的膝藕,因而大幅度提高了壓縮比式廷。更好的壓縮比有利于在返回結果時降低帶寬的消耗。
列式存儲.png
關系型數據庫(RDBMS)
關系型數據庫已經應用非常廣泛了芭挽,也有比較成熟的解決方案滑废。目前我司的一些業(yè)務也已經到了分庫分表的階段了。
數據增長解決方案
讀寫分離
這種方案保留了一個主數據庫服務器袜爪,但是這個主數據庫服務器現在只服務于寫請求蠕趁,這樣做主要是考慮到網站的請求主要由用戶瀏覽產生,因此寫請求遠少于讀請求辛馆。
增加緩存
Memcached“陈現在可以將讀操作接入到高速的在內存中緩存數據的系統(tǒng),但是這種方案沒有辦法保持數據的一致性昙篙,因為用戶更新數據到數據庫腊状,而數據庫并不能主動更新緩存系統(tǒng)中的數據,所以需要盡可能快地同步緩存和數據庫視圖苔可,把更新緩存數據與更新數據庫數據的時間差最小化缴挖。
這種方案減少了讀的壓力,但是寫請求壓力的增加問題沒有得到解決硕蛹。一旦寫性能下降就需要垂直擴容醇疼,增加服務器更多的內核,更多的內存...同時法焰,如果采用了讀寫分離的方案的話秧荆,那么就要讓從服務器和主服務器一樣強,成本也會增加一到兩倍埃仪。
逆范式
隨著業(yè)務的增加乙濒,以前順利執(zhí)行的SQL JOIN會慢慢變慢, 或者干脆無法執(zhí)行,那么就要使用逆范式化的存儲颁股。適當的增加冗余減少join的次數
- 第一范式:如果數據表中的每個字段都是不可再分的最小數據單元么库,則滿足第一范式。
user用戶表
id | username | password |
---|
role角色表
id | name |
---|
- 第二范式:在第一范式的基礎上甘有,目標是確保表中的每列都和主鍵相關诉儒。如果一個關系滿足第一范式,并且除了主鍵之外的其他列亏掀,都依賴于該主鍵忱反,則滿足第二范式。
user用戶表
id | username | password | role_id |
---|
role角色表
id | name |
---|
- 第三范式:在第二范式的基礎上更進一步滤愕,目標是確保表中的列都和主鍵直接相關温算,而不是間接相關。
user用戶表
id | username | password |
---|
role角色表
id | name |
---|
role用戶和角色中間表
user_id | role_id |
---|
- 反范式化:反范式化指的是通過增加冗余或重復的數據來提高數據庫的讀性能间影。
user用戶表, role角色表
id | username | password | role_id | role_name |
---|
分庫分表
可以根據某個值hash取余的方式注竿,將數據進行分庫分表。運維管理起來很復雜魂贬,成本很高巩割。
RDBMS問題
不擅長大規(guī)模數據處理
RDBMS非常適合事務性操作,但不見長于超大規(guī)模的數據分析處理随橘,因為超大規(guī)模的查詢需要進行大范圍的數據記錄掃描或全量掃描喂分。分析型數據庫可以存儲數百或數千TB的數據,在一臺服務器上做查詢工作的響應時間机蔗,會遠遠超過用戶可接受的合理響應時間。垂直擴展服務器性能甘萧,即增加CPU核數和磁盤數目萝嘁,也并不能很好地解決該問題。
等待與死鎖
RDBMS的等待和死鎖的出現頻率扬卷,與事務和并發(fā)的增加并不是線性關系牙言,準確地說,與并發(fā)數目的平方以及事務規(guī)模的3次方甚至5次方相關怪得。分區(qū)通常是一個不切合實際的解決方案咱枉,因為它需要客戶端采用非常復雜的方式和較高的代價來維護分區(qū)信息。
非關系型數據庫Not-Only-SQL(NoSQL)
非關系型數據庫和關系型數據庫并沒有嚴格的界限徒恋, 有些菲關系型數據庫也實現了SQL語言的入口蚕断, 用于執(zhí)行一些關系型數據庫中常用的復雜查詢條件查詢。因此從查詢方式上并沒有嚴格的區(qū)分入挣。實際上兩者在底層上是有區(qū)別的亿乳,尤其是涉及到模式或者ACID事務特性時,因為這與實際的存儲架構是相關的。很多這一類的新系統(tǒng)首先做的事情是:拋棄一些限制因素以提升擴展性葛假。
一致性模型
- 嚴格一致性: 數據的變化是原子的障陶,一經改變即時生效,這是一致性的最高形式聊训。
- 順序一致性: 每個客戶端看到的數據依據他們操作執(zhí)行的順序而變化抱究。
- 因果一致性: 客戶端以因果順序觀察到數據的變化。
- 最終一致性: 在沒有更新數據的一段時間里带斑, 系統(tǒng)將通過廣播保證副本之間的數據一致性媳维。
- 弱一致性: 沒有做出保證的情況下,所有的更新都會通過廣播的形式傳遞遏暴,展現給不同客戶端的數據順序可能不一樣侄刽。
CAP定理
一個分布式系統(tǒng)只能同時實現一致性、可用性和分區(qū)容忍性(或者分區(qū)容錯性)中的兩個朋凉。也就是說在較大型的分布式系統(tǒng)中州丹,由于網絡分隔,一致性與可用性不能同時滿足杂彭。這意味著三個要素只能同時實現兩個墓毒,不可能三者兼具;放寬一致性的要求會提升系統(tǒng)的可用性...提升一致性意味著系統(tǒng)需要犧牲一定的可用性亲怠。
存儲
Region
HBase中擴展和負載均衡的基本單位稱之為Region所计,region的本質上是以行鍵排序的連續(xù)存儲的區(qū)間。如果region太大团秽,系統(tǒng)就會把他們動態(tài)拆分主胧,相反的,就把多個region合并习勤,以減少存儲文件的數量踪栋。一開始用戶只有一個region,如果數據超過限制之后就會自動拆分成兩個大致相等的region图毕。每一個region只能由一臺region服務器加載夷都,每一臺region服務器可以同時加載多個region。
HFile
數據存儲在存儲文件中予颤,成為HFile囤官,HFile中的存儲的是經過排序的鍵值映射結構。文件內部由連續(xù)的塊組成蛤虐,塊的索引信息存儲在文件的尾部党饮。當HFile打開并加載到內存中時,索引信息會優(yōu)先加載到內存中笆焰。
更新數據
存儲文件保存在Hadoop分布式文件系統(tǒng)中劫谅。每次更新數據時,都會將數據記錄在 提交日志 (commit log)中,在HBASE中這叫預寫日志(write-ahead log. WAL)捏检,然后才會將這些數據寫入內存中荞驴。一旦保存在內存中的數據的累計大小超過了一個給定的最大值,系統(tǒng)就會將這些數據移除內存作為HFile文件刷寫到磁盤中贯城。數據移除內存之后熊楼,系統(tǒng)會丟棄對應的提交日志,只保留未持久化到磁盤中的提交日志能犯。隨著memstore中的數據不斷的刷寫到磁盤中鲫骗,會產生很多的HFile文件,HBASE內部會將多個文件合并成一個大的文件踩晶。合并有兩種類型:minor合并和major壓縮合并执泰。
memstore中的數據已經是按照行鍵排序,持久化到磁盤中的HFile也是按照這個順序排序的渡蜻,所以不必執(zhí)行排序或者其他的特殊操作术吝。
minor合并 將多個小文件重寫為數量較少的大文件,減少存儲文件的數量茸苇,這個過程實際上是個多路歸并的過程排苍。因為HFile的每個文件都是經過歸類的,所以合并速度很快学密,只受到磁盤I/O性能的影響淘衙。
major合并 將一個region中的一個列族的若干個HFile重寫為一個新HFile,與minor合并相比腻暮,還有更獨特的功能:major合并能掃描所有鍵值對彤守,順序重寫全部數據,重寫數據的過程中會略過做了刪除標記的數據西壮。斷言刪除此時生效遗增。例如,對于那些超過版本限制的數據以及生存時間到期的數據款青,在重寫數據時就不再寫入磁盤了。(這也就說說明了在實際使用過程中霍狰,如果是剛剛刪除數據抡草,然后再scan的時候也是很慢的原因:數據并沒有立即刪除)
查詢數據
每個HFile文件都有一個塊索引,通過一個磁盤查找就可以實現查詢蔗坯。首先康震,在內存的塊索引中進行二分查找,確定可能包含給定鍵的塊宾濒,然后讀取磁盤塊找到實際要找的鍵腿短。讀回的數據是兩部分數據合并的結果:一部分是memstore中還沒有寫入磁盤的數據,另一部分是磁盤上的存儲文件。
數據檢索時用不到WAL橘忱,只有服務器內存中的數據在服務器崩潰前沒有寫入到磁盤赴魁,而后進行恢復數據的時候才會用到WAL。
刪除數據
以為存儲文件是不可被改變的钝诚,所以無法通過移除某個鍵值對來簡單的刪除颖御。所以是做個刪除標記,客戶端在檢索的時候讀不到實際的值凝颇。具體刪除操作可以參考major合并過程潘拱。
客戶端API
HBase的主要客戶端接口是由org.apache.hadoop.hbase.client中包含的HTable類提供的。所有的修改數據的操作都保證了行級別的 原子性 拧略,也就是說其他客戶端或線程對同一行的讀寫操作都不會影響這行數據的原子性:要么讀到最新的修改芦岂,要么等待系統(tǒng)允許寫入該行修改。目前還不支持跨行事務和跨表事務垫蛆。
創(chuàng)建HTable實例是有代價的禽最。 每個實例都要掃描.META.表,以檢查該表是否存在月褥、是否可用弛随,此外還要執(zhí)行一些其他操作,這些檢查和操作導致實例調用非常的耗時宁赤。因此舀透,需要只創(chuàng)建一次實例,而且每個線程創(chuàng)建一個實例决左,然后在生命周期復用這個實例愕够。如果需要創(chuàng)建多個實例的話,可以使用 HTablePool 類佛猛。
通常情況下惑芭,客戶端讀操作不會受到其他修改數據的客戶端的影響,因為他們之間的沖突可以忽略不計继找,但是遂跟,當許多客戶端需要同時修改同一行數據的時候就會產生問題。所以婴渡,用戶應當盡量避免使用批量處理更新來減少單獨操作同一行數據的次數幻锁。
CURD操作
SCAN操作
過濾器操作
比較運算符 | 描述 |
---|---|
LESS | 匹配小于設定值的值 |
LESS_OR_EQUAL | 匹配小于或等于設定值的值 |
EQUAL | 匹配等于設定值的值 |
NOT_EQUAL | 匹配與設定值不相等的值 |
GREATER_OR_EQUAL | 匹配大于或等于設定值的值 |
GREATER | 匹配大于設定值的值 |
NO_OP | 排除一切值 |
比較器 | 描述 |
---|---|
BinaryComparator | 使用Bytes.compareTo()比較當前值與閾值 |
BinaryPrefixComparator | 與上面的相似,使用Bytes.compareTo()進行行匹配边臼,但是是從左端開始進行前綴匹配 |
NullComparator | 不做匹配哄尔,只判斷當前值是不是null |
BitComparator | 通過BitwiseOp類提供的an位與(AND)、或(OR)柠并、異或(XOR)操作執(zhí)行位級運算 |
RegexStringComparator | 根據一個正則表達式岭接,在實例化這個比較器的時候去匹配表中的數據 |
SubStringComparator | 把閾值和表中數據當做string實例富拗,同時通過contains()操作匹配字符串 |
具體比較器 | 描述 |
---|---|
RowFilter | 行過濾器基于行鍵過濾 |
FamilyFilter | 比較列族返回結果 |
QualifierFilter | 選擇不同的列 |
ValueFilter | 篩選某個特定的單元格 |
DependentColumnFilter | 參考列過濾器,允許用戶指定一個參考列或者引用列鸣戴,并使用參考列控制其他列的過濾 |
專用過濾器 | 描述 |
---|---|
SingleColumnValueFilter | 單列過濾器啃沪,用一列的值決定是否一行數據被過濾 |
SingleColumnValueExcludeFilter | 單列排除過濾器,參考列不被包括到結果中 |
PrefixFilter | 前綴過濾器葵擎,對結果進行分頁 |
PageFilter | 分頁過濾器谅阿,篩選某個特定的單元格 |
KeyOnlyFilter | 行鍵過濾器,只返回KeyValue的鍵酬滤,而不返回實際數據 |
FirstKeyOnlyFilter | 首次行鍵過濾器签餐,找到每行中最早創(chuàng)建的列 |
InclusiveStopFilter | 包含結束的過濾器,將結束行包括到結果中 |
TimeStampsFilter | 時間戳過濾器盯串,找到與之間戳精確匹配的列版本 |
ColumnCountGetFilter | 列計數過濾器氯檐, 限制每行最多取回多少列 |
ColumnPaginationFilter | 列分頁過濾器,對一行所有的列進行分頁 |
ColumnPrefixFilter | 列前綴過濾器体捏,通過對列名的前綴進行過濾 |
RandomRowFilter | 隨機行過濾器冠摄,0.0~1.0 |
架構
如上圖所示,HBase是Hadoop生態(tài)圈的實時几缭,分布式河泳,高維數據庫。有一下特點:
- 高可靠性年栓、高性能拆挥、面向列、可伸縮某抓、 實時讀寫的分布式數據庫
- 利用Hadoop HDFS作為其文件存儲系統(tǒng),利用Hadoop MapReduce來處理 HBase中的海量數據,利用Zookeeper作為其分布式協同服務
- 主要用來存儲非結構化和半結構化的松散數據(列存NoSQL數據庫)
通過這個整體的框架圖纸兔,我們可以看到,HBase數據庫主要由三個部分組成否副,一部分是Zookeeper作為分布式協調系統(tǒng)汉矿,一部分是HBase實現的合并排序部分,還有一部分就是底層的數據存儲备禀,可以是HDFS文件系統(tǒng)洲拇,也可以是操作系統(tǒng)的文件系統(tǒng)。不過一般會使用HDFS文件系統(tǒng)曲尸,HDFS提供了數據冗余的功能呻待,能有效防止數據的丟失。
Region查找過程
結合整體的架構圖队腐,我們可以看出來,首先是要查找到對應的region色冀,從圖中可以看到啃匿,client先請求zookeeper查詢到Root表的地址,Root表里面存儲的是META表的地址铺浇,通過META表能夠查詢到表和region的對相應關系为严,就可以定位到表在哪個region上敛熬。然后是以下的步驟:
- HBase Client寫入數據,存入MemStore第股,一直到MemStore滿 应民,Flush成一個StoreFile;
- StoreFile直至增長到一定閾值 夕吻,觸發(fā)Compact合并操作 诲锹,多個StoreFile合并成一個StoreFile,同時進行版本合并和數據刪除涉馅,當StoreFiles Compact后归园,逐步形成越來越大的StoreFile。
- 單個StoreFile大小超過一定閾值后稚矿,觸發(fā)Split操作庸诱,把當前Region Split成2個Region,Region會下線晤揣,新Split出的2個孩子Region會被HMaster分配到相應的HRegionServer 上桥爽,使得原先1個Region的壓力得以分流到2個Region上。
- 由此過程可知昧识,HBase只是增加數據钠四,有所得更新和刪除操作,都是在Compact階段做的滞诺,所以形导,用戶寫操作只需要進入到內存即可立即返回,從而保證I/O高性能习霹。
B+ Tree vs LSM Tree
B+樹存儲引擎是B+樹的持久化實現朵耕,不僅支持單條記錄的增、刪淋叶、讀阎曹、改操作,還支持順序掃描(B+樹的葉子節(jié)點之間的指針)煞檩,對應的存儲系統(tǒng)就是關系數據庫(Mysql等)处嫌。因為隨著insert操作,為了維護B+樹結構斟湃,節(jié)點分裂熏迹。讀磁盤的隨機讀寫概率會變大,性能會逐漸減弱凝赛。
LSM樹(Log-Structured MergeTree)存儲引擎和B+樹存儲引擎一樣注暗,同樣支持增坛缕、刪、讀捆昏、改赚楚、順序掃描操作。而且通過批量存儲技術規(guī)避磁盤隨機寫入問題骗卜。
當然凡事有利有弊宠页,LSM樹和B+樹相比,LSM樹犧牲了部分讀性能寇仓,用來大幅提高寫性能举户。LSM樹存儲引擎的代表數據庫就是Hbase。LSM樹核心思想的核心就是放棄部分讀能力焚刺,換取寫入的最大化能力敛摘。LSM Tree ,這個概念就是結構化合并樹的意思乳愉,它的核心思路其實非常簡單兄淫,就是假定內存足夠大,因此不需要每次有數據更新就必須將數據寫入到磁盤中蔓姚,而可以先將最新的數據駐留在內存中捕虽,等到積累到足夠多之后,再使用歸并排序的方式將內存內的數據合并追加到磁盤隊尾(因為所有待排序的樹都是有序的坡脐,可以通過合并排序的方式快速合并到一起)泄私。
日志結構的合并樹(LSM-tree)是一種基于硬盤的數據結構,與B+tree相比备闲,能顯著地減少硬盤磁盤臂的開銷晌端,并能在較長的時間提供對文件的高速插入(刪除)。然而LSM-tree在某些情況下恬砂,特別是在查詢需要快速響應時性能不佳咧纠。通常LSM-tree適用于索引插入比檢索更頻繁的應用系統(tǒng)。
兩種優(yōu)化技術:
- Bloom filter: 就是個帶隨機概率的bitmap,可以快速的告訴你泻骤,某一個小的有序結構里有沒有指定的那個數據的漆羔。于是就可以不用二分查找,而只需簡單的計算幾次就能知道數據是否在某個小集合里啦狱掂。效率得到了提升演痒,但付出的是空間代價。
- compact:小樹合并為大樹:因為小樹性能有問題趋惨,所以要有個進程不斷地將小樹合并到大樹上鸟顺,這樣大部分的老數據查詢也可以直接使用log2N的方式找到,不需要再進行(N/m)*log2n的查詢了
B+樹受限于磁盤的尋到速度器虾,每次查找需要訪問磁盤log(N)次诊沪,而LSM樹利用存儲的連續(xù)傳輸能力养筒,并以一定的速率排序和合并文件,需要執(zhí)行l(wèi)og(updates)次的操作端姚。10 MB/s的傳輸帶寬、10ms的磁盤尋到時間挤悉、沒條目100字節(jié)(100億條目)渐裸、每頁10kB(10億頁),更新1%條目所需的時間装悲,隨機B-tree更新需要1000天昏鹃,批量b-tree需要100天,使用排序和合并需要1天
布隆過濾器
布隆過濾器(Bloom Filter)的核心實現是一個超大的位數組和幾個哈希函數诀诊。假設位數組的長度為m洞渤,哈希函數的個數為k
以上圖為例,具體的操作流程:假設集合里面有3個元素{x, y, z}属瓣,哈希函數的個數為3载迄。首先將位數組進行初始化,將里面每個位都設置位0抡蛙。對于集合里面的每一個元素护昧,將元素依次通過3個哈希函數進行映射,每次映射都會產生一個哈希值粗截,這個值對應位數組上面的一個點惋耙,然后將位數組對應的位置標記為1。查詢W元素是否存在集合中的時候熊昌,同樣的方法將W通過哈希映射到位數組上的3個點绽榛。如果3個點的其中有一個點不為1,則可以判斷該元素一定不存在集合中婿屹。反之灭美,如果3個點都為1,則該元素可能存在集合中选泻。注意:此處不能判斷該元素是否一定存在集合中冲粤,可能存在一定的誤判率∫趁校可以從圖中可以看到:假設某個元素通過映射對應下標為4梯捕,5,6這3個點窝撵。雖然這3個點都為1傀顾,但是很明顯這3個點是不同元素經過哈希得到的位置,因此這種情況說明元素雖然不在集合中碌奉,也可能對應的都是1短曾,這是誤判率存在的原因寒砖。
預寫日志
寫在內存中的數據在意外情況下很容易丟失。一個比較常見的解決方案就是預寫日志嫉拐,每次更新都會寫入日志哩都,只有寫入成功才會通知客戶端操作成功。類似于MySQL的binarylog婉徘,WAL存儲了對數據的所有更改漠嵌。這在主存儲器出現意外的情況下非常重要。如果服務器崩潰盖呼,它可以有效地回放日志儒鹿,使得服務器恢復到服務器崩潰以前。這也就意味著如果將記錄寫入到WAL失敗時几晤,整個操作也會被認為是失敗的约炎。處理過程如下:首先客戶端啟動一個操作來修改數據。例如蟹瘾,可以對put()圾浅、delete()和increment()進行調用。每一個修改都封裝到一個KeyValue對象實例中热芹,并通過RPC調用發(fā)送出去贱傀。這些調用(理想情況下)成批地發(fā)送給含有匹配region的HRegionServer。一旦KeyValue實例到達伊脓,它們會被發(fā)送到管理相應行的HRegion實例府寒。數據被寫入到WAL,然后被放入到實際擁有記錄的存儲文件的MemStore中报腔。實質上株搔,這就是HBase大體的寫路徑。如果服務終端纯蛾,重啟region的時候就會檢查沒有flush到磁盤的數據纤房。