從MySQL到HBase:數(shù)據(jù)存儲方案轉(zhuǎn)型演進
本文大致會從以下幾個方面入手,談?wù)劰P者對數(shù)據(jù)存儲方案選型的看法:
- 從MySQL到HBase集群化方案的演化
 - MySQL與HBase的性能取舍
 - 不同方案的優(yōu)化思路
 - 總結(jié)
 
一、集群化方案
1、MySQL應(yīng)用的演化
MySQL與HBase說到最核心的點,是一種數(shù)據(jù)存儲方案。方案本身沒有對錯、沒有好壞,只有合適與否。相信多數(shù)公司都與MySQL有著不解之緣,部分學(xué)校的課程甚至直接以SQL語言作為數(shù)據(jù)庫講解。我想借自身經(jīng)歷,先來談?wù)凪ySQL應(yīng)用的演化。
只有MySQL
筆者之前曾在一家O2O創(chuàng)業(yè)公司工作,公司所有數(shù)據(jù)都存儲在同一個MySQL里,而且沒有任何主備方案。相信這是很多初創(chuàng)公司會用到的一個典型解決辦法,當時這臺MySQL為用戶、訂單、物流服務(wù),同時也為線下分析服務(wù)。
單實例的問題:
- 一旦MySQL掛了,服務(wù)全部停止;
 - 一旦MySQL的磁盤壞了,公司的所有服務(wù)都沒有了 (一般會定時備份數(shù)據(jù)文件)。
 
主從方案
隨著業(yè)務(wù)增加,單個DB是無法承載這么多請求的。于是就有了主從復(fù)制、讀寫分離的解決方案。
master只負責寫請,slave同步master用來服務(wù)讀請求:
- 為了擴展讀能力可以增加多個slave;
 - 允許slave同步有一定的延遲;
 - 一致性要求嚴格的,可以指定讀主庫。
 
主從功能的問題:
- 需要增加管理Proxy層,分配寫請求、讀請求;
 - 節(jié)點故障:其它節(jié)點應(yīng)該快速接管故障節(jié)點的功能。
 
垂直拆分
業(yè)務(wù)繼續(xù)增長,master甚至無法承載所有的寫請求,數(shù)據(jù)庫需要按業(yè)務(wù)拆分。
垂直拆分的問題:
- 線下分析,需要在業(yè)務(wù)代碼里join各個表。因為拆成多個庫,已經(jīng)無法join了。
 - 不容易做數(shù)據(jù)庫的事務(wù)性,用戶余額減少與下單成功的情況下無法使用MySQL的事務(wù)功能。
 
水平拆分
業(yè)務(wù)繼續(xù)增長,訂單表有大量的并發(fā)寫入,而且已經(jīng)有了幾千萬行數(shù)據(jù)。
- 單個庫無法承載大量的并發(fā)寫入;
 - 上千萬行的大表,數(shù)據(jù)寫入可能需要調(diào)整一棵巨大的B+樹;
 - 上千萬行,B+樹過深,讀寫需要更多的磁盤IO;
 - 很多老數(shù)據(jù)訪問較少,B+樹上層緩存的部分信息無用;
 - ……
 
參考:大眾點評訂單系統(tǒng)分庫分表實踐
https://zhuanlan.zhihu.com/p/24036067
水平分庫/分表帶來的問題:
- 維護map方案;
 - 輔助索引只能局部有效;
 - 由于分庫,無法使用join等函數(shù);由于分表count、order、group等聚合函數(shù)也無法做了;
 - 擴容:需要再次水平拆分的:修改map,遷移數(shù)據(jù)……
 
2、MySQL的問題
MySQL的主要瓶頸,單機單進程。CPU有限、內(nèi)存/磁盤功能、連接數(shù)有限、網(wǎng)卡吞吐有限……
- 集群的限制點:
 - 關(guān)系型數(shù)據(jù)庫,縱向的外鍵相互join;
 - 范式參考鏈接:https://zhuanlan.zhihu.com/p/20028672
 - 數(shù)據(jù)庫事務(wù)性,基于單機的鎖機制,無法擴展到集群中使用;
 - 全局有序列性基于B+樹,數(shù)據(jù)有序聚合存儲,集群化后無法保證;
 - 數(shù)據(jù)本地存儲,擴容需要遷移數(shù)據(jù)。
 
