一文搞懂 HBase 的基本原理

本文會對 HBase 的基本原理進行剖析,通過本文你可以瞭解到:

從 BigTable 說起

HBase 是在谷歌 BigTable 的基礎之上進行開源實現的,是一個高可靠、高性能、面向列、可伸縮的分佈式數據庫,可以用來存儲非結構化和半結構化的稀疏數據。HBase 支持超大規模數據存儲,可以通過水平擴展的方式處理超過 10 億行數據和百萬列元素組成的數據表。

BigTable 是一個分佈式存儲系統,利用谷歌提出的 MapReduce 分佈式並行計算模型來處理海量數據,使用谷歌分佈式文件系統 GFS 作爲底層的數據存儲,並採用 Chubby 提供協同服務管理,具備廣泛的應用型、可擴展性、高可用性及高性能性等特點。關於 BigTable 與 HBase 的對比,見下表:

RX4p4D

CAP 理論

2000 年,Berkerly 大學有位 Eric Brewer 教授提出了一個 CAP 理論,在 2002 年,麻省理工學院的Seth Gilbert(賽斯·吉爾伯特)Nancy Lynch(南希·林奇)發表了布魯爾猜想的證明,證明了 CAP 理論的正確性。所謂 CAP 理論,是指對於一個分佈式計算系統來說,不可能同時滿足以下三點:

如上圖所示:一個分佈式的系統不可能同時滿足一致性、可用性和分區容錯性,最多同時滿足兩個。當處理 CAP 的問題時,可以有一下幾個選擇:

爲什麼出現 NoSQL

所謂 NoSQL,即 Not Only SQL 的縮寫,意思是不只是 SQL。上面提到的 CAP 理論正是 NoSQL 的設計原則。那麼,爲什麼會興起 NoSQL 數據庫呢? 因爲 WEB2.0 以及大數據時代的到來,關係型數據庫越來越不能滿足需求。大數據、物聯網、移動互聯網和雲計算的發展,使得非結構化的數據比例高達 90% 以上,關係型數據庫由於模型不靈活以及擴展水平較差,在面對大數據時,暴露出了越來越多的缺陷。由此 NoSQL 數據庫應運而生,更好地滿足了大數據時代及 WEB2.0 的需求。

面對 WEB2.0 以及大數據的挑戰,關係型數據庫在以下幾個方面表現欠佳:

綜上,NoSQL 數據庫應運而生,是 IT 發展的必然。

HBase 的特點及使用場景

特點

HBase 不是 最終一致性(eventually consistent) 數據存儲. 這讓它很適合高速計數聚合類任務

HBase 表通過 region 分佈在集羣中。數據增長時,region 會自動分割並重新分佈

HBase 支持本機外 HDFS 作爲它的分佈式文件系統

HBase 通過 MapReduce 支持大併發處理, HBase 可以同時做源 (Source) 和匯(Sink)

HBase 支持易於使用的 Java API 進行編程訪問

支持 Thrift 和 REST 的方式訪問 HBase

HBase 支持 Block Cache 和 布隆過濾器進行查詢優化,提升查詢性能

HBase 提供內置的用於運維的網頁和 JMX 指標

使用場景

HBase 並不適合所有場景

首先,** 數據量方面 **。確信有足夠多數據,如果有上億或十億行數據,至少單表數據量超過千萬,HBase 會是一個很好的選擇。如果只有上千或上百萬行,用傳統的 RDBMS 可能是更好的選擇。

其次,關係型數據庫特性方面。確信可以不依賴所有 RDBMS 的額外特性 (如列數據類型、二級索引、事務、高級查詢語言等) 。一個建立在 RDBMS 上應用,並不能通過簡單的改變 JDBC 驅動就能遷移到 HBase,需要一次完全的重新設計。

再次,硬件方面。確信你有足夠硬件。比如由於 HDFS 的默認副本是 3,所以一般至少 5 個數據節點才能夠發揮其特性,另外 還要加上一個 NameNode 節點。

最後,數據分析方面。數據分析是 HBase 的弱項,因爲對於 HBase 乃至整個 NoSQL 生態圈來說,基本上都是不支持表關聯的。如果主要需求是數據分析,比如做報表,顯然 HBase 是不太合適的。

