HBase 在淘寶的應用和優化小結

個人渣記錄僅為自己搜尋用發表於2017-09-18

這就是為什麼不放重要資料的原因.

HBase工程師線上工作經驗總結----HBase常見問題及分析 http://itindex.net/detail/52338-hbase-%E5%B7%A5%E7%A8%8B%E5%B8%88-%E7%BA%BF%E4%B8%8A

兩次hbase丟失資料的故障及原因分析  http://itindex.net/detail/30180-hbase-%E6%95%B0%E6%8D%AE-%E5%88%86%E6%9E%90





1 前言
hbase是從hadoop中分離出來的apache頂級開源專案。由於它很好地用java實現了google的bigtable系統大部分特性,因此在 資料量猛增的今天非常受到歡迎。對於淘寶而言,隨著市場規模的擴大,產品與技術的發展,業務資料量越來越大,對海量資料的高效插入和讀取變得越來越重要。 由於淘寶擁有也許是國內最大的單一hadoop叢集(雲梯),因此對hadoop系列的產品有比較深入的瞭解,也就自然希望使用hbase來做這樣一種海 量資料讀寫服務。本篇文章將對淘寶最近一年來在online應用上使用和優化hbase的情況做一次小結。

2 原因
為什麼要使用hbase?
淘寶在2011年之前所有的後端持久化儲存基本上都是在mysql上進行的(不排除少量oracle/bdb/tair/mongdb等),mysql由於開源,並且生態系統良好,本身擁有分庫分表等多種解決方案,因此很長一段時間內都滿足淘寶大量業務的需求。
但是由於業務的多樣化發展,有越來越多的業務系統的需求開始發生了變化。一般來說有以下幾類變化:
a) 資料量變得越來越多,事實上現在淘寶幾乎任何一個與使用者相關的線上業務的資料量都在億級別,每日系統呼叫次數從億到百億都有,且歷史資料不能輕易刪除。這需要有一個海量分散式檔案系統,能對TB級甚至PB級別的資料提供線上服務
b) 資料量的增長很快且不一定能準確預計,大多數應用系統從上線起在一段時間內資料量都呈很快的上升趨勢,因此從成本的角度考慮對系統水平擴充套件能力有比較強烈的需求,且不希望存在單點制約
c) 只需要簡單的kv讀取,沒有複雜的join等需求。但對系統的併發能力以及吞吐量、響應延時有非常高的需求,並且希望系統能夠保持強一致性
d) 通常系統的寫入非常頻繁,尤其是大量系統依賴於實時的日誌分析
e) 希望能夠快速讀取批量資料
f ) schema靈活多變,可能經常更新列屬性或新增列
g) 希望能夠方便使用,有良好且語義清晰的java介面

以上需求綜合在一起,我們認為hbase是一種比較適合的選擇。首先它的資料由hdfs天然地做了資料冗餘,雲梯三年的穩定執行,資料100%可靠 己經證明了hdfs叢集的安全性,以及服務於海量資料的能力。其次hbase本身的資料讀寫服務沒有單點的限制,服務能力可以隨伺服器的增長而線性增長, 達到幾十上百臺的規模。LSM-Tree模式的設計讓hbase的寫入效能非常良好,單次寫入通常在1-3ms內即可響應完成,且效能不隨資料量的增長而 下降。region(相當於資料庫的分表)可以ms級動態的切分和移動,保證了負載均衡性。由於hbase上的資料模型是按rowkey排序儲存的,而讀 取時會一次讀取連續的整塊資料做為cache,因此良好的rowkey設計可以讓批量讀取變得十分容易,甚至只需要1次io就能獲取幾十上百條使用者想要的 資料。最後,淘寶大部分工程師是java背景的同學,因此hbase的api對於他們來說非常容易上手,培訓成本相對較低。
當然也必須指出,在大資料量的背景下銀彈是不存在的,hbase本身也有不適合的場景。比如,索引只支援主索引(或看成主組合索引),又比如服務是單點 的,單臺機器當機後在master恢復它期間它所負責的部分資料將無法服務等。這就要求在選型上需要對自己的應用系統有足夠了解。