集群的方案:
- 放棄部分功能,輔助索引檢索、join、全局事務(wù)性、聚合函數(shù)等;
 - 水平拆分:存儲KV化,用機械的map思路實現(xiàn)集群;
 - 擴容方案:手動導(dǎo)數(shù)據(jù),開發(fā)數(shù)據(jù)遷移腳本;
 - 事務(wù)性:兩階段事務(wù)、paxos、單庫事務(wù)……
 - 備份容災(zāi):從節(jié)點同步主節(jié)點,但有一定的數(shù)據(jù)延遲;
 - 服務(wù)穩(wěn)定性:主節(jié)點掛了,Proxy會將從節(jié)點升級為主節(jié)點;從節(jié)點掛了會被其它從節(jié)點替換。
 
3、HBase集群化解決方案
水平拆分:
- region:拆分后的子表;
 - Region Server:管理這些數(shù)據(jù)的server,相當于一個MySQL實例;
 - .META.表存儲拆分信息map<row, server>。
 
單個region過大,RegionServer會將region均分為兩個(自動、手工)。然后更新.META.表。
擴容方案:
RegionServer向HMaster匯報狀態(tài)。HMaster為RegionServer負載均衡,調(diào)整其負責的region 。
增/刪RegionServer后,會為重新調(diào)整region的分配方式。
服務(wù)穩(wěn)定性:
RegionServer只是計算單元,掛掉后Hmaster可以隨便再找一個節(jié)點代替壞節(jié)點服務(wù)。
事務(wù)性:
HBase只保證行級事務(wù),單行數(shù)據(jù)肯定存在同一臺機器(單機事務(wù)很好做)。
備份容災(zāi):
- 數(shù)據(jù)使用HDFS存儲,多復(fù)本,任何一個復(fù)本掛掉都不影響功能;
 - RegionServer只是計算單元,掛掉后不影響服務(wù)。
 
二、性能取舍
1、數(shù)據(jù)請求流程
HBase:
- Client會通過Zookeeper定位到 .META. 表;
 - 根據(jù) .META. 查找需要服務(wù)的RegionServer,連接RegionServer進行讀寫;
 - Client會緩存 .META. 表信息,下次可以直接連到RegionServer 。
 
MySQL:
- Client通過Proxy,查找需要連接的MySQL實例,連接并進行讀寫。
 
Rquest的路由流程,MySQL與HBase基本一致,那么RegionServer與MySQL的性能差異如何呢?
2、Hbase寫得快
新增
為什么MySQL建議自增主鍵?(MySQL隨機插入的代價)
- 主鍵索引是有序的B+樹結(jié)構(gòu),新增條目的ID肯定是***的,新增給B+結(jié)構(gòu)帶來的調(diào)整最?。?/li>
 - 主鍵索引是聚簇的:新增條目,ID是***的。其data追加在上一次插入的后面,磁盤更容易順序?qū)憽?/li>
 
輔助索引,插入基本是隨機的:
- 插入條目,可能會引起B(yǎng)+樹結(jié)構(gòu)很大的調(diào)整。
 
HBase可以隨機插入:
- HBase的所有插入只是寫入內(nèi)存memstore,只保證內(nèi)存數(shù)據(jù)的有序即可(很快、很容易);
 - 為防止數(shù)據(jù)丟失寫入memstore前,先寫入wal(可以關(guān)閉,速度更快);
 - HBase沒有輔助索引需要維護;
 - memstore寫滿了,申請一塊新的內(nèi)存,舊的memstore被后臺線程刷盤,存入HFile。
 
修改
MySQL數(shù)據(jù)變化引起存儲變動:
- 數(shù)據(jù)塊大小變化:磁盤空間不足,可能需要調(diào)整磁盤存儲結(jié)構(gòu),引起大量的磁盤隨機讀寫;
 - 輔助索引發(fā)生變化:可能需要重新調(diào)整輔助索引B+樹。
 
HBase直接將變化寫入到memstore,沒有其它開銷。
刪除
MySQL數(shù)據(jù)刪除:
- 直接操作B+樹的節(jié)點,肯定需要刷新磁盤;
 - 如果引起樹結(jié)構(gòu)變化,甚至可能需要多次刷新磁盤。
 