HBase 的數據模型

基本術語

HBase 是一個稀疏、多維、持久化存儲的映射表,採用的 row key、列族、列限定符合時間戳進行索引,每個 cell 的值都是字節數組 byte[]。瞭解 HBase 需要先知道下面的一些概念:

概念模型

在 HBase 概念模型中,一個表可以被看做是一個稀疏的、多維的映射關係,如下圖所示:

如上表所示:

該表包含兩行數據,分別爲com.cnn.wwwcom.example.www;

三個列族,分別爲:contents, anchorpeople

對於第一行數據 (對應的 row key 爲com.cnn.www), 列族anchor包含兩列:anchor:cssnsi.comanchor:my.look.ca; 列族contents包含一列:contents:html;

對於第一行數據 (對應的 row key 爲com.cnn.www), 包含 5 個版本的數據

對於第二行數據 (對應的 row key 爲com.example.www), 包含 1 個版本的數據

上表中可以通過一個四維座標定位一個單元格數據:[row key, 列族, 列, 時間戳],比如 [com.cnn.www,contents,contents:html,t6]

物理模型

從概念模型上看,HBase 的表是稀疏的。在物理存儲的時候,是按照列族進行存儲的。一個列限定符 (column_family:column_qualifier) 可以被隨時添加到已經存在的列族上。

從物理模型上看,概念模型中存在的空單元格是不會被存儲的。比如要訪問contents:html,時間戳爲t8, 則不會返回值。值得注意的是,如果訪問數據時沒有指定時間戳,則默認訪問最新版本的數據,因爲數據是按照版本時間戳降序排列的。

如上表:如果訪問行com.cnn.www,列contents:html,在沒有指定時間戳的情況下,則返回t6對應的數據; 同理如果訪問anchor:cnnsi.com, 則返回t9對應的數據。

HBase 的原理及運行機制

整體架構

通過上面的描述,應該對 HBase 有了一定的瞭解。現在我們在來看一下 HBase 的宏觀架構,如下圖:

我們先從宏觀的角度看一下 HBase 的整體架構。從 HBase 的部署架構上來說,HBase 有兩種服務器:Master服務器RegionServer服務器。一般一個 HBase 集羣有一個 Master 服務器和幾個 RegionServer 服務器。

Master服務器負責維護表結構信息,實際的數據都存儲在 RegionServer 服務器上。在 HBase 的集羣中,客戶端獲取數據由客戶端直連 RegionServer 的,所以你會發現 Master 掛掉之後你依然可以查詢數據,但是不能創建新的表了。

我們都知道,在 Hadoop 採用的是 master-slave 架構,即 namenode 節點爲主節點,datanode 節點爲從節點。namenode 節點對於 hadoop 集羣而言至關重要,如果 namenode 節點掛了,那麼整個集羣也就癱瘓了。

但是,在 HBase 集羣中,Master 服務的作用並沒有那麼的重要。雖然是 Master 節點,其實並不是一個 leader 的角色。Master 服務更像是一個‘打雜’的,類似於一個輔助者的角色。因爲當我們連接 HBase 集羣時,客戶端會直接從 Zookeeper 中獲取 RegionServer 的地址,然後從 RegionServer 中獲取想要的數據,不需要經過 Master 節點。除此之外,當我們向 HBase 表中插入數據刪除數據等操作時,也都是直接跟 RegionServer 交互的,不需要 Master 服務參與。

那麼,Master 服務有什麼作用呢?Master 只負責各種協調工作,比如建表刪表移動 Region合併等操作。這些操作有一個共性的問題:就是需要跨 RegionServer。所以,HBase 就將這些工作分配給了 Master 服務。這種結構的好處是大大降低了集羣對 Master 的依賴。而 Master 節點一般只有一個到兩個,一旦宕機,如果集羣對 Master 的依賴度很大,那麼就會產生單點故障問題。在 HBase 中即使 Master 宕機了,集羣依然可以正常地運行,依然可以存儲和刪除數據。

