HBase 初探:架構 + 原理 + 對比 + 實踐

因為工作需要使用 HBase,因此調研了 HBase 相關的內容。本文的寫作目的不僅僅是對前期工作的總結,也希望能幫助到工作繁忙但又想了解 HBase 的同學。在本文寫作過程中,將穿插 MySQL 相關內容,希望能幫助理解 HBase 。

本文主要討論以下幾個問題,所述內容僅為個人思考,見解有限,有誤之處還望批評指正。

  • HBase 是什麼?其架構是怎樣的?
  • HBase 如何管理數據?
  • HBase 是分佈式數據庫,那數據怎麼路由?
  • HBase 的適用場景?
  • HBase 和 MySQL 的主要區別?
  • HBase 怎麼用?如何實現 CREATE、INSERT、SELECT、UPDATE、DELETE、LIKE 操作?

1 HBase

1.1 HBase 架構

HBase 是什麼?其架構是怎樣的?

HBase(Hadoop DataBase),是一種非關係型分佈式數據庫(NoSQL),支持海量數據存儲(官方:單表支持百億行百萬列)。HBase 採用經典的主從架構,底層依賴於 HDFS,並藉助 ZooKeeper 作為協同服務,其架構大致如下:

HBase 初探:架構 + 原理 + 對比 + 實踐

其中,

  • Master:HBase 管理節點。管理 Region Server,分配 Region 到 Region Server,提供負載均衡能力;執行創建表等 DDL 操作。
  • Region Server:HBase 數據節點。管理 Region,一個 Region Server 可包含多個 Region,Region 相當於表的分區。客戶端可直接與 Region Server 通信,實現數據增刪改查等 DML 操作。
  • ZooKeeper:協調中心。負責 Master 選舉,節點協調,存儲 hbase:meta 等元數據。
  • HDFS:底層存儲系統。負責存儲數據,Region 中的數據通過 HDFS 存儲。

對 HBase 全局有了基本理解後,我認為有幾個比較重要的點值得關注:HBase 數據模型、Region 的概念、數據路由。

1.2 HBase 數據模型

HBase 如何管理數據?(邏輯層)

HBase 的數據模型和 MySQL 等關係型數據庫有比較大的區別,其是一種 ”Schema-Flexiable“ 的理念。

  1. 在表的維度,其包含若干行,每一行以 RowKey 來區分。
  2. 在行的維度,其包含若干列族,列族類似列的歸類,但不只是邏輯概念,底層物理存儲也是以列族來區分的(一個列族對應不同 Region 中的一個 Store)。
  3. 在列族的維度,其包含若干列,列是動態的。與其說是列,不如說是一個個鍵值對,Key 是列名,Value 是列值。

HBase 的表結構如下:

HBase 初探:架構 + 原理 + 對比 + 實踐

  • RowKey(行鍵):RowKey 是字典有序的,HBase 基於 RowKey 實現索引;
  • Column Family(列族):縱向切割,一行可有多個列族,一個列族可有任意個列;
  • Key-Value(鍵值對):每一列存儲的是一個鍵值對,Key 是列名,Value 是列值;
  • Byte(數據類型):數據在 HBase 中以 Byte 存儲,實際的數據類型交由用戶轉換;
  • Version(多版本):每一列都可配置相應的版本,獲取指定版本的數據(默認返回最新版本);
  • 稀疏矩陣:行與行之間的列數可以不同,但只有實際的列才會佔用存儲空間。

1.3 Region

HBase 如何管理數據?(物理層)

Region 是 HBase 中的概念,類似 RDBMS 中的分區。

  1. Region 是表的橫向切割,一個表由一個或多個 Region 組成,Region 被分配到各個 Region Server;
  2. 一個 Region 根據列族分為多個 Store,每個 Store 由 MemStore 和 StoreFile 組成;數據寫入 MemStore,MemStore 類似輸入緩衝區,持久化後為 StoreFile;數據寫入的同時會更新日誌 WAL,WAL 用於發生故障後的恢復,保障數據讀寫安全;
  3. 一個 StoreFile 對應一個 HFile,HFile 存儲在 HDFS 。

下面是我梳理的大致模型:

HBase 初探:架構 + 原理 + 對比 + 實踐

1)Region 是一個 RowKey Range