HBase只是在memstore記錄刪除標記,沒有其它開銷。
3、結(jié)論
HBase寫入內(nèi)存+后臺刷盤(最多是WAL,磁盤順序?qū)懀?;MySQL需要維護B+樹,大量的磁盤隨機讀寫。
MySQL要求盡量追加寫(自增 ID),速度較慢;HBase可以隨機插入,速度很快。
MySQL讀得快
MySQL數(shù)據(jù)是本地存儲的,HBase是基于HDFS,有可能數(shù)據(jù)不在本地。
B+ 樹天然的全局有序
- 根據(jù)主鍵查詢,可以快速定位到數(shù)據(jù)所在磁盤塊,只需要極少的磁盤IO即可拿到數(shù)據(jù):通過緩存高層節(jié)點,主健查詢只需要一次磁盤IO就可拿到數(shù)據(jù);MySQL單表行數(shù)一般建議不會超過2千萬,千萬行以下的大表,B+樹只需2~3層即可;
 - 輔助索引,提供快速定位能力:輔助索引B+樹,可以快速定位到最終所需的主鍵ID,根據(jù)主鍵ID可以快速拿到所需信息。
 
HBase只有局部信息,沒有輔助索引
- 查詢會優(yōu)先查找memstore,如果沒有會查找Hfile(存儲結(jié)構(gòu)類似B+樹)。如果***個Hfile中沒有所需的信息,則需要去第二、第三個Hfile中查詢;如果查詢的數(shù)據(jù)恰好在memstore,***個Hfile,HBase會優(yōu)于MySQL;平均下來,HBase讀性能一般。減少Hfile數(shù)據(jù)以提速,小的HFile合并成大的HFile文件。這種存儲結(jié)構(gòu)叫LSM樹(Log-structured merge-tree);
 - 如果需要檢索特定的列,可能需要遍歷所有Hfile,成本巨高。
 
MySQL成也B+,敗也B+;HBase成也LSM,敗也LSM。
4、附錄
B+ 樹
查詢“值為25”的節(jié)點,只需要2次定位即可。
LSM樹
查詢“值為25”的節(jié)點,只需要4次定位即可。
三、優(yōu)化思路
1、HBase優(yōu)化點 (主要是讀)
異步化
- 后臺線程將memstore寫入Hfile;
 - 后臺線程完成Hfile合并;
 - wal異步寫入(數(shù)據(jù)有丟失的風險)。
 
數(shù)據(jù)就近
- blockcache,緩存常用數(shù)據(jù)塊:讀請求先到memstore中查數(shù)據(jù),查不到就到blockcache中查,再查不到就會到磁盤上讀,把最近讀的信息放入blockcache,基于LRU淘汰,可以減少磁盤讀寫,提高性能;
 - 本地化,如果Region Server恰好是HDFS的data node,Hfile會將其中一個副本放在本地;
 - 就近原則,如果數(shù)據(jù)沒在本地,Region Server會取最近的data node中數(shù)據(jù)。
 
快速檢索
基于bloomfilter過濾:
- 正常檢索,RegionServer會遍歷所有Hfile查詢所需數(shù)據(jù)。其中,需要遍歷Hfile的索引塊才能判斷Hfile中是否有所需數(shù)據(jù);
 - BloomFiler存儲HFile的摘要,可以通過極少磁盤IO,快速判斷當前HFile是否有所需數(shù)據(jù):
 
行緩存:快速判斷Hflie是否有所需要的行,粒度較粗,存儲占用少,磁盤IO少,數(shù)據(jù)較快;
列緩存:快速判斷Hfile是否有所需的列,粒度較細,但存儲占用較多。
基于timestamp過濾:
- HFile基于日志追加、合并,維護了版本信息;
 - 當查詢1小時內(nèi)提交的信息時,可以跳過只包含1小時前數(shù)據(jù)的文件。
 
HFile存儲結(jié)構(gòu):
HFile存儲格式
參考鏈接:
https://link.zhihu.com/?target=https%3A//blog.csdn.net/yangbutao/article/details/8394149
- Trailer存儲整個Hfile的定位信息;
 - DataIndex存儲Data塊的索引信息:Data存儲為一組磁盤塊,存儲數(shù)據(jù)信息;DataIndex功能類似于B+樹的非葉子節(jié)點;Data每個磁盤塊中的數(shù)據(jù)按key有序,加載到內(nèi)存后可以用二分查找定位;Key按行 + 列族 + 列 + 時間戳生成,按字典序排序(***查詢方式:最左匹配);
 - MetaIndex存儲Meta的索引信息,Meta存儲一系列元信息;MetaIndex功能類似于B+樹的非葉子節(jié)點;Meta存儲bloomfiler等輔助信息。
 