RegionServer 就是存放 Region 的容器,直觀上說就是服務器上的一個服務。RegionServer 是真正存儲數據的節點,最終存儲在分佈式文件系統 HDFS。當客戶端從 ZooKeeper 獲取 RegionServer 的地址後,它會直接從 RegionServer 獲取數據。對於 HBase 集羣而言,其重要性要比 Master 服務大。

RegionServer 非常依賴 ZooKeeper 服務,ZooKeeper 在 HBase 中扮演的角色類似一個管家。ZooKeeper 管理了 HBase 所有 RegionServer 的信息,包括具體的數據段存放在哪個 RegionServer 上。客戶端每次與 HBase 連接,其實都是先與 ZooKeeper 通信,查詢出哪個 RegionServer 需要連接,然後再連接 RegionServer。

我們可以通過 zkCli 訪問 hbase 節點的數據,通過下面命名可以獲取 hbase:meta 表的信息:

[zk: localhost:2181(CONNECTED) 17] get /hbase/meta-region-server

簡單總結 Zookeeper 在 HBase 集羣中的作用如下:對於服務端,是實現集羣協調與控制的重要依賴。對於客戶端,是查詢與操作數據必不可少的一部分

需要注意的是:當 Master 服務掛掉時,依然可以進行能讀能寫操作;但是把 ZooKeeper 一旦掛掉,就不能讀取數據了,因爲讀取數據所需要的元數據表 hbase:meata 的位置存儲在 ZooKeeper 上。可見 zookeeper 對於 HBase 而言是至關重要的。

Region 就是一段數據的集合。HBase 中的表一般擁有一個到多個 Region。Region 不能跨服務器,一個 RegionServer 上有一個或者多個 Region。當開始創建表時,數據量小的時候,一個 Region 足以存儲所有數據,等到數據量逐漸增加,會拆分爲多個 region;當 HBase 在進行負載均衡的時候,也有可能會從一臺 RegionServer 上把 Region 移動到另一臺 RegionServer 上。Region 是存儲在 HDFS 的,它的所有數據存取操作都是調用了 HDFS 的客戶端接口來實現的。一個 Region 就相當於關係型數據庫中分區表的一個分區。

微觀架構

上一小節對 HBase 的整體架構進行了說明,接下來再看一下內部細節,如下圖所示:展示了一臺 RegionServer 的內部架構。

如上圖所示:一個 RegionServer 可以存儲多個 region,Region 相當於一個數據分片。每一個 Region 都有起 始 rowkey 和結束 rowkey,代表了它所存儲的 row 範圍。在一個 region 內部,包括多個 store,其中一個 store 對應一個列族,每個 store 的內部又包含一個 MemStore,主要負責數據排序,等超過一定閾值之後將 MemStore 的數據刷到 HFile 文件,HFile 文件時最終存儲數據的地方。

值得注意的是:一臺 RegionServer 共用一個 WAL(Write-Ahead Log) 預寫日誌,如果開啓了 WAL,那麼當寫數據時會先寫進 WAL,可以起到容錯作用。WAL 是一個保險機制,數據在寫到 Memstore 之前,先被寫到 WAL 了。這樣當故障恢復的時候可以從 WAL 中恢復數據。另外,每個 Store 都有一個 MemStore,用於數據排序。一臺 RegionServer 也只有一個 BlockCache,用於讀數據是進行緩存。

Write Ahead Log (WAL) 會記錄 HBase 中的所有數據,WAL 起到容錯恢復的作用,並不是必須的選項。在 HDFS 上,WAL 的默認路徑是/hbase/WALs/, 用戶可以通過hbase.wal.dir進行配置。

WAL 默認是開啓的,如果關閉,可以使用下面的命令Mutation.setDurability(Durability.SKIP_WAL)。WAL 支持異步和同步的寫入方式,異步方式通過調用下面的方法Mutation.setDurability(Durability.ASYNC_WAL)。同步方式通過調用下面的方法:Mutation.setDurability(Durability.SYNC_WAL),其中同步方式是默認的方式。

關於異步 WAL,當有 Put、Delete、Append 操作時,並不會立即觸發同步數據。而是要等到一定的時間間隔,該時間間隔可以通過參數hbase.regionserver.optionallogflushinterval進行設定,默認是 1000ms。

