淺談HBase的數據分佈

摘要: HBase的rowkey設計一直都是難點和痛點,不合適的rowkey設計會致使讀寫性能、吞吐不佳等諸多問題。本文從數據分佈問題展開,介紹HBase基於Range的分佈策略與region的調度問題,詳細討論了rowkey的比較規則及其應用,但願可以加深用戶對HBase數據分佈機制和rowkey的理解,從而作出更合適的設計,精準、高效的使用HBase。

福利:國際頂級盛會HBaseCon Asia 2018將於8月在北京舉行,目前正免費開放申請中,更多詳情參考https://yq.aliyun.com/promotion/631web

若是你對大數據存儲、分佈式數據庫、HBase等感興趣,歡迎加入咱們,一塊兒作最好的大數據在線存儲,職位參考及聯繫方式:https://maimai.cn/job?webjid=1heZGIyM4&srcu=1aOrffoj1&src=app&fr=my_jobsrecruit_job數據庫

數據分佈問題簡述

分佈式產生的根源是「規模」,規模可理解爲計算和存儲的需求。當單機能力沒法承載日益增加的計算存儲需求時,就要尋求對系統的擴展方法。一般有兩種擴展方式:提高單機能力(scale up),增長機器(scale out,水平擴展)。限於硬件技術,單機能力的提高在一個階段內是有上限的;而水平擴展在理論上能夠是無限的,同時,也更廉價、更容易落地。水平擴展能夠經過快速、簡單的「加機器」,有效解決業務快速增加的問題,這幾乎是現代分佈式系統必備的能力。對於爆發式增加的業務,水平擴展彷佛是惟一可選擇的方案。緩存

對於存儲系統而言,本來存儲在一臺機器上的數據,如今要存放在多臺機器上。此時必須解決兩個問題:分片,複製。服務器

  • 數據分片(sharding),又稱分區(partition),將數據集「合理的」拆分紅多個分片,每臺機器負責其中若干個分片。以此來突破單機容量的限制,同時也提高了總體的訪問能力。另外,分片也下降了單個分片故障的影響範圍。
  • 數據複製(replica),也叫「副本」。分片沒法解決單機故障丟數據的問題,因此,必然要經過冗餘來解決系統高可用的問題。同時,副本機制也是提高系統吞吐、解決熱點問題的重要手段。

分片和副本是正交的,這意味着咱們能夠只使用其中一種或都使用,但一般都是同時使用的。由於分片解決的是規模和擴展性的問題,副本解決可靠、可用性的問題。對於一個生產可用的系統,兩者必須同時具有。網絡

從使用者/客戶端的角度看,分片和副本能夠歸結爲同一個問題:請求路由,即請求應該發送給哪臺機器來處理。架構

  • 讀數據時,能經過某種機制來確保有一個合適的分片/副原本提供服務
  • 寫數據時,能經過一樣的機制來確保寫到一個合適的地方,並確保副本的一致性

不管客戶端的請求是直達服務端(如HBase/cassandra),仍是經過代理(如公有云上的基於gateway的訪問方式),請求路由都是分佈式系統必須解決的問題。app

不管是分片仍是副本,本質上都是數據分佈的體現。下面咱們來看HBase的數據分佈模型。負載均衡

HBase的數據分佈模型

HBase的數據分片按表進行,以行爲粒度,基於rowkey範圍進行拆分,每一個分片稱爲一個region。一個集羣有多張表,每張表劃分爲多個region,每臺服務器服務不少region。因此,HBase的服務器稱爲RegionServer,簡稱RS。RS與表是正交的,即一張表的region會分佈到多臺RS上,一臺RS也會調度多張表的region。以下圖所示:運維

「以行爲粒度」,意思是行是region劃分的最小單位,即一行數據要麼屬於A region,要麼屬於Bregion,不會被拆到兩個region中去。(對行進行拆分的方式是「垂直分庫」,一般只能在業務層面進行,HBase是水平拆分)分佈式

HBase的副本機制是經過經過底層的HDFS實現的。因此,HBase的副本與分片是解耦的,是存儲計算分離的。這使得region能夠在RS之間靈活的移動,而不須要進行數據遷移,這賦予了HBase秒級擴容的能力和極大的靈活性。

對於單個表而言,一個「好」的數據分佈,應該是每一個region的數據量大小相近,請求量(吞吐)接近,每臺機器調度的region數量大體相同。這樣,這張表的數據和訪問可以均勻的分佈在整個集羣中,從而獲得最好的資源利用率和服務質量,即達到負載均衡。當集羣進行擴容、縮容時,咱們但願這種「均衡」可以自動保持。若是數據分佈未能實現負載均衡,則負載較高的機器很容易稱爲整個系統的瓶頸,這臺機器的響應慢,可能致使客戶端的大部分線程都在等待這臺機器返回,從而影響總體吞吐。因此,負載均衡是region劃分和調度的重要目標。

這裏涉及到3層面的負載均衡問題:

  • 數據的邏輯分佈:即region劃分/分佈,是rowkey到region的映射問題
  • 數據的物理分佈:即region在RS上的調度問題
  • 訪問的分佈:即系統吞吐(請求)在各個RS上的分佈問題,涉及數據量和訪問量之間的關係,訪問熱點等。

可見,一行數據的分佈(找到一行數據所在的RS),存在2個層級的路由:一是rowkey到region的路由,二是region到RS的路由。這一點是HBase可以實現靈活調度、秒級擴容的關鍵。後面咱們會詳細討論。本文僅討論前面兩個問題,第三個問題放在後續的文章中討論。

基於rowkey範圍的region劃分

首先,咱們來看數據的邏輯分佈,即一張表如何劃分紅多個region。

region劃分的粒度是行,region就是這個表中多個連續的行構成的集合。行的惟一標識符是rowkey,因此,能夠將region理解爲一段連續分佈的rowkey的集合。因此,稱這種方式爲基於rowkey範圍的劃分。

一個region負責的rowkey範圍是一個左閉右開區間,因此,後一個region的start key是前一個region的end key。注意,第一個region是沒有start key的,最後一個region是沒有end key的。這樣,這個表的全部region加在一塊兒就能覆蓋任意的rowkey值域。以下圖所示:

上圖中,region1是第一個region,沒有startKey,region3是最後一個region,沒有endKey。圖中的region分佈是比較均勻的,即每一個region的行數是至關的,那麼,這個分佈是怎麼獲得的呢?或者說,region的邊界是如何肯定的?

通常來講,region的生成有3種方式:

  • 建表時進行預分區:經過對rowkey進行預估,預先劃分好region
  • region分裂:手工分裂,或達到必定條件時自動分裂(如region大小超過一個閾值)
  • region合併:手工合併

建表時若是未顯式指定region分佈,HBase就會只建立一個region,這個region天然也只能由一臺機器進行調度(後面會討論一個region由多個RS調度的狀況)。那這個region的吞吐上限就是單機的吞吐上限。若是經過合理的預分區將表分紅8個region,分佈在8臺RS上,那整表的吞吐上限就是8臺機器的吞吐上限。

因此,爲了使表從一開始就具有良好的吞吐和性能,實際生產環境中建表一般都須要進行預分區。但也有一些例外,好比沒法預先對rowkey範圍進行預估,或者,不容易對rowkey範圍進行均勻的拆分,此時,也能夠建立只有一個region的表,由系統本身分裂,從而逐漸造成一個「均勻的」region分佈。

好比一張存儲多個公司的員工信息的表,rowkey組成是orgId + userid,其中orgId是公司的id。因爲每一個公司的人數是不肯定的,同時也多是差異很大的,因此,很難肯定一個region中包含幾個orgId是合適的。此時,能夠爲其建立單region的表,而後導入初始數據,隨着數據的導入進行region的自動分裂,一般都能獲得比較理想的region分佈。若是後續公司人員發生較大的變化,也能夠隨時進行region的分裂與合併,來得到最佳分佈。

字典序與rowkey比較