2、MySQL優(yōu)化點(主要是寫)
查詢緩存
將SQL執(zhí)行結(jié)果放入緩存。
緩存B+高層節(jié)點
一千萬行的大表,一般只需要一棵3層的B+樹,其中索引節(jié)點 (非葉子節(jié)點) 的大小約20MB。完全可以考慮將大部葉子節(jié)點緩存,基于主鍵查詢只需要一次IO。
減少隨機寫——緩沖:延遲寫/批量寫
上節(jié)提到,B+樹通過自增主鍵大量減少隨機插入。由于輔助索引的存在,插入、修改、刪除操作,輔助索引可能引起大量的隨機IO。
插入緩沖:只是將被插入數(shù)據(jù)寫入insert buffer;定期將其merge到B+樹;
修改緩沖:類似于insert buffer的思路。
減少隨機讀——MRR
- SELECT * FROM t WHERE key_part1 >= 1000 AND key_part1 < 2000 AND key_part2 = 10000;
 - # 普通操作分解:
 - key_part1= 1000, key_part2=1000, id = 1
 - select * from t where id=1
 - key_part1= 1001, key_part2=1000, id = 10
 - select * from t where id=10
 - ...
 - # MRR 操作分解:
 - SELECT * FROM t WHERE key_part1 >= 1000 AND key_part1 < 2000 AND key_part2 = 10000;
 - key_part1= 1000, key_part2=1000, id = 1
 - buffer.append(1)
 - key_part1= 1000, key_part2=1000, id = 10
 - buffer.append(10)
 - ...
 - sort(buffer)
 - select * from t where id in (buffer)
 
索引下推
- MySQL的server處理完索引后,會將索引其它部分傳給引擎層;
 - 引擎層根據(jù)過濾條件過濾掉無用的行,減少數(shù)據(jù)量,進而優(yōu)化server的性能。
 
3、集群化數(shù)據(jù)庫的輔助索引
InnoDB的輔助索引
- B+樹全局有序,葉子節(jié)點存儲的是主鍵。基于輔助索引定位主鍵,再用主鍵定位數(shù)據(jù)。MySQL水平切分后,沒辦法跨庫維持建立全局有序索引:
 - 單實例維護索引,喪失了全局有序性;
 
再做一個基于新索引分庫方案,喪失了輔助索引維護的事務(wù)性。
HBase相同問題
- 仿照InnoDB實現(xiàn)輔助索引,輔助索引可以做成單獨的key,其value是被索引行的key;
 - 可以做到全局信息的維護,但沒法保證事務(wù)性。
 
4、HBase異步合并帶來的好處
- TTL:基于后臺合并,TTL很容易做;
 - 數(shù)據(jù)多版本支持:基于“追加”,HBase天然的可以支持多版本;
 - 版本數(shù)量:基于后臺合并,可以將太舊版本干掉。
 
四、總結(jié)
不知道BigTable的前輩們是出于什么思路,本人冒昧揣測一下,多少應(yīng)該是受到SQL數(shù)據(jù)庫的影響。個人感覺,這些或許就是一脈相承的演進,至少用這種思路學(xué)習(xí)不顯突兀。HBase不是憑空而來,也絕對不是解決所有問題的***靈丹。
最直接的存儲思路肯定是“文件”,當“文件”不能滿足需求,就有了數(shù)據(jù)的組織方式,進而演進到關(guān)系數(shù)據(jù)庫如MySQL。
MySQL以其“單機”很好地解決了ACID問題,但是,性能再好的“單機”勢必演變成“單點”瓶頸,進而,分布式思路成為必然。
最簡單的是擴展讀,“***”掛slave;進而拆分寫節(jié)點,多點寫入:垂直拆庫、水平拆庫。一旦選擇分布式,就涉及如何主從一致、如何發(fā)現(xiàn)節(jié)點、如何運維、ACID的如何保證等問題。
進而就是一系列分布式方案,而HBase就是其中一種解決思路——只讀主庫保證一致,水平拆分、zk等機制保證自動運維、單行級ACID。至于性能方面,由于存儲思路不同,MySQL與HBase分別取舍了不同的讀寫性能。繼而,就衍生出了如何針對性進行優(yōu)化。
以這種思路,HBase不是憑空出現(xiàn)。以個人淺顯的目光所及,沒有***的架構(gòu),也沒有絕對厲害的設(shè)計。固然SQL類數(shù)據(jù)庫有其獨領(lǐng)風騷的場景,NoSQL數(shù)據(jù)庫自然也有縱橫馳騁的疆域,無論是哪種架構(gòu),都有自己鞭長莫及的角落。
所以,應(yīng)該說任何一種方案都沒有***,只有合適。而所有的合適都是演變而來,萬變不離其宗:更好的解決問題。
























 
 
 















 
 
 
 