3 應用情況
我們從2011年3月開始研究hbase如何用於線上服務。儘管之前在一淘搜尋中己經有了幾十節點的離線服務。這是因為hbase早期版本的目標就是一個 海量資料中的離線服務。2009年9月釋出的0.20.0版本是一個里程碑,online應用正式成為了hbase的目標,為此hbase引入了 zookeeper來做為backupmaster以及regionserver的管理。2011年1月0.90.0版本是另一個里程碑,基本上我們今天 看到的各大網站,如facebook/ebay/yahoo內所使用於生產的hbase都是基於這一個版本(fb所採用的0.89版本結構與0.90.x 相近)。bloomfilter等諸多屬性加入了進來,效能也有極大提升。基於此,淘寶也選用了0.90.x分支作為線上版本的基礎。
第一個上線的應用是資料魔方中的prom。prom原先是基於redis構建的,因為資料量持續增大以及需求的變化,因此我們用hbase重構了它的儲存 層。準確的說prom更適合0.92版本的hbase,因為它不僅需要高速的線上讀寫,更需要count/group by等複雜應用。但由於當時0.92版本尚未成熟,因此我們自己單獨實現了coprocessor。prom的資料匯入是來源於雲梯,因此我們每天晚上花 半個小時將資料從雲梯上寫入hbase所在的hdfs,然後在web層做了一個client轉發。經過一個月的資料比對,確認了速度比之redis並未有 明顯下降,以及資料的準確性,因此得以順利上線。
第二個上線的應用是TimeTunnel,TimeTunnel是一個高效的、可靠的、可擴充套件的實時資料傳輸平臺,廣泛應用於實時日誌收集、資料實時監 控、廣告效果實時反饋、資料庫實時同步等領域。它與prom相比的特點是增加了線上寫。動態的資料增加使hbase上compact/balance /split/recovery等諸多特性受到了極大的挑戰。TT的寫入量大約一天20TB,讀的量約為此的1.5倍,我們為此準備了20臺 regionserver的叢集,當然底層的hdfs是公用的,數量更為龐大(下文會提到)。每天TT會為不同的業務在hbase上建不同的表,然後往該 表上寫入資料,即使我們將region的大小上限設為1GB,最大的幾個業務也會達到數千個region這樣的規模,可以說每一分鐘都會有數次 split。在TT的上線過程中,我們修復了hbase很多關於split方面的bug,有好幾個commit到了hbase社群,同時也將社群一些最新 的patch打在了我們的版本上。split相關的bug應該說是hbase中會導致資料丟失最大的風險之一,這一點對於每個想使用hbase的開發者來 說必須牢記。hbase由於採用了LSM-Tree模型,從架構原理上來說資料幾乎沒有丟失的可能,但是在實際使用中不小心謹慎就有丟失風險。原因後面會 單獨強調。TT在預發過程中我們分別因為Meta表損壞以及split方面的bug曾經丟失過資料,因此也單獨寫了meta表恢復工具,確保今後不發生類 似問題(hbase-0.90.5以後的版本都增加了類似工具)。另外,由於我們存放TT的機房並不穩定,發生過很多次當機事故,甚至發生過假死現象。因 此我們也著手修改了一些patch,以提高當機恢復時間,以及增強了監控的強度。
CTU以及會員中心專案是兩個對線上要求比較高的專案,在這兩個專案中我們特別對hbase的慢響應問題進行了研究。hbase的慢響應現在一般歸納為四 類原因:網路原因、gc問題、命中率以及client的反序列化問題。我們現在對它們做了一些解決方案(後面會有介紹),以更好地對慢響應有控制力。
和Facebook類似,我們也使用了hbase做為實時計算類專案的儲存層。目前對內部己經上線了部分實時專案,比如實時頁面點選系統,galaxy實 時交易推薦以及直播間等內部專案,使用者則是散佈到公司內各部門的運營小二們。與facebook的puma不同的是淘寶使用了多種方式做實時計算層,比如 galaxy是使用類似affa的actor模式處理交易資料,同時關聯商品表等維度表計算排行(TopN),而實時頁面點選系統則是基於twitter 開源的storm進行開發,後臺通過TT獲取實時的日誌資料,計算流將中間結果以及動態維表持久化到hbase上,比如我們將rowkey設計為 url+userid,並讀出實時的資料,從而實現實時計算各個維度上的uv。
最後要特別提一下歷史交易訂單專案。這個專案實際上也是一個重構專案,目的是從以前的solr+bdb的方案上遷移到hbase上來。由於它關係到己買到 頁面,使用者使用頻率非常高,重要程度接近核心應用,對資料丟失以及服務中斷是零容忍。它對compact做了優化,避免大資料量的compact在服務時 間內發生。新增了定製的filter來實現分頁查詢,rowkey上對應用進行了巧妙的設計以避免了冗餘資料的傳輸以及90%以上的讀轉化成了順序讀。目 前該叢集儲存了超過百億的訂單資料以及數千億的索引資料,線上故障率為0。
隨著業務的發展,目前我們定製的hbase叢集己經應用到了線上超過二十個應用,數百臺伺服器上。包括淘寶首頁的商品實時推薦、廣泛用於賣家的實時量子統計等應用,並且還有繼續增多以及向核心應用靠近的趨勢。

4 部署、運維和監控
Facebook之前曾經透露過Facebook的hbase架構,可以說是非常不錯的。如他們將message服務的hbase叢集按使用者分為數個集 群,每個叢集100臺伺服器,擁有一臺namenode以及分為5個機架,每個機架上一臺zookeeper。可以說對於大資料量的服務這是一種優良的架 構。對於淘寶來說,由於資料量遠沒有那麼大,應用也沒有那麼核心,因此我們採用公用hdfs以及zookeeper叢集的架構。每個hdfs叢集儘量不超 過100臺規模(這是為了儘量限制namenode單點問題)。在其上架設數個hbase叢集,每個叢集一個master以及一個 backupmaster。公用hdfs的好處是可以儘量減少compact的影響,以及均攤掉硬碟的成本,因為總有叢集對磁碟空間要求高,也總有叢集對 磁碟空間要求低,混合在一起用從成本上是比較合算的。zookeeper叢集公用,每個hbase叢集在zk上分屬不同的根節點。通過zk的許可權機制來保 證hbase叢集的相互獨立。zk的公用原因則僅僅是為了運維方便。
由於是線上應用,運維和監控就變得更加重要,由於之前的經驗接近0,因此很難招到專門的hbase運維人員。我們的開發團隊和運維團隊從一開始就很重視該問題,很早就開始自行培養。以下講一些我們的運維和監控經驗。
我們定製的hbase很重要的一部分功能就是增加監控。hbase本身可以傳送ganglia監控資料,只是監控項遠遠不夠,並且ganglia的展示方 式並不直觀和突出。因此一方面我們在程式碼中侵入式地增加了很多監控點,比如compact/split/balance/flush佇列以及各個階段的耗 時、讀寫各個階段的響應時間、讀寫次數、region的open/close,以及具體到表和region級別的讀寫次數等等。仍然將它們通過 socket的方式傳送到ganglia中,ganglia會把它們記錄到rrd檔案中,rrd檔案的特點是歷史資料的精度會越來越低,因此我們自己編寫 程式從rrd中讀出相應的資料並持久化到其它地方,然後自己用js實現了一套監控介面,將我們關心的資料以趨勢圖、餅圖等各種方式重點彙總和顯示出來,並 且可以無精度損失地檢視任意歷史資料。在顯示的同時會把部分非常重要的資料,如讀寫次數、響應時間等寫入資料庫,實現波動報警等自定義的報警。經過以上措 施,保證了我們總是能先於使用者發現叢集的問題並及時修復。我們利用redis高效的排序演算法實時地將每個region的讀寫次數進行排序,能夠在高負載的 情況下找到具體請求次數排名較高的那些region,並把它們移到空閒的regionserver上去。在高峰期我們能對上百臺機器的數十萬個 region進行實時排序。
為了隔離應用的影響,我們在程式碼層面實現了可以檢查不同client過來的連線,並且切斷某些client的連線,以在發生故障時,將故障隔離在某個應用內部而不擴大化。mapreduce的應用也會控制在低峰期執行,比如在白天我們會關閉jobtracker等。
此外,為了保障服務從結果上的可用,我們也會定期跑讀寫測試、建表測試、hbck等命令。hbck是一個非常有用的工具,不過要注意它也是一個很重的工操 作,因此儘量減少hbck的呼叫次數,儘量不要並行執行hbck服務。在0.90.4以前的hbck會有一些機率使hbase當機。另外為了確保hdfs 的安全性,需要定期執行fsck等以檢查hdfs的狀態,如block的replica數量等。
我們會每天根蹤所有線上伺服器的日誌,將錯誤日誌全部找出來並且郵件給開發人員,以查明每一次error以上的問題原因和fix。直至錯誤降低為0。另外 每一次的hbck結果如果有問題也會郵件給開發人員以處理掉。儘管並不是每一次error都會引發問題,甚至大部分error都只是分散式系統中的正常現 象,但明白它們問題的原因是非常重要的。

5 測試與釋出
因為是未知的系統,我們從一開始就非常注重測試。測試從一開始就分為效能測試和功能測試。效能測試主要是注意基準測試,分很多場景,比如不同混合讀寫比 例,不同k/v大小,不同列族數,不同命中率,是否做presharding等等。每次執行都會持續數小時以得到準確的結果。因此我們寫了一套自動化系 統,從web上選擇不同的場景,後臺會自動將測試引數傳到各臺伺服器上去執行。由於是測試分散式系統,因此client也必須是分散式的。
我們判斷測試是否準確的依據是同一個場景跑多次,是否資料,以及執行曲線達到99%以上的重合度,這個工作非常煩瑣,以至於消耗了很多時間,但後來的事實 證明它非常有意義。因為我們對它建立了100%的信任,這非常重要,比如後期我們的改進哪怕只提高2%的效能也能被準確捕捉到,又比如某次程式碼修改使 compact佇列曲線有了一些起伏而被我們看到,從而找出了程式的bug,等等。
功能測試上則主要是介面測試和異常測試。介面測試一般作用不是很明顯,因為hbase本身的單元測試己經使這部分被覆蓋到了。但異常測試非常重要,我們絕 大部分bug修改都是在異常測試中發現的,這幫助我們去掉了很多生產環境中可能存在的不穩定因素,我們也提交了十幾個相應的patch到社群,並受到了重 視和commit。分散式系統設計的難點和複雜度都在異常處理上,我們必須認為系統在通訊的任何時候都是不可靠的。某些難以復現的問題我們會通過檢視程式碼 大體定位到問題以後,在程式碼層面強行丟擲異常來複現它。事實證明這非常有用。
為了方便和快速定位問題,我們設計了一套日誌收集和處理的程式,以方便地從每臺伺服器上抓取相應的日誌並按一定規律彙總。這非常重要,避免浪費大量的時間到登入不同的伺服器以尋找一個bug的線索。
由於hbase社群在不停發展,以及線上或測試環境發現的新的bug,我們需要制定一套有規律的釋出模式。它既要避免頻繁的釋出引起的不穩定,又要避免長 期不釋出導致生產版本離開發版本越來越遠或是隱藏的bug爆發。我們強行規定每兩週從內部trunk上release一個版本,該版本必須通過所有的測試 包括迴歸測試,並且在release後在一個小型的叢集上24小時不受甘擾不停地執行。每個月會有一次釋出,釋出時採用最新release的版本,並且將 現有的叢集按重要性分級釋出,以確保重要應用不受新版本的潛在bug影響。事實證明自從我們引入這套釋出機制後,由釋出帶來的不穩定因素大大下降了,並且 線上版本也能保持不落後太多。