上一節咱們提到region的rowkey範圍是一個左閉右開區間,全部落在這個範圍的rowkey都屬於這個region。爲了進行這個判斷,必須將其與這個region的起止rowkey進行比較。除了region歸屬的判斷,在region內部,也須要依賴rowkey的比較規則來對rowkey進行排序。

不少人都會認爲rowkey的比較很是簡單,沒有什麼討論的必要。但正是由於簡單,它的使用才能靈活多樣,使得HBase具有無限的可能性。能夠說,rowkey的比較規則是整個HBase數據模型的核心,直接影響了整個請求路由體系的設計、讀寫鏈路、rowkey設計、scan的使用等,貫穿整個HBase。對於用戶而言,深刻理解這個規則及其應用有助於作出良好的表設計,寫出精準、高效的scan。

HBase的rowkey是一串二進制數據,在Java中就是一個byte[],是一行數據的惟一標識符。而業務的主鍵多是有各類數據類型的,因此,這裏要解決2個問題:

  • 將各類實際使用的數據類型與byte[]進行相互轉換
  • 保序:byte[]形式的rowkey的排序結果與原始數據的排序結果一致

rowkey的比較就是byte[]的比較,按字典序進行比較(二進制排序),簡單說,就是c語言中memcmp函數。經過下面的示例,咱們經過排序結果來對這一比較規則以及數據類型轉換進行理解。

(1)ascii碼的大小比較
1234 -> 0x31 32 33 34
5 -> 0x35
從ascii碼錶示的數字來看,1234 > 5, 但從字典序來看,1234 < 5

(2)具備相同前綴的ascii碼比較
1234     -> 0x31 32 33 34
12340 -> 0x31 32 33 34 00
在C語言中,字符串通常是以0本身結尾的。本例的兩個字符串雖然前綴相同,但第二個末尾多了0字節,則第二個「較大」。

(3)正數與負數的比較
int類型的100 -> 0x00 00 00 64
int類型的-100 -> 0xFF FF FF 9C
100 > -100,但其二進制表達中,100 < -100

咱們能夠將這個比較規則總結以下:

  • 從左到右逐個字節進行比較,以第一個不一樣字節的比較結果做爲兩個byte[]的比較結果
  • 字節的比較是按無符號數方式進行的
  • 「不存在」比「存在」小

常見的rowkey編碼問題:

  • 有符號數:二進制表示中,有符號數的首bit是1,在字典序規則下,負數比正數大,因此,當rowkey的值域同時包含正數和負數時,須要對符號位進行反轉,以確保正數比負數大
  • 倒序:一般用long來描述時間,通常都是倒排的,假設原始值是v,則v的倒序編碼是Long#MAX_VALUE - v。

下面經過一個前綴掃描的案例來體會一下這個比較規則的應用。

示例:前綴掃描

Hbase的rowkey能夠理解爲單一主鍵列。若是業務場景須要多列一塊兒構成聯合主鍵(也叫多列主鍵,組合主鍵,複合主鍵等等),就須要將多列拼接爲一列。通常來講,直接將二進制拼接在一塊兒便可。例如:

rowkey組成:userId + ts

爲了簡單,假設userid和ts都是定長的,且只有1個字節。例如:

如今,咱們要作的事情是,查找某個userid = 2的全部數據。這是一個典型的前綴掃描場景,咱們須要構造一個Scan操做來完成:設置正確掃描範圍[startRow, stopRow),與region的邊界同樣,scan的範圍也是一個左閉右開區間。

一個直接的思路是找到最小和最大的ts,與userid = 2拼接,做爲查詢範圍,即[0x02 00, 0x02 FF)。因爲scan是左臂右開區間,則0x02 FF不會被做爲結果返回。因此,這個方案不可行。

正確的scan範圍必須知足:

  • startRow:必須必任何userId = 2的rowkey都小,且比任何userId = 1的rowkey都大
  • stopRow:必須必任何userId = 2的rowkey都大,且比任何userId = 3的rowkey都小

那如何利用rowkey的排序規則來「找到」這樣一個掃描範圍呢?