每個 Store 中有一個 MemStore 實例。數據寫入 WAL 之後就會被放入 MemStore。MemStore 是內存的存儲對象,只有當 MemStore 滿了的時候纔會將數據刷寫(flush)到 HFile 中。

爲了讓數據順序存儲從而提高讀取效率,HBase 使用了 LSM 樹結構來存儲數據。數據會先在 Memstore 中 整理成 LSM 樹,最後再刷寫到 HFile 上。

關於 MemStore,很容易讓人混淆。數據在被刷到 HFile 之前,已經被存儲到了 HDFS 的 WAL 上了,那麼爲什麼還要在放入 MemStore 呢?其實很簡單,我們都知道 HDFS 是不能修改的,而 HBase 的數據又是按照 Row Key 進行排序的,其實這個排序的過程就是在 MemStore 中進行的。值得注意的是:MemStore 的作用不是爲了加快寫速度,而是爲了對 Row Key 進行排序。

HFile 是數據存儲的實際載體,我們創建的所有表、列等數據都存儲在 HFile 裏面。當 Memstore 達到一定閥值,或者達到了刷寫時間間隔閥值的時候,HBaes 會被這個 Memstore 的內容刷寫到 HDFS 系統上,稱爲一個存儲在硬盤上的 HFile 文件。至此,我們數據真正地被持久化到硬盤上。

Region 的定位

在開始講解 HBase 的數據讀寫流程之前,先來看一下 Region 是怎麼定位的。我們知道 Region 是 HBase 非常重要的一個概念,Region 存儲在 RegionServer 中,那麼客戶端在讀寫操作時是如何定位到所需要的 region 呢?關於這個問題,老版本的 HBase 與新版本的 HBase 有所不同。

老版本 HBase(0.96.0 之前)

老版本的 HBase 採用的是爲三層查詢架構,如下圖所示:

如上圖:第一層定位是 Zookeeper 中的節點數據,記錄了-ROOT-表的位置信息;

第二層-ROOT-表記錄了.META.region 位置信息,-ROOT-表只有一個 region,通過-ROOT-表可以訪問.META.表中的數據

第三層.META.表,記錄了用戶數據表的 region 位置信息,.META.表可以有多個 region。

整個查詢步驟如下:

第一步:用戶通過查找 zk(ZooKeeper)的 / hbase/root-regionserver 節點來知道-ROOT-表的 RegionServer 位置。

第二步:訪問-ROOT-表,查找所需要的數據表的元數據信息存在哪個.META.表上,這個.META.表在哪個 RegionServer 上。

第四步:訪問.META.表來看你要查詢的行鍵在什麼 Region 範圍裏面。

第五步:連接具體的數據所在的 RegionServer,這個一步纔開始在很正的查詢數據。

新版本 HBase

老版本的 HBase 尋址存在很多弊端,在新版本中進行了改進。採用的是二級尋址的方式,僅僅使用 hbase:meta表來定位 region,那麼 從哪裏獲取hbase:meta的信息呢,答案是 zookeeper。在 zookeeper 中存在一個/hbase/meta-region-server節點,可以獲取hbase:meta表的位置信息,然後通過hbase:meta表查詢所需要數據所在的 region 位置信息。

整個查詢步驟如下:

第一步:客戶端先通過 ZooKeeper 的/hbase/meta-region-server節點查詢hbase:meta表的位置。

第二步:客戶端連接hbase:meta表所在的 RegionServer。hbase:meta表存儲了所有 Region 的行鍵範圍信息,通過這個表就可以查詢出你要存取的 rowkey 屬於哪個 Region 的範圍裏面,以及這個 Region 屬於哪個 RegionServer。

第三步:獲取這些信息後,客戶端就可以直連擁有你要存取的 rowkey 的 RegionServer,並直接對其操作。

第四步:客戶端會把 meta 信息緩存起來,下次操作就不需要進行以上加載 hbase:meta 的步驟了。

客戶端 API 基本使用

public class Example {

  private static final String TABLE_NAME = "MY_TABLE_NAME_TOO";
  private static final String CF_DEFAULT = "DEFAULT_COLUMN_FAMILY";

  public static void createOrOverwrite(Admin admin, HTableDescriptor table) throws IOException {
    if (admin.tableExists(table.getTableName())) {
      admin.disableTable(table.getTableName());
      admin.deleteTable(table.getTableName());
    }
    admin.createTable(table);
  }

  public static void createSchemaTables(Configuration config) throws IOException {
    try (Connection connection = ConnectionFactory.createConnection(config);
         Admin admin = connection.getAdmin()) {

      HTableDescriptor table = new HTableDescriptor(TableName.valueOf(TABLE_NAME));
      table.addFamily(new HColumnDescriptor(CF_DEFAULT).setCompressionType(Algorithm.NONE));

      System.out.print("Creating table. ");
      createOrOverwrite(admin, table);
      System.out.println(" Done.");
    }
  }

  public static void modifySchema (Configuration config) throws IOException {
    try (Connection connection = ConnectionFactory.createConnection(config);
         Admin admin = connection.getAdmin()) {

      TableName tableName = TableName.valueOf(TABLE_NAME);
      if (!admin.tableExists(tableName)) {
        System.out.println("Table does not exist.");
        System.exit(-1);
      }

      HTableDescriptor table = admin.getTableDescriptor(tableName);

      // 更新table
      HColumnDescriptor newColumn = new HColumnDescriptor("NEWCF");
      newColumn.setCompactionCompressionType(Algorithm.GZ);
      newColumn.setMaxVersions(HConstants.ALL_VERSIONS);
      admin.addColumn(tableName, newColumn);

      // 更新column family
      HColumnDescriptor existingColumn = new HColumnDescriptor(CF_DEFAULT);
      existingColumn.setCompactionCompressionType(Algorithm.GZ);
      existingColumn.setMaxVersions(HConstants.ALL_VERSIONS);
      table.modifyFamily(existingColumn);
      admin.modifyTable(tableName, table);

      // 禁用table
      admin.disableTable(tableName);

      // 刪除column family
      admin.deleteColumn(tableName, CF_DEFAULT.getBytes("UTF-8"));

      // 刪除表,首先要禁用表
      admin.deleteTable(tableName);
    }
  }

  public static void main(String... args) throws IOException {
    Configuration config = HBaseConfiguration.create();

    config.addResource(new Path(System.getenv("HBASE_CONF_DIR")"hbase-site.xml"));
    config.addResource(new Path(System.getenv("HADOOP_CONF_DIR")"core-site.xml"));
    createSchemaTables(config);
    modifySchema(config);
  }
}

易混淆知識點總結

Q1:MemStore 的作用是什麼?

在 HBase 中,一個表可以有多個列族,一個列族在物理上是存儲在一起的,一個列族會對應一個 store,在 store 的內部會存在一個 MemStore,其作用並不是爲了提升讀寫速度,而是爲了對 RowKey 進行排序。我們知道,HBase 的數據是存儲在 HDFS 上的,而 HDFS 是不支持修改的,HBase 爲了按 RowKey 進行排序,首先會將數據寫入 MemStore,數據會先在 Memstore 中整理成 LSM 樹,最後再刷寫到 HFile 上。

總之一句話:Memstore 的實現目的不是加速數據寫入或讀取,而是維持數據結構。

Q2:讀取數據時會先從 MemStore 讀取嗎?

MemStore 的作用是爲了按 RowKey 進行排序,其作用不是爲了提升讀取速度的。讀取數據的時候是有專門的緩存叫BlockCache,如果開啓了 BlockCache,就是先讀 BlockCache,然後纔是讀 HFile+Memstore 的數據。

Q3:BlockCache 有什麼用?

塊緩存(BlockCache)使用內存來記錄數據,適用於提升讀取性能。當開啓了塊緩存後,HBase 會優先從塊緩存中查詢是否有記錄,如果沒有才去檢索存儲在硬盤上的 HFile。

值得注意的是,一個 RegionServer 只有一個 BlockCache。BlockCache 不是數據存儲的必須組成部分,只是用來優化讀取性能的。

BlockCache 的基本原理是:在讀請求到 HBase 之後,會先嚐試查詢 BlockCache,如果獲取不到所需的數據,就去 HFile 和 Memstore 中去獲取。如果獲取到了,則在返回數據的同時把 Block 塊緩存到 BlockCache 中。

Q4:HBase 是怎麼刪除數據的?

HBase 刪除記錄並不是真的刪除了數據,而是標識了一個墓碑標記(tombstone marker),把這個版本連同之前的版本都標記爲不可見了。這是爲了性能着想,這樣 HBase 就可以定期去清理這些已經被刪除的記錄,而不用每次都進行刪除操作。所謂定期清理,就是按照一定時間週期在 HBase 做自動合併(compaction,HBase 整理存儲文件時的一個操作,會把多個文件塊合併成一個文件)。這樣刪除操作對於 HBase 的性能影響被降到了最低,即便是在很高的併發負載下大量刪除記錄也是 OK 的。

合併操作分爲兩種:Minor CompactionMajor Compaction

其中 Minor Compaction 是將 Store多個 HFile 合併爲一個 HFile。在這個過程中達到 TTL 的數據會被移除,但是被手動刪除的數據不會被移除。這種合併觸發頻率較高。

Major Compaction 合併 Store 中的所有 HFile 爲一個 HFile。在這個過程中被手動刪除的數據會被真正地移除。同時被刪除的還有單元格內超過 MaxVersions 的版本數據。這種合併觸發頻率較低,默認爲 7 天一次。不過由於 Major Compaction 消耗的性能較大,一般建議手動控制 MajorCompaction 的時機。

需要注意的是:Major Compaction 刪除的是那些帶墓碑標記的數據,而 Minor Compaction 合併的時候直接會忽略過期數據文件,所以過期的這些文件會在 Minor Compaction 的時候就被刪除。

Q5:爲什麼 HBase 具有高性能的讀寫能力?

因爲 HBase 使用了一種 LSM 的存儲結構,在 LSM 樹的實現方式中,會在數據存儲之前先對數據進行排序。LSM 樹是 Google BigTable 和 HBase 的基本存儲算法,它是傳統關係型數據庫的 B + 樹的改進。算法的核心在於儘量保證數據是順序存儲到磁盤上的,並且會有頻率地對數據進行整理,確保其順序性。

LSM 樹就是一堆小樹,在內存中的小樹即 memstore,每次 flush,內存中的 memstore 變成磁盤上一個新的 storefile。這種批量的讀寫操作使得 HBase 的性能較高。

Q6:Store 與列簇是什麼關係?

Region 是 HBase 的核心模塊,而 Store 則是 Region 的核心模塊。每個 Store 對應了表中的一個列族存儲。每個 Store 包含一個 MemStore 和若干個 HFile。

Q7:WAL 是 RegionServer 共享的,還是 Region 級別共享的?

在 HBase 中,每個 RegionServer 只需要維護一個 WAL,所有 Region 對象共用一個 WAL,而不是每個 Region 都維護一個 WAL。這種方式對於多個 Region 的更新操作所發生的的日誌修改,只需要不斷地追加到單個日誌文件中,不需要同時打開並寫入多個日誌文件,這樣可以減少磁盤尋址次數,提高寫性能。

但是這種方式也存在一個缺點,如果 RegionServer 發生故障,爲了恢復其上的 Region 對象,需要將 RegionServer 上的 WAL 按照其所屬的 Region 對象進行拆分,然後分發到其他 RegionServer 上執行恢復操作。

Q8:Master 掛掉之後,還能查詢數據嗎?

可以的。Master 服務主要負責表和 Region 的管理工作。主要作用有:

客戶端訪問 HBase 時,不需要 Master 的參與,只需要連接 zookeeper 獲取 hbase:meta 地址,然後直連 RegionServer 進行數據讀寫操作,Master 僅僅維護表和 Region 的元數據信息,負載很小。但是 Master 節點也不能長時間的宕機。

總結

本文首先從谷歌的 BigTable 說起,然後介紹了 CAP 相關理論,並分析了 NoSQL 出現的原因。接着對 HBase 的數據模型進行了剖析,然後詳細描述了 HBase 的原理和運行機制。最後給出了客戶端 API 的基本使用,並對常見的、易混淆的知識點進行了解釋。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/su3HYQGJQ7pC-pxYuLen4A