本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,2萬元獎池等你挑戰!」html
今天分享的內容是關於HBASE相關設計的實戰經驗,記錄學到的東西。主要是線上(集羣規模10~20臺,每秒讀寫數據量在幾十萬條記錄的量級)出現了bug, hbase暫時不提供服務了,即整理了該篇文章。前端
HBase是一個分佈式的、面向列的開源數據庫,該技術來源於 Fay Chang 所撰寫的Google論文「Bigtable:一個結構化數據的分佈式存儲系統」。就像Bigtable利用了Google文件系統(File System)所提供的分佈式數據存儲同樣,HBase在Hadoop之上提供了相似於Bigtable的能力。git
HBase是Apache的Hadoop項目的子項目。HBase不一樣於通常的關係數據庫,它是一個適合於非結構化數據存儲的數據庫。另外一個不一樣的是HBase基於列的而不是基於行的模式。github
這就是一張表,咱們能夠根據行鍵(roykey)獲取一列族數據或多列族數據,每個列族下面有不限量的列,每個列上能夠存儲數據,每個數據都是有版本的,能夠經過時間戳來區別。因此咱們在一張表中,知道行鍵,列族,列,版本時間戳能夠肯定一個惟一的值面試
任何一張表,他的rowkey是全局有序的,因爲對物理存儲上的考慮,咱們把它放在多個機器上,咱們按照大小或者其餘策略,分爲多個region。每個region負責表示一份數據,region會在物理機器上,保證是一個均衡的狀態。算法
首先它也是一套標準的存儲架構。他的Hmaster主要負責簡單的協調服務,好比region的轉移,均衡,以及錯誤的恢復,實際上他並不參與查詢,真正的查詢是發生在region server。region server事負責存儲的,剛纔咱們說過,每個表會分爲幾個region。而後存儲在region server。sql
這裏最重要的部分是hlog。爲了保證數據一致性,首先會寫一份日誌文件,這是數據庫系統裏面以來的一種特性,建立了日誌之後,咱們才能寫入成功。咱們剛纔提到HBase裏面有不少column-family列族,沒個列族在一個region裏對應一個store,store分別包含storefile和menstore。數據庫
爲了後續對HBase能夠優化,咱們首先考慮把文件寫入menstore裏面,隨着menstore裏面的數據滿了以後,會把數據分發到磁盤裏,而後storefile和memstore總體的話,依賴一個數據模型,叫作lmstree。apache
而後,數據是採用append方式寫入的,不管是插入,修改,刪除。實際上都是不斷的append的。好比說你的更新,刪除的操做方式,都是以打標記方式寫入,因此它避免了磁盤的隨機io,提升了寫入性能,固然的話,它的底層的話是創建在hdfs之上。後端
HBase 使用 Zookeeper 作分佈式管理服務,來維護集羣中全部服務的狀態。Zookeeper 維護了哪些 servers 是健康可用的,而且在 server 故障時作出通知。Zookeeper 使用一致性協議來保證分佈式狀態的一致性。注意這須要三臺或者五臺機器來作一致性協議。
zk的分佈式協議仍是必需要掌握的,畢竟大數據中的香餑餑flink,hbase這些都是是用zk來作分佈式協議的。
Region Server 運行在 HDFS DataNode 上,由如下組件組成:
首先是將數據寫入到 WAL 中(WAL 是在文件尾部追加,性能高)
加入到 MemStore 即寫緩存, 服務端就能夠向客戶端返回 ack 表示寫數據完成
MemStore 中累積了足夠多
整個有序數據集就會被寫入一個新的 HFile 文件到 HDFS 上(順序寫)
這也是爲何 HBase 要限制 Column Family 數量的一個緣由(列族不能太多)
維護一個最大序列號,這樣就知道哪些數據被持久化了
HFile 使用多層索引來查詢數據而沒必要讀取整個文件,這種多層索引相似於一個 B+ tree:
trailer 指向原信息數據塊,它是在數據持久化爲 HFile 時被寫在 HFile 文件尾部。trailer 還包含例如布隆過濾器和時間範圍等信息。
布隆過濾器用來跳過那些不包含指定 rowkey 的文件,時間範圍信息則是根據時間來過濾,跳過那些不在請求的時間範圍以內的文件。
剛纔討論的索引,在 HFile 被打開時會被載入內存,這樣數據查詢只要一次硬盤查詢。
上一篇說了,當咱們讀取數據時,首先是定位,從 Meta table 獲取 rowkey 屬於哪一個 Region Server 管理,而Region Server又有讀緩存、寫緩存、HFILE
所以咱們讀一個數據,它有可能在讀緩存(LRU),也有可能剛剛纔寫入還在寫緩存,或者都不在,則HBase 會使用 Block Cache 中的索引和布隆過濾器來加載對應的 HFile 到內存,所以數據可能來自讀緩存、scanner 讀取寫緩存和HFILE,這就叫左HBASE的讀合併
以前說過寫緩存可能存在多個HFILE,所以一個讀請求可能會讀多個文件,影響性能,這也被稱爲讀放大(read amplification)。
簡單的說就是,HBase 會自動合併一些小的 HFile,重寫成少許更大的 HFiles
它使用歸併排序算法,將小文件合併成大文件,有效減小 HFile 的數量
這個過程被稱爲 寫合併(minor compaction)
1.它合併重寫每一個 列族(Column Family) 下的全部的 HFiles
2.在這個過程當中,被刪除的和過時的 cell 會被真正從物理上刪除,這能提升讀的性能
3.可是由於 major compaction 會重寫全部的 HFile,會產生大量的硬盤 I/O 和網絡開銷。這被稱爲寫放大(Write Amplification)。
4.HBASE默認是自動調度,由於存在寫放大,建議在凌晨或週末進行
5.Major compaction 還能將由於服務器 crash 或者負載均衡致使的數據遷移從新移回到離 Region Server 的地方,這樣就能恢復本地數據( data locality)。
咱們再來回顧一下 region 的概念:
一開始每一個 table 默認只有一個 region。當一個 region 逐漸變得很大時,它會分裂(split)成兩個子 region,每一個子 region 都包含了原來 region 一半的數據,這兩個子 region 並行地在原來這個 region server 上建立,這個分裂動做會被報告給 HMaster。處於負載均衡的目的,HMaster 可能會將新的 region 遷移給其它 region server。
全部的讀寫都發生在 HDFS 的主 DataNode 節點上。HDFS 會自動備份 WAL(寫前日誌) 和 HFile 的文件 blocks。HBase 依賴於 HDFS 來保證數據完整安全。當數據被寫入 HDFS 時,一份會寫入本地節點,另外兩個備份會被寫入其它節點。
WAL 和 HFiles 都會持久化到硬盤並備份。那麼 HBase 是怎麼恢復 MemStore 中還未被持久化到 HFile 的數據呢?
當某個 Region Server 發生 crash 時,它所管理的 region 就沒法被訪問了,直到 crash 被檢測到,而後故障恢復完成,這些 region 才能恢復訪問。Zookeeper 依靠心跳檢測發現節點故障,而後 HMaster 會收到 region server 故障的通知。
當 HMaster 發現某個 region server 故障,HMaster 會將這個 region server 所管理的 regions 分配給其它健康的 region servers。爲了恢復故障的 region server 的 MemStore 中還未被持久化到 HFile 的數據,HMaster 會將 WAL 分割成幾個文件,將它們保存在新的 region server 上。每一個 region server 而後回放各自拿到的 WAL 碎片中的數據,來爲它所分配到的新 region 創建 MemStore。
WAL 包含了一系列的修改操做,每一個修改都表示一個 put 或者 delete 操做。這些修改按照時間順序依次寫入,持久化時它們被依次寫入 WAL 文件的尾部。
當數據仍然在 MemStore 還未被持久化到 HFile 怎麼辦呢?WAL 文件會被回放。操做的方法是讀取 WAL 文件,排序並添加全部的修改記錄到 MemStore,最後 MemStore 會被刷寫到 HFile。
HBASE基本講解就到此結束了,開始講解實戰演練吧!
加班回來才能開始寫文章,好了,開始了。
在告警業務場景中,通常分爲兩類場景
針對這兩種狀況,咱們能夠涉及rowkey爲 : 惟一標識id + 時間 + 告警類型
咱們對id作一個md5,作一個哈希,這樣能夠保證數據的分配均衡.
第二個場景叫作指標平臺,咱們使用kylin作了一層封裝,在這上面能夠選擇咱們在HBase存儲好的數據,能夠選擇哪些維度,去查詢哪些指標。好比這個成交數據,能夠選擇時間,城市。就會造成一張圖,進而建立一張報表。而後這張報表能夠分享給其餘人使用。
爲何會選擇Kylin呢,由於Kylin是一個molap引擎,他是一個運算模型,他知足咱們的需求,對頁面的相應的話,須要亞秒級的響應。
第二,他對併發有必定的要求,原始的數據達到了百億的規模。另外須要具備必定的靈活性,最好有sql接口,以離線爲主。綜合考慮,咱們使用的是Kylin。
Kylin給他家簡單介紹一下,Apache Kylin™是一個開源的分佈式分析引擎,提供Hadoop之上的SQL查詢接口及多維分析(OLAP)能力以支持超大規模數據,最初由eBay Inc.開發並貢獻至開源社區。它能在亞秒內查詢巨大的Hive表。他的原理比較簡單,基於一個並運算模型,我預先知道,我要從那幾個維度去查詢某個指標。在預約好的指標和維度的狀況下去把全部的狀況遍歷一遍。利用molap把全部的結果都算出來,在存儲到HBase中。而後根據sql查詢的維度和指標直接到HBase中掃描就好了。爲何可以實現亞秒級的查詢,這就依賴於HBase的計算。
和剛纔講的邏輯是一致的,左邊是數據倉庫。全部的數據都在數據倉庫中存儲。中間是計算引擎,把天天的調度作好,轉化爲HBase的KY結構存儲在HBase中,對外提供sql接口,提供路由功能,解析sql語句,轉化爲具體的HBase命令。
Kylin中有一個概念叫Cube和Cubold,其實這個邏輯也很是簡單,好比已經知道查詢的維度有A,b,c,d四個。那abcd查詢的時候,能夠取也能夠不取。一共有16種組合,總體叫作,cube。其中每一種組合叫作Cuboid。
首先定義一張原始表,有兩個維度,year和city。
在定義一個指標,好比總價。下面是全部的組合,剛纔說到Kylin裏面有不少cuboid組合,好比說前面三行有一個cuboid:00000011組合,他們在HBase中的RowKey就是cuboid加上各維度的取值。
這裏面會作一點小技巧,對維度的值作一些編碼,若是把程式的原始值放到Rowkey裏,會比較長。
Rowkey也會存在一個sell裏任何一個版本的值都會存在裏面。若是rowkey變的很是長,對HBase的壓力會很是大,全部經過一個字典編碼去減小長度,經過這種方法,就能夠把kylin中的計算數據存儲到HBase中。
用比特位來作一些特徵,多個維度統計分析,速度是比較快的,也是大數據分析經常使用的手段!!!
固然咱們在實際中也配合apache Phoenix提供查詢功能
我先給出一個圖
優化原理:在解釋這個問題以前,首先須要解釋什麼是scan緩存,一般來說一次scan會返回大量數據,所以客戶端發起一次scan請求,實際並不會一次就將全部數據加載到本地,而是分紅屢次RPC請求進行加載,這樣設計一方面是由於大量數據請求可能會致使網絡帶寬嚴重消耗進而影響其餘業務,另外一方面也有可能由於數據量太大致使本地客戶端發生OOM。在這樣的設計體系下用戶會首先加載一部分數據到本地,而後遍歷處理,再加載下一部分數據到本地處理,如此往復,直至全部數據都加載完成。數據加載到本地就存放在scan緩存中,默認100條數據大小。一般狀況下,默認的scan緩存設置就能夠正常工做的。可是在一些大scan(一次scan可能須要查詢幾萬甚至幾十萬行數據)來講,每次請求100條數據意味着一次scan須要幾百甚至幾千次RPC請求,這種交互的代價無疑是很大的。所以能夠考慮將scan緩存設置增大,好比設爲500或者1000就可能更加合適。筆者以前作過一次試驗,在一次scan掃描10w+條數據量的條件下,將scan緩存從100增長到1000,能夠有效下降scan請求的整體延遲,延遲基本下降了25%左右。
優化建議:大scan場景下將scan緩存從100增大到500或者1000,用以減小RPC次數
優化原理:HBase分別提供了單條get以及批量get的API接口,使用批量get接口能夠減小客戶端到RegionServer之間的RPC鏈接數,提升讀取性能。另外須要注意的是,批量get請求要麼成功返回全部請求數據,要麼拋出異常。
優化建議:使用批量get進行讀取請求
優化原理:HBase是典型的列族數據庫,意味着同一列族的數據存儲在一塊兒,不一樣列族的數據分開存儲在不一樣的目錄下。若是一個表有多個列族,只是根據Rowkey而不指定列族進行檢索的話不一樣列族的數據須要獨立進行檢索,性能必然會比指定列族的查詢差不少,不少狀況下甚至會有2倍~3倍的性能損失。
優化建議:能夠指定列族或者列進行精確查找的儘可能指定查找
優化原理:一般離線批量讀取數據會進行一次性全表掃描,一方面數據量很大,另外一方面請求只會執行一次。這種場景下若是使用scan默認設置,就會將數據從HDFS加載出來以後放到緩存。可想而知,大量數據進入緩存必將其餘實時業務熱點數據擠出,其餘業務不得不從HDFS加載,進而會形成明顯的讀延遲毛刺
優化建議:離線批量讀取請求設置禁用緩存,scan.setBlockCache(false)
優化建議:
hbase.hstore.compactionThreshold設置不能太大,默認是3個;設置須要根據Region大小肯定,一般能夠簡單的認爲hbase.hstore.compaction.max.size = RegionSize / hbase.hstore.compactionThreshold
(1)Minor Compaction設置:hbase.hstore.compactionThreshold設置不能過小,又不能設置太大,所以建議設置爲5~6;hbase.hstore.compaction.max.size = RegionSize / hbase.hstore.compactionThreshold(2)Major Compaction設置:大Region讀延遲敏感業務( 100G以上)一般不建議開啓自動Major Compaction,手動低峯期觸發。小Region或者延遲不敏感業務能夠開啓Major Compaction,但建議限制流量;(3)期待更多的優秀Compaction策略,相似於stripe-compaction儘早提供穩定服務
任何業務都應該設置Bloomfilter,一般設置爲row就能夠,除非確認業務隨機查詢類型爲row+cf,能夠設置爲rowcol
BloomFilter是啓用在每一個ColumnFamily上的,咱們可使用JavaAPI
//咱們能夠經過ColumnDescriptor來指定開啓的BloomFilter的類型
HColumnDescriptor.setBloomFilterType() //可選NONE、ROW、ROWCOL
複製代碼
咱們還能夠在建立Table的時候指定BloomFilter
hbase> create 'mytable',{NAME => 'colfam1', BLOOMFILTER => 'ROWCOL'}
複製代碼
優化原理:當前HDFS讀取數據都須要通過DataNode,客戶端會向DataNode發送讀取數據的請求,DataNode接受到請求以後從硬盤中將文件讀出來,再經過TPC發送給客戶端。Short Circuit策略容許客戶端繞過DataNode直接讀取本地數據。
優化原理:HBase數據在HDFS中通常都會存儲三份,並且優先會經過Short-Circuit Local Read功能嘗試本地讀。可是在某些特殊狀況下,有可能會出現由於磁盤問題或者網絡問題引發的短期本地讀取失敗,爲了應對這類問題,社區開發者提出了補償重試機制 – Hedged Read。該機制基本工做原理爲:客戶端發起一個本地讀,一旦一段時間以後尚未返回,客戶端將會向其餘DataNode發送相同數據的請求。哪個請求先返回,另外一個就會被丟棄。 優化建議:開啓Hedged Read功能,具體配置參考這裏官網
數據本地率:HDFS數據一般存儲三份,假如當前RegionA處於Node1上,數據a寫入的時候三副本爲(Node1,Node2,Node3),數據b寫入三副本是(Node1,Node4,Node5),數據c寫入三副本(Node1,Node3,Node5),能夠看出來全部數據寫入本地Node1確定會寫一份,數據都在本地能夠讀到,所以數據本地率是100%。如今假設RegionA被遷移到了Node2上,只有數據a在該節點上,其餘數據(b和c)讀取只能遠程跨節點讀,本地率就爲33%(假設a,b和c的數據大小相同)。優化原理:數據本地率過低很顯然會產生大量的跨網絡IO請求,必然會致使讀請求延遲較高,所以提升數據本地率能夠有效優化隨機讀性能。數據本地率低的緣由通常是由於Region遷移(自動balance開啓、RegionServer宕機遷移、手動遷移等),所以一方面能夠經過避免Region無端遷移來保持數據本地率,另外一方面若是數據本地率很低,也能夠經過執行major_compact提高數據本地率到100%。優化建議:避免Region無端遷移,好比關閉自動balance、RS宕機及時拉起並遷回飄走的Region等;在業務低峯期執行major_compact提高數據本地率
集羣規模10~20臺,每秒讀寫數據量在幾十萬條記錄的量級
客戶端與HBase集羣進行RPC操做時會拋出NotServingRegionException異常,結果就致使了讀寫操做失敗。大量的寫操做被阻塞,寫入了文件,系統也發出了警報!
問題排查
經過查看HBase Master運行日誌,結合客戶端拋出異常的時刻,發現當時HBase集羣內正在進行Region的Split和不一樣機器之間的Region Balance
1)因爲表中rowkey有時間字段,所以天天都須要新建立Region,同時因爲寫入數據量大,進一步觸發了HBase的Region Split操做,這一過程通常耗時較長(測試時從線上日誌來看,平均爲10秒左右,Region大小爲4GB),且Region Split操做觸發較爲頻繁;
2)同時因爲Region Split操做致使Region分佈不均勻,進而觸發HBase自動作Region Balance操做,Region遷移過程當中也會致使Region下線,這一過程耗時較長(測試時從線上日誌來看,平均爲20秒左右)。
解決:
1)對於寫端,能夠將未寫入成功的記錄,添加到一個客戶端緩存中,隔一段時間後交給一個後臺線程統一從新提交一次;也能夠經過setAutoFlush(flase, false)保證提交失敗的記錄不被拋棄,留在客戶端writeBuffer中等待下次writeBuffer滿了後再次嘗試提交,直到提交成功爲止。
2)對於讀端,捕獲異常後,能夠採起休眠一段時間後進行重試等方式。
3)將timestamp字段改爲一個週期循環的timestamp,如取timestamp % TS_MODE後的值,其中TS_MODE須大於等於表的TTL時間週期,這樣才能保證數據不會被覆蓋掉。通過這樣改造後,便可實現Region的複用,避免Region的無限上漲。對於讀寫端的變動也較小,讀寫端操做時只需將timestamp字段取模後做爲rowkey進行讀寫,另外,讀端須要考慮能適應scan掃描時處理[startTsMode, endTsMode]和[endTsMode, startTsMode]兩種狀況
參考文章:
❤️❤️❤️❤️
很是感謝人才們能看到這裏,若是這個文章寫得還不錯,以爲有點東西的話 求點贊👍 求關注❤️ 求分享👥 對帥氣歐巴的我來講真的 很是有用!!!
若是本篇博客有任何錯誤,請批評指教,不勝感激 !
文末福利,最近整理一份面試資料《Java面試通關手冊》,覆蓋了Java核心技術、JVM、Java併發、SSM、微服務、數據庫、數據結構等等。獲取方式:GitHub github.com/Tingyu-Note…,更多內容關注公號:汀雨筆記,陸續奉上。