正確的掃描範圍是[0x02, 0x03)。

0x02比任何userid = 2的行都小。由於ts這一列是缺失的。同理,0x03比任何userid = 2的行都大,又比任何userId = 3的行都小。可見,要實現前綴掃描,只根據前綴的值就能夠獲得所需的startRow和stopRow,而不須要知道後面的列及其含義。

請讀者仔細體會這個例子,而後思考下面幾個場景該如何構造startRow和stopRow(答案見文末)。

  • where userid = 2 and ts >= 5 and ts < 20
  • where userid = 2 and ts > 5 and ts < 20
  • where userid = 2 and ts > 5 and ts <= 20
  • where userid > 2 and userid < 4

還有下面這些組合場景:

  • where userid in (3, 5, 7, 9)
  • where userid = 2 and ts in (10, 20, 30)

如今,已經能夠感覺到使用scan的難點和痛點所在了。在上面的例子中,只有兩個定長的列,但在實際業務中,列多是變長的,有各類各樣的數據類型,各類豐富的查詢模式。此時,構造一個正確、高效的scan是有難度的。那爲何會有這些問題呢?有沒有系統性的解決方案呢?

從形式是看,這是一個「如何將業務查詢邏輯轉換爲HBase的查詢邏輯」的問題,本質上是關係表模型到KV模型的映射問題。HBase僅提供了KV層的API,使得用戶不得不本身實現這兩個模型之間的轉換。因此,纔會有上面這麼多的難點問題。不只是HBase,全部的KV存儲系統在面臨複雜的業務模型時,都面臨相同的困境。

這個問題的解法是SQL on NoSQL,業界這類方案有不少(如Hive,presto等),HBase之上的方案就是Phoenix。此類方案經過引入SQL來解決NoSQL的易用性問題。對於傳統的關係型數據庫,雖然有強大的SQL和事務支持,但擴展性和性能受限,爲了解決性能問題,MySQL提供了基於Memcached的KV訪問方式;爲了解決擴展性問題,有了各類NewSQL的產品,如Spanner/F1,TiDB,CockroachDB等。NoSQL在作SQL,支持SQL的在作KV,咱們能夠想象一下將來的存儲、數據庫系統會是什麼樣子。這個話題很大,不在本文的討論範圍內,這裏就不展開了。

region的元數據管理與路由

前面咱們討論了將一張表的行經過合理的region劃分,能夠獲得數據量大體接近的region分佈。經過合理的運維手段(region的分裂與合併),咱們能夠通保證在系統持續運行期間的region分佈均勻。此時,數據在邏輯上的拆分已經能夠實現均勻。本節中咱們看一下region如何分佈在RS上,以及客戶端如何定位region。

由於region的rowkey範圍自己的不肯定性或者主觀性(人爲拆分),沒法經過一個數學公式來計算rowkey屬於哪一個region(對比一致性hash的分片方式)。所以,基於範圍進行的分片方式,須要一個元數據表來記錄一個表被劃分爲哪些region,每一個region的起止rowkey是什麼。這個元數據表就是meta表,在HBase1.x版本中表名是「hbase:meta」(在094或更老的版本中,是-ROOT-和.META.兩個元數據表)。

咱們從Put操做來簡要的瞭解region的定位過程。

  • ZK上找meta表所在的RS(緩存)
  • 到meta表上找rowkey所在的region及這個region所在的RS(緩存)
  • 發Put請求給這個RS,RS根據region名字來執行寫操做
  • 若是RS發現這個region不在本身這裏,拋異常,客戶端從新路由

不管讀仍是寫,其定位region的邏輯都是如此。爲了下降客戶端對meta表的訪問,客戶端會緩存region location信息,當且僅當緩存不正確時,才須要訪問meta表來獲取最新的信息。因此,HBase的請求路由是一種基於路由表的解決方案。相對應的,基於一致性Hash的分片方式,則是經過計算來獲得分佈信息的。

這種基於路由表的方式

  • 優勢:region的歸屬RS能夠任意更換,或者說,region在RS上的調度是靈活的、可人工干預的。
  • 缺點:meta表是一個單點,其有限的吞吐限制了集羣的規模和客戶端數量

region的靈活調度,結合存儲計算分離的架構,賦予了HBase極其強大的能力。

  • 秒級擴容:新加入的RS只須要移動region便可當即投產,不依賴數據的遷移(後續慢慢遷)
  • 人工隔離:對於有問題的region(如熱點,有異常請求),能夠手工移動到一臺單獨的RS上,進行故障域的快速隔離。

這兩點,是衆多基於一致性hash的分片方案沒法作到的。固然,爲了得到這種靈活性,HBase所付出的代價就是複雜的meta表管理機制。其中比較關鍵的問題就是meta表的單點問題。例如:大量的客戶端都會請求meta表來獲取region location,meta表的負載較高,會限制獲取location的總體吞吐,從而限制集羣的規模和客戶端規模。

對於一個擁有數百臺機器,數十萬region的集羣來講,這套機制能夠很好的工做。但當集羣規模進一步擴展,觸及到meta表的訪問上限時,就會因meta表的訪問阻塞而影響服務。固然,絕大多數的業務場景都是沒法觸達這個臨界規模的。

meta表的問題能夠有不少種解決思路,最簡單的方式就是副本。例如TiDB的PD服務,獲取location的請求能夠發送給任何一臺PD服務器。

region的調度

下面咱們討論region調度問題:

  • region在RS之間的負載均衡
  • 同一個region在多個RS上調度

對於第一個問題,HBase的默認均衡策略是:以表爲單位,每一個RS上調度儘量相同數量的region。

這個策略假設各個region的數據量分佈相對均勻,每一個region的請求相對均勻。此時,該策略很是有效。這也是目前使用最多的一種。同時,HBase也提供了基於負載的調度(StochasticLoadBalancer),會綜合考慮多種因素來進行調度決策,不過,暫時缺乏生產環境使用的案例和數據。

對於第二個問題,region同一時間只在一臺RS上調度,使得HBase在請求成功的狀況下提供了強一致的語義,即寫成功的數據能夠當即被讀到。其代價是region的單點調度,即region所在的服務器由於各類緣由產生抖動,都會影響這個region的服務質量。咱們可將影響region服務的問題分爲兩類:

  • 不可預期的:宕機恢復,GC,網絡問題,磁盤抖動,硬件問題等等
  • 可預期的(或人爲的):擴容/縮容致使的region移動,region split/merge等。

這些事件發生時,會對這個region的服務或多或少產生一些影響。尤爲在宕機場景,從ZK發現節點宕機到region的re-assign,split log,log replay,一些列步驟執行完,通常都須要1分鐘以上的時間。對於宕機節點上的region,意味着這段時間這些region都沒法服務。

解決方案依然是副本方案,讓region在多個RS上調度,客戶端選擇其中一個進行訪問,這個特性叫「region replia」。引入副本必然帶來額外的成本和一致性問題。目前這個特性的實現並未下降MTTR時間,內存水位的控制、髒讀,使得這個特性仍未在生產中大規模使用。

總結

Hbase的數據分佈與region調度問題,放大到整個分佈式系統中,是任務的拆分與調度問題,這個話題的內涵大到足以寫幾本書。本文只是從HBase這個切面來對數據分片這個話題進行一些討論,但願可以加深讀者對HBase rowkey和region概念的思考和理解,不管是數據庫的用戶仍是開發,都可以從這個討論中有所收穫。

附錄

正文中一些查詢場景所對應的scan range:

  • where userid = 2 and ts >= 5 and ts < 20: [0x02 05, 0x02 14)
  • where userid = 2 and ts > 5 and ts < 20: [0x02 06, 0x02 14)
  • where userid = 2 and ts > 5 and ts <= 20: [0x02 06, 0x02 15)
  • where userid > 2 and userid < 5: [0x03, 0x05)

本文做者:楊晗

閱讀原文

本文爲雲棲社區原創內容,未經容許不得轉載。

相關文章
相關標籤/搜索