6 改進和優化
Facebook是一家非常值得尊敬的公司,他們毫無保留地對外公佈了對hbase的所有改造,並且將他們內部實際使用的版本開源到了社群。 facebook線上應用的一個重要特點是他們關閉了split,以降低split帶來的風險。與facebook不同,淘寶的業務資料量相對沒有如此龐 大,並且由於應用型別非常豐富,我們並們並沒有要求使用者強行選擇關閉split,而是儘量去修改split中可能存在的bug。到目前為止,雖然我們並不 能說完全解決了這個問題,但是從0.90.2中暴露出來的諸多跟split以及當機相關的可能引發的bug我們的測試環境上己經被修復到接近了0,也為社 區提交了10數個穩定性相關的patch,比較重要的有以下幾個:
https://issues.apache.org/jira/browse/HBASE-4562
https://issues.apache.org/jira/browse/HBASE-4563
https://issues.apache.org/jira/browse/HBASE-5152
https://issues.apache.org/jira/browse/HBASE-5100
https://issues.apache.org/jira/browse/HBASE-4880
https://issues.apache.org/jira/browse/HBASE-4878
https://issues.apache.org/jira/browse/HBASE-4899
還有其它一些,我們主要將patch提交到0.92版本,社群會有commitor幫助我們backport回0.90版本。所以社群從0.90.2一直 到0.90.6一共釋出了5個bugfix版本後,0.90.6版本其實己經比較穩定了。建議生產環境可以考慮這個版本。
split這是一個很重的事務,它有一個嚴重的問題就是會修改meta表(當然當機恢復時也有這個問題)。如果在此期間發生異常,很有可能meta表、 rs記憶體、master記憶體以及hdfs上的檔案會發生不一致,導致之後region重新分配時發生錯誤。其中一個錯誤就是有可能同一個region被兩 個以上的regionserver所服務,那麼就可能出現這一個region所服務的資料會隨機分別寫到多臺rs上,讀取的時候也會分別讀取,導致資料丟 失。想要恢復原狀,必須刪除掉其中一個rs上的region,這就導致了不得不主動刪掉資料,從而引發資料丟失。
前面說到慢響應的問題歸納為網路原因、gc問題、命中率以及client的反序列化問題。網路原因一般是網路不穩定引起的,不過也有可能是tcp引數設定 問題,必須保證儘量減少包的延遲,如nodelay需要設定為true等,這些問題我們通過tcpdump等一系列工具專門定位過,證明tcp引數對包的 組裝確實會造成慢連線。gc要根據應用的型別來,一般在讀比較多的應用中新生代不能設定得太小。命中率極大影響了響應的時間,我們會盡量將version 數設為1以增加快取的容量,良好的balance也能幫助充分應用好每臺機器的命中率。我們為此設計了表級別的balance。
由於hbase服務是單點的,即當機一臺,則該臺機器所服務的資料在恢復前是無法讀寫的。當機恢復速度決定了我們服務的可用率。為此主要做了幾點優化。首 先是將zk的當機發現時間儘量縮短到1分鐘,其次改進了master恢復日誌為並行恢復,大大提高了master恢復日誌的速度,然後我們修改了 openhandler中可能出現的一些超時異常,以及死鎖,去掉了日誌中可能發生的open…too long等異常。原生的hbase在當機恢復時有可能發生10幾分鐘甚至半小時無法重啟的問題己經被修復掉了。另外,hdfs層面我們將 socket.timeout時間以及重試時間也縮短了,以降低datanode當機引起的長時間block現象。
hbase本身讀寫層面的優化我們目前並沒有做太多的工作,唯一打的patch是region增加時寫效能嚴重下降的問題。因為由於hbase本身良好的 效能,我們通過大量測試找到了各種應用場景中比較優良的引數並應用於生產環境後,都基本滿足需求。不過這是我們接下來的重要工作。

7 將來計劃
我們目前維護著淘寶內基於社群0.90.x而定製的hbase版本。接下來除繼續fix它的bug外,會維護基於0.92.x修改的版本。之所以這樣,是 因為0.92.x和0.90.x的相容性並不是非常好,而且0.92.x修改掉的程式碼非常多,粗略統計會超過30%。0.92中有我們非常看重的一些特 性。
· 0.92版本改進了hfile為hfileV2,v2版本的特點是將索引以及bloomfilter進行了大幅改造,以支援單個大hfile檔案。現有的 HFile在檔案大到一定程度時,index會佔用大量的記憶體,並且載入檔案的速度會因此下降非常多。而如果HFile不增大的話,region就無法擴 大,從而導致region數量非常多。這是我們想盡量避免的事。
· 0.92版本改進了通訊層協議,在通訊層中增加了length,這非常重要,它讓我們可以寫出nio的客戶端,使反序列化不再成為影響client效能的地方。
· 0.92版本增加了coprocessor特性,這支援了少量想要在rs上進行count等的應用。
· 還有其它很多優化,比如改進了balance演算法、改進了compact演算法、改進了scan演算法、compact變為CF級別、動態做ddl等等特性。
除了0.92版本外,0.94版本以及最新的trunk(0.96)也有很多不錯的特性,0.94是一個效能優化版本。它做了很多革命性工作,比如去掉root表,比如HLog進行壓縮,replication上支援多個slave叢集,等等。
我們自己也有一些優化,比如自行實現的二級索引、backup策略等都會在內部版本上實現。
另外值得一提的是hdfs層面的優化也非常重要,hadoop-1.0.0以及cloudera-3u3的改進對hbase非常有幫助,比如本地化讀、 checksum的改進、datanode的keepalive設定、namenode的HA策略等。我們有一支優秀的hdfs團隊來支援我們的hdfs 層面工作,比如定位以及fix一些hdfs層面的bug,幫助提供一些hdfs上引數的建議,以及幫助實現namenode的HA等。最新的測試表 明,3u3的checksum+本地化讀可以將隨機讀效能提升至少一倍。
我們正在做的一件有意義的事是實時監控和調整regionserver的負載,能夠動態地將負載不足的叢集上的伺服器挪到負載較高的叢集中,而整個過程對使用者完全透明。

總的來說,我們的策略是儘量和社群合作,以推動hbase在整個apache生態鏈以及業界的發展,使其能更穩定地部署到更多的應用中去,以降低使用門檻以及使用成本。

相關文章