每個 Region 實際上是一個 RowKey Range,比如 Region A 存放的 RowKey 區間為 [aaa,bbb),Region B 存放的 RowKey 區間為 [bbb,ccc) ,以此類推。Region 在 Region Server 中存儲也是有序的,Region A 必定在 Region B 前面。

注:這裡將 RowKey 設計為 aaa,而不是 1001 這樣的數字,是為了強調 RowKey 並非只能是數字,只要能進行字典排序的字符都是可以的,如:abc-123456 。

HBase 初探:架構 + 原理 + 對比 + 實踐

2)數據被路由到各個 Region

表由一個或多個 Region 組成(邏輯),Region Server 包含一個或多個 Region(物理)。數據的路由首先要定位數據存儲在哪張表的哪個 Region,表的定位直接根據表名,Region 的定位則根據 RowKey(因為每個 Region 都是一個 RowKey Range,因此根據 RowKey 很容易知道其對應的 Region)。

注:Master 默認採用 DefaultLoadBalancer 策略分配 Region 給 Region Server,類似輪詢方式,可保證每個 Region Server 擁有相同數量的 Region(這裡只是 Region 的數量相同,但還是有可能出現熱點聚集在某個 Region,從而導致熱點聚集在某個 Region Server 的情況)。

HBase 初探:架構 + 原理 + 對比 + 實踐

3)當一個表太大時,Region 將自動分裂

  • 自動分裂

0.94 版本之前,Region 分裂策略為
ConstantSizeRegionSplitPolicy ,根據一個固定值觸發分裂。

0.94 版本之後,分裂策略默認為
IncreasingToUpperBoundRegionSplitPolicy,該策略會根據 Region 數量和 StoreFile 的最大值決策。當 Region 的數量小於 9 且 StoreFile 的最大值小於某個值時,分裂 Region;當Region數量大於9 時,採用


ConstantSizeRegionSplitPolicy 。

  • 手動分裂


ConstantSizeRegionSplitPolicy 下,通過設置
hbase.hregion.max.filesize 控制 Region 分裂。

HBase 初探:架構 + 原理 + 對比 + 實踐

1.4 數據路由 hbase:meta

HBase 是分佈式數據庫,那數據怎麼路由?

數據路由藉助 hbase:meta 表完成,hbase:meta 記錄的是所有 Region 的元數據信息,hbase:meta 的位置記錄在 ZooKeeper 。

注:一些比較老的文章可能會提到 .root 和 .meta 兩個表。事實上, .root 和 .meta 兩個表是 HBase 0.96 版本之前的設計。在 0.96 版本後,.root 表已經被移除,.meta 表更名為 hbase:meta。

hbase:meta 表的格式如下:

HBase 初探:架構 + 原理 + 對比 + 實踐

其中,

  • table:表名;
  • region start key:Region 中的第一個 RowKey,如果 region start key 為空,表示該 Region 是第一個 Region;
  • region id:Region 的 ID,通常是 Region 創建時的 timestamp;
  • regioninfo:該 Region 的 HRegionInfo 的序列化值;
  • server:該 Region 所在的 Region Server 的地址;
  • serverstartcode:該 Region 所在的 Region Server 的啟動時間。

一條數據的寫入流程:

數據寫入時需要指定表名、Rowkey、數據內容。

  1. HBase 客戶端訪問 ZooKeeper,獲取 hbase:meta 的地址,並緩存該地址;
  2. 訪問相應 Region Server 的 hbase:meta;
  3. 從 hbase:meta 表獲取 RowKey 對應的 Region Server 地址,並緩存該地址;
  4. HBase 客戶端根據地址直接請求 Region Server 完成數據讀寫。

注 1:數據路由並不涉及Master,也就是說 DML 操作不需要 Master 參與。藉助 hbase:meta,客戶端直接與 Region Server 通信,完成數據路由、讀寫。

注 2:客戶端獲取 hbase:meta 地址後,會緩存該地址信息,以此減少對 ZooKeeper 的訪問。同時,客戶端根據 RowKey 查找 hbase:meta,獲取對應的 Region Server 地址後,也會緩存該地址,以此減少對 hbase:meta 的訪問。因為 hbase:meta 是存放在 Region Server 的一張表,其大小可能很大,因此不會緩存 hbase:meta 的完整內容。

HBase 初探:架構 + 原理 + 對比 + 實踐

1.5 HBase 適用場景

  1. 不需要複雜查詢的應用。HBase 原生只支持基於 RowKey 的索引,對於某些複雜查詢(如模糊查詢,多字段查詢),HBase 可能需要全表掃描來獲取結果。
  2. 寫密集應用。HBase 是一個寫快讀慢(慢是相對的)的系統。HBase 是根據 Google 的 BigTable 設計的,典型應用就是不斷插入新數據(如 Google 的網頁信息)。
  3. 對事務要求不高的應用。HBase 只支持基於 RowKey 的事務。
  4. 對性能、可靠性要求高的應用。HBase 不存在單點故障,可用性高。
  5. 數據量特別大的應用。HBase 支持百億行百萬列的數據量,單個 Region 過大將自動觸發分裂,具備較好的伸縮能力。

2 HBase 與 MySQL 的區別?

HBase 和 MySQL 的主要區別?

2.1 MySQL

MySQL 表結構規整,每一行有固定的列。

  • 創建表時,需要指定表名,預設字段(列)個數以及數據類型,Schema 是固定的。
  • 插入數據時,只需根據表的 Schema 填充每個列的值即可。如果 Schema 沒有該列,則無法插入。
HBase 初探:架構 + 原理 + 對比 + 實踐

2.2 HBase

HBase 支持動態列,不同行可擁有不同數量的列,可動態增加新的列。HBase 的表結構看起來雜亂無章,但卻有利於存儲稀疏數據。

  • 創建表時,需指定表名、列族,無需指定列的個數、數據類型,Schema 是靈活的。
  • 插入數據時,需要指定表名、列族、RowKey、若干個列(列名和列值),這裡列的個數可以是一個或多個。
HBase 初探:架構 + 原理 + 對比 + 實踐


2.3 對比

進一步,假設 ct_account_info_demo 表中只有一條記錄(account_id = 1,account_owner = Owner1,account_amount = 23.0,is_deleted = n),分別通過 MySQL 和 HBase 查找該記錄。

MySQL 返回的結果:

<code>mysql> select * from ct_account_info_demo;
+------------+---------------+----------------+------------+
| account_id | account_owner | account_amount | is_deleted |
+------------+---------------+----------------+------------+
|     1      |     Owner1    |      23.0      |     n      |
+------------+---------------+----------------+------------+
1 rows in set (0.01 sec)/<code>

HBase 返回的結果:

<code>hbase(main):001:0> scan 'ct_account_info_demo';
ROW           COLUMN+CELL
 1            column=CT:account_amount, timestamp=1532502487735, value=23.0
 1            column=CT:account_id, timestamp=1532909576152, value=1
 1            column=CT:account_owner, timestamp=1532502487528, value=Owner1
 1            column=CT:is_deleted, timestamp=1532909576152, value=n/<code>

上述結果都表示一行數據,MySQL 的返回結果比較直觀,容易理解。

HBase 返回的結果其實是多個鍵值對,ROW 表示數據的 RowKey,COLUMN+CELL 表示該 RowKey 對應的內容。

COLUMN+CELL 中又是多個鍵值對,如:

<code>column=CT:account_amount, timestamp=1532502487735, value=23.0/<code>

表示列族 CT 的列 account_amount 的值為 23.0,時間戳為 1532502487735 。

注:ROW 為 1 是因為這裡 RowKey = {account_id},CT 是提前定義的列族(HBase 在插入數據時需要指定 RowKey、Column Family)。

總的來說,

  1. HBase 比 MySQL 多了 RowKey 和 Column Family 的概念,這裡的 RowKey 類似 MySQL 中的主鍵,Column Family 相當於多個列的“歸類”。
  2. 列族只有一個的情況下,HBase 的 Schema 和 MySQL 可以保持一致,但 HBase 允許某些字段為空或動態增加某個列,而 MySQL 只可根據 Schema 填充相應的列,不能動態增減列。
  3. 因為 HBase 的 Schema 是不固定的,所以每次插入、查找數據不像 MySQL 那麼簡潔,HBase 需要指定行鍵、列族、列等信息。

更為詳細的對比如下表(引自:HBase 深入淺出):

RDBMS HBase 硬件架構 傳統的多核系統,硬件成本昂貴 類似於 Hadoop 的分佈式集群,硬件成本低廉 容錯性 一般需要額外硬件設備實現 HA 機制 由軟件架構實現,因為多節點,所以不擔心單點故障 數據庫大小 GB、TB PB 數據排布 以行和列組織 稀疏的、分佈的、多維的 Map 數據類型 豐富的數據類型 Bytes 事務支持 全面的 ACID 支持,支持 Row 和表 ACID 只支持單個 Row 級別 查詢語言 SQL 只支持 Java API (除非與其他框架一起使用,如 Phoenix、Hive) 索引 支持 只支持 Row-key(除非與其他技術一起應用,如 Phoenix、Hive) 吞吐量 數千查詢/每秒 百萬查詢/每秒

3 HBase 相關操作(CRUD)

HBase 怎麼用?如何實現 CREATE、INSERT、SELECT、UPDATE、DELETE、LIKE 操作?

為便於理解,結合 MySQL 說明 HBase 的 DML 操作,演示如何使用 HBase 來實現 MySQL 的 CREATE、 INSERT、SELECT、UPDATE、DELETE、LIKE 操作。

為方便代碼複用,這裡提前封裝獲取 HBase 連接的代碼:

<code>// 獲取HBase連接
public Connection getHBaseConnect() throws IOException {
	// 配置
    Configuration conf = HBaseConfiguration.create();
    conf.set("hbase.zookeeper.quorum", "127.0.0.1");
    conf.set("hbase.zookeeper.property.clientPort", "2181");
    conf.set("log4j.logger.org.apache.hadoop.hbase", "WARN");
    // 創建連接
    Connection connection = ConnectionFactory.createConnection(conf);
    return connection;
}/<code>

3.0 CREATE

<code>// 創建表
public void createTable (String tableName,String columnFamily)  {
	try {
	// 獲取連接,DDL操作需要獲取Admin
        Connection hbaseConnect = hbase.getHBaseConnect();
        Admin admin = hbaseConnect.getAdmin();
        // 設置表名
        HTableDescriptor tableDescriptor = new HTableDescriptor(TableName.valueOf(tableName));
        // 設置列族
        tableDescriptor.addFamily(new HColumnDescriptor(columnFamily));
        // 創建表
        admin.createTable(tableDescriptor);
    } catch (IOException e) {
    	e.printStackTrace();
    }
}/<code>

3.1 INSERT

MySQL:

<code>INSERT INTO ct_account_info_demo(account_id, account_owner , account_amount, is_deleted ) VALUES (?,?,?,?)/<code>

HBase 實現上述 SQL 語句的功能:

<code>// 插入數據
public int insertAccount(Long accountId, String accountOwner, BigDecimal accountAmount) {
    String tableName = "ct_account_info_demo";		// 表名
    // 行鍵(為便於理解,這裡將accountID作為RowKey,實際應用中RowKey的設計應該重點考慮)
    String rowKey = String.valueOf(accountId);
    String familyName = "account_info";				// 列族(在創建表時已定義)
    Map columns = new HashMap<>();	 // 多個列
    columns.put("account_id",String.valueOf(accountId));
    columns.put("account_owner",accountOwner);
    columns.put("account_amount",String.valueOf(accountAmount));
    columns.put("is_deleted","n");
    updateColumnHBase(tableName,rowKey,familyName,columns);	// 更新HBase數據
    return 0;
}

private void updateColumnHBase(String tableName, String rowKey, String familyColumn, Map columns) {
    try {
        Connection hbaseConnect = hbase.getHBaseConnect();	        // 獲取HBase連接
        Table table = hbaseConnect.getTable(TableName.valueOf(tableName));   // 獲取相應的表
        Put put = new Put(Bytes.toBytes(rowKey));	        		// 封裝Put對象
        for (Map.Entry entry : columns.entrySet()) {
            put.addColumn(Bytes.toBytes(familyColumn), Bytes.toBytes(entry.getKey()),
            Bytes.toBytes(entry.getValue()));
        }
        table.put(put);	        // 提交數據
        table.close();
    } catch (IOException e) {
    	e.printStackTrace();
    }
}/<code>

3.2 SELECT

MySQL:

<code>SELECT * from ct_account_info_demo WHERE account_id = #{account_id}/<code>

HBase 實現上述 SQL 語句的功能:

<code>// 讀取數據
public Account getAccountInfoByID(Long accountId) {
     Account account = new Account();
     String tableName = "ct_account_info_demo";		// 表名
     String familyName = "account_info";			// 列族
     String rowKey = String.valueOf(accountId);		// 行鍵
     List columns = new ArrayList<>();		// 設置需要返回哪些列
     columns.add("account_id");
     columns.add("account_owner");
     columns.add("account_amount");
     columns.add("is_deleted");
     // 獲取某一行指定列的數據
     HashMap accountRecord = getColumnHBase(tableName, rowKey,familyName,columns);
     if (accountRecord.size()==0) {
     	return null;
     }
     // 根據查詢結果,封裝賬戶信息
     account.setId( Long.valueOf(accountRecord.get("account_id")));
     account.setOwner(accountRecord.get("account_owner"));
     account.setBalance(new BigDecimal(accountRecord.get("account_amount")));
     account.setDeleted(accountRecord.get("isDeleted"));
     return account;
}
 
private HashMap getColumnHBase(String tableName, String rowKey, String familyColumn, List columns) {
     HashMap accountRecord = new HashMap<>(16);
     try {
         Connection hbaseConnect = hbase.getHBaseConnect();		// 獲取HBase連接
         Table table = hbaseConnect.getTable(TableName.valueOf(tableName));	// 獲取相應的表
         Get get = new Get(Bytes.toBytes(rowKey));				// 封裝Get對象
         for (String column:columns) {
             get.addColumn(Bytes.toBytes(familyColumn), Bytes.toBytes(column));
         }
         Result result = table.get(get);		// 獲取數據
         if (result.listCells() != null) {
             for (Cell cell : result.listCells()) {
                 String k = Bytes.toString(cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength());
                 String v = Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
                 accountRecord.put(k,v);	// 將結果存放在map中
             }
         }
         table.close();
     } catch (IOException e) {
     e.printStackTrace();
     }
     return accountRecord;		// 返回本次查詢的結果
}/<code> 

3.3 UPDATE

MySQL:

<code>UPDATE ct_account_info_demo SET account_amount = account_amount + #{transAmount} WHERE account_id = #{fromAccountId}/<code>

HBase 實現上述 SQL 語句的功能:

<code>// 更新數據
public void transIn(Long accountId, BigDecimal accountAmount) {
    String tableName = "ct_account_info_demo";		// 表名
    String rowKey = String.valueOf(accountId);		// 行鍵
    String familyName = "account_info";				// 列族
    List columns = new ArrayList<>();		// 獲取賬戶信息
    columns.add("account_amount");
    HashMap accountRecord = getColumnHBase(tableName, rowKey,familyName,columns);
    // 增加賬戶餘額
    BigDecimal newAccountAmount = new BigDecimal(accountRecord.get("account_amount")).add(accountAmount);
    // 更新賬戶的餘額
    Map fromColumns = new HashMap<>(1);
    fromColumns.put("account_amount",String.valueOf(newAccountAmount));
    // 更新HBase數據
    updateColumnHBase(tableName,rowKey,familyName,fromColumns);
}/<code>

3.4 DELETE

MySQL:

<code>DELETE FROM ct_account_info_demo WHERE account_id = ?/<code>

通過 HBase 實現上述 SQL 語句的功能:

<code>// 刪除數據
public void deleteAccount (String tableName, Long accountId) {
    try {
        Connection hbaseConnect = hbase.getHBaseConnect();
        // 行鍵
        String rowKey = String.valueOf(accountId);
        // 列族
        String familyName = "account_info";
        Table table = hbaseConnect.getTable(TableName.valueOf(tableName));
        Delete delete = new Delete(Bytes.toBytes(rowKey));
        // 刪除該行指定列的數據
        delete.deleteColumn(Bytes.toBytes(familyName), Bytes.toBytes("account_id"));
        delete.deleteColumn(Bytes.toBytes(familyName), Bytes.toBytes("account_owner"));
        delete.deleteColumn(Bytes.toBytes(familyName), Bytes.toBytes("account_amount"));
        delete.deleteColumn(Bytes.toBytes(familyName), Bytes.toBytes("is_deleted"));
        // 刪除整個列族
        //delete.deleteFamily(Bytes.toBytes(familyName));
        table.delete(delete);
        table.close();
    } catch (IOException e) {
    	e.printStackTrace();
    }
}/<code>

3.5 LIKE

MySQL:

<code>SELECT * FROM ct_account_info_demo WHERE account_id = #{account_id} AND account_owner LIKE CONCAT('%', #{keyWord}, '%')
複製代碼/<code>

HBase 實現上述 SQL 語句的功能:

<code>// 模糊查詢
public Account getAccountInfoByKeyWord(Long accountId, String keyWord) {
    Account account = new Account();
    // 表名
    String tableName = "ct_account_info_demo";		
    // 起始行鍵(閉區間)
    String startRow = String.valueOf(accountId);	
    // 終止行鍵(開區間,結果不包含stopRow)
    String stopRow = String.valueOf(accountId);	
    // 列族
    String familyName = "account_info";
    // 設置需要模糊查詢的列
    String targetColumn = "account_owner";
    // 設置需要返回哪些列
    List columns = new ArrayList<>();
    columns.add("account_id");
    columns.add("account_owner");
    columns.add("account_amount");
    columns.add("is_deleted");
    // 模糊查詢
    HashMap accountRecord = singleColumnFilter(tableName, familyName, startRow, stopRow, targetColumn, keyWord, columns);
    if (accountRecord.size()==0) {
    	return null;
    }
    // 根據查詢結果,封裝賬戶信息
    account.setId( Long.valueOf(accountRecord.get("account_id")));
    account.setOwner(accountRecord.get("account_owner"));
    account.setBalance(new BigDecimal(accountRecord.get("account_amount")));
    account.setDeleted(accountRecord.get("isDeleted"));
    return account;
}

private HashMap singleColumnFilter(String tableName, String familyColumn, String startRowKey, String stopRowKey, String targetColumn, String keyWord, List columns) {
    if (hbase == null) {
    	throw new NullPointerException("HBaseConfig");
    }
    if (familyColumn == null || columns.size() == 0) {
    	return null;
    }
    HashMap accountRecord = new HashMap<>(8);
    try {
        // 獲取HBase連接
        Connection hbaseConnect = hbase.getHBaseConnect();
        // 獲取相應的表
        Table table = hbaseConnect.getTable(TableName.valueOf(tableName));
        // 封裝Scan
        Scan scan = new Scan();
        scan.setStartRow(Bytes.toBytes(startRowKey));
        scan.setStopRow(Bytes.toBytes(stopRowKey));
        // 設置查詢的列
        for (String column:columns) {
            scan.addColumn(Bytes.toBytes(familyColumn), Bytes.toBytes(column));
        }
        // 定義過濾器:某一列的值是否包含關鍵字
        SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter(Bytes.toBytes(familyColumn),Bytes.toBytes(targetColumn),CompareFilter.CompareOp.EQUAL,new SubstringComparator(keyWord));
        //ValueFilter filter = new ValueFilter(CompareFilter.CompareOp.EQUAL, new SubstringComparator(keyWord));
        FilterList list = new FilterList(FilterList.Operator.MUST_PASS_ONE,singleColumnValueFilter);
        // Scan添加過濾器
        scan.setFilter(list);
        // 獲取數據
        ResultScanner resultScanner = table.getScanner(scan);
        for(Result result = resultScanner.next();result!=null;result = resultScanner.next()){
        	if (result.listCells() != null) {
            	for (Cell cell : result.listCells()) {
                    // 將結果存放在map中
                    String k = Bytes.toString(cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength());
                    String v = Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
                    accountRecord.put(k,v);
    			}
    		}
    	}
    	table.close();
    } catch (IOException e) {
    	e.printStackTrace();
    }
    // 返回所有行數據
    return accountRecord;
}/<code> 

後話:

在學校時,我參與過 Elasticsearch 相關的項目。在理解 HBase 時,發現 HBase 的設計其實和 Elasticsearch 十分相似,如 HBase 的 Flush&Compact 機制等設計與 Elasticsearch 如出一轍,因此理解起來比較順利。

從本質上來說,HBase 的定位是分佈式存儲系統,Elasticsearch 是分佈式搜索引擎,兩者並不等同,但兩者是互補的。HBase 的搜索能力有限,只支持基於 RowKey 的索引,其它二級索引等高級特性需要自己開發。因此,有一些案例是結合 HBase 和 Elasticsearch 實現存儲 + 搜索的能力。通過 HBase 彌補 Elasticsearch 存儲能力的不足,通過 Elasticsearch 彌補 HBase 搜索能力的不足。

其實,不只是 HBase 和 Elasticsearch。任何一種分佈式框架或系統,它們都有一定的共性,不同之處在於各自的關注點不同。我的感受是,在學習分佈式中間件時,應先弄清其核心關注點,再對比其它中間件,提取共性和特性,進一步加深理解。


分享到:


相關文章: