淺談Phoenix在HBase中的應用

1、前言

業務使用HBase已經有一段時間了,期間也反饋了不少問題,其中反饋最多的是HBase是否支持SQL查詢和二級索引,因爲HBase在這兩塊上目前暫不支持,致使業務在使用時沒法更好的利用現有的經驗來查詢HBase。雖然HBase自己不支持SQL,但業界仍是有現成的方案來支持,如Hive、Impala、Phoenix等。衆多方案各有各的優點,本文主要對Phoenix做一個大概的介紹。html

Phoenix中文翻譯爲鳳凰, 其最先是Salesforce的一個開源項目,Salesforce背景是一個搞ERP的,ERP軟件一個很大的特色就是數據庫操做,因此能搞出一個數據庫中間件也是很正常的。然後,Phoenix成爲Apache基金的頂級項目。java

Phoenix具體是什麼呢,其本質是用Java寫的基於JDBC API操做HBase的開源SQL引擎。它有以下幾個功能特性:
git

1

圖1.phoenix功能特性

我以爲值得關注的幾個特性主要有如下幾塊:github

  • 經過JDBC API實現了大部分的java.sql接口,包括元數據API
  • DDL支持:經過CREATE TABLE、DROP TABLE及ALTER TABLE來添加/刪除
  • DML支持:用於逐行插入的UPSERT VALUES,用於相同或不一樣表之間大量數據傳輸的UPSERT SELECT,用於刪除行的DELETE
  • 事務支持:經過客戶端的批處理實現的有限的事務支持(beta測試中)
  • 二級索引支持:
  • 遵循ANSI SQL標準

當前使用Phoenix的公司有不少,以下圖所示:sql

![2]
圖2.phoenix使用公司

對於咱們公司來講,雖然HBase用得多,但用Phoenix的比較少。從本身測試來看,Phoenix確實還存在各類不穩定,以下面描述的幾點問題:數據庫

  • 最新版本對HBase、Hadoop等有嚴格版本控制,對於已經用上HBase的業務來講要升級HBase版本適配Phoenix代價太大
  • 與HBase強相關,做爲HBase中的一個組件啓動,HBase元數據容易遭到破壞
  • 官方提供的建立索引方法,容易致使插入失敗,查詢失敗,程序崩潰等問題

我以爲Phoenix整體思路仍是很不錯的,但自己太冒進,急於集成新功能,但現有的功能所存在的問題卻並未有很好的解決方案,致使版本不少,但沒有一個版本能放心在生產環境使用。下面關注一下Phoenix的總體設計思路。apache

2、Phoenix架構

上面說到,Phoenix是以JDBC驅動方式嵌入到HBase中的,在部署時只有一個包,直接放HBase的lib目錄,邏輯構架以下:架構

![3]
圖3.phoenix_structure

從圖中可看出,每一個RS結點上,都會有一個Phoenix協處理器來處理每一個表、每一個region的數據,應用端經過Phoneix客戶端與HBase客戶端打交道,從而實現Sql化訪問HBase數據。下面先來講下Coprocessor。併發

2.1 Coprocessor

HBase的協處理器主要受Google BigTable的影響,具體可參考Dean-Keynote-Ladis2009-page 66-67。 對於HBase來講,引入Coprocessor也是爲了提供更好的並行計算能力,而無需依賴於Hadoop的MapReduce。同時,基於Coprocessor,能夠更好的實現二級索引、複雜過濾規則、權限訪問控制等更接地氣的特性。Coprocessor有兩種類型,ObserverEndPoint框架

前者Observer,相似於RDBMS的觸發器,主要做用於RegionServer服務端,經過重載Coprocessor框架的Upcall函數插入用戶本身的邏輯,這些邏輯只有在固定的事件發生時纔會被觸發調用執行,主要有三類hook接口:RegionObserverWALObserverMasterObserver。RegionObserver提供了一些數據層操做事件的hook,如Put、Get、Delete和Scan等,在每一個操做發生或結束時,會觸發調用一些前置的Hook(pre+操做,如preGet)或後置的Hook(post+操做,如postGet);WALObserver提供了WAL相關的Hook;MasterObserver提供了HMaster相關的Hook。

後者EndPoint相似於RDBMS的存儲過程,主要做用於客戶端,客戶端能夠調用這些EndPoint執行一段Server端代碼,並將Server端代碼結果返回給客戶端進一步處理,如常見聚合操做,找一張大表某個字段的最大值,若是不用Coprocesser則只能全表掃描,在客戶端遍歷全部結果找出最大值,且只能利用有限的客戶端資源進行迭代計算,沒法利用上HBase的併發計算能力;若是用了Coprocessor,則client端可在RegionServer端執行統計每一個Region最大值的邏輯,並將Server端結果返回客戶端,再找出全部Server端所返回的最大值中的最大值獲得最終結果,很明顯,這種方式儘可能將統計執行下放到Server端,Client端只執行一些最後的聚合,大幅提升了統計效率;還有一個很常見的需求可能就是統計表的行數,其邏輯和上面同樣,具體可參考Coprocessor Introduction,在這裏就不展開了,後面有機會針對Coprocessor單獨展開介紹。

2.2 Phoenix 實現原理

Phoenix的SQL實現原理主要也是基於一系列的Scan操做來完成,Scan是HBase的批量掃描過程。這一系列的Scan操做也是分散到各臺RegionServer上經過Coprocessor來完成。主要用到的是RegionObserver,經過RegionObserver在postScannerOpen Hook中將RegionScanner替換成支持聚合操做的定製化Scanner,在真正執行聚合時,會經過自定的Scan屬性傳遞給RegionScanner,在這個Scan中也可加入一些過濾規則,儘可能減小返回Client的結果。

2.3 Phoenix 數據模型

Phoenix在數據模型上是將HBase非關係型形式轉換成關係型數據模型 ,以下圖所示

6

圖4.Phoenix Data Model

對於Phoenix來講,HBase的rowkey會被轉換成primary key,column family若是不指定則爲0不然字段名會帶上,qualifier轉換成表的字段名,以下是建立一個Phoenix表的例子,以建立表test爲例,主鍵爲id即爲HBase的rowkey, column family爲i, qualifier爲name和age。

create table "test" ("id" varchar(20) primary key,"i"."name" varchar(20) ,"i"."age" varchar(20));

Phoenix還支持組合primary key,即由多個字段聯合組成主鍵,對於組合主鍵來講,在HBase底層會把主鍵的多個字段組合成rowkey顯示,其它字段爲HBase的qualifier顯示。如上面test表,假設id和name爲主鍵,建立表語句又變成:

create table "test" ("id" varchar(20), "name" varchar(20) ,"i"."age" varchar(20),constraint pk PRIMARY KEY("id","name"));

這樣,假設插入一條數據:以下所示

upsert into "test" values ('1','a','23');

在HBase中,rowkey即爲"1a", i:age 爲 23。這裏,可能你們對雙引號有點疑問,對於Phoenix來講,加了引號的話,不論是表仍是字段名,會變成大小寫敏感,不加的話,會統一轉換成大寫字母。

2.4 Phoenix所支持的語法

目前Phoenix已經支持關係型數據庫的大部分語法,以下圖所示:

7

圖4.Phoenix 語法

具體語法用法可參考Phoenix官網,寫得比較詳細。

3、 Phoenix二級索引

我相信,二級索引這個特性應該是大部分用戶引入Phoenix主要考慮的因素之一。HBase因其歷史緣由只支持rowkey索引,當使用rowkey來查詢數據時能夠很快定位到數據位置。現實中,業務查詢需求條件每每比較複雜,帶有多個查詢字段組合,若是用HBase查的話,只能全表掃描進行過濾,效率很低。而Phoenix支持除rowkey外的其它字段的索引建立,即二級索引,查詢效率可大幅提高。

3.1 索引類別

3.1.1 Covered Indexes

從字面上可理解爲覆蓋索引,什麼意思呢,即索引表中就包含你想要的所有字段數據,這樣就只須要經過訪問索引表而無需訪問主表就能獲得數據。建立方式以下:

create index my_index on test (v1) include(v2);

當執行select v2 from test where v1='...'時,就只會查找索引表數據,不會去主表掃描。

3.1.2 Global Indexes

全局索引適用於讀多寫少的場景。全局索引在寫數據時會消耗大量資源,全部對數據的增刪改操做都會更新索引表,而索引表是分佈在各個結點上的,性能會受到影響。好處就是,在讀多的場景下若是查詢的字段用到索引,效率會很快,由於能夠很快定位到數據所在具體結點region上,對於寫性能就很慢了,由於每寫一次,須要更新全部結點上的索引表數據。建立方式以下:

create index my_index on test (v1);

若是執行`select v2 from test where v1='...', 實際是用不上索引的,由於v2不在索引字段中,對於全局索引來講,若是查詢的字段不包含在索引表中,則仍是會去全表掃描主表。

3.1.3 Local Indexes

局部索引適用於寫多讀少場景,和全局索引相似,Phoenix會在查詢時自動選擇是否使用索引。若是定義爲局部索引,索引表數據和主表數據會放在同一regionserver上,避免寫操做時跨節點寫索引錶帶來的額外開銷(如Global Indexes)。當使用局部索引查詢時,即便查詢字段不是索引字段,索引表也會正常使用,這和Global Indexes是有區別的。在4.8版本以前,全部局部索引數據存放在一個單獨的共享表中,4.8以後是存儲在主表的一個獨立的列族中。由於是局部索引,因此在client端查詢使用索引時,須要掃描每一個結點上的索引表以獲得數據所在具體region位置,當region多時,查詢時耗會很高,因此查詢性能比較低,適合讀少寫多場景。建立局部索引方式:

create local index my_index on test (v1);

3.2 Mutable Indexing 和Immutable Indexing

3.2.1 IMMutable Indexing

不可變索引主要建立在不可變表上,適用於數據只寫一次不會有Update等操做,在什麼場景下會用到不可變索引呢,很經典的時序數據:write once read many times。在這種場景下,全部索引數據(primary和index)要麼所有寫成功,要麼一個失敗全都失敗返回錯誤給客戶端。不可變索引用到場景比較少,下面是建立不可變索引的方式:

create table test (pk VARCHAR primary key,v1 VARCHAR, v2 VARCHAR) IMMUTABLE_ROWS=true;

即在建立表時指定IMMUTABLE_ROWS參數爲true,默認這個參數爲false。若是想把不可變索引改成可變索引,可用alter修改:

alter table test set IMMUTABLE_ROWS=false;

3.2.2 Mutable Indexing

可變索引意思是在修改數據如Insert、Update或Delete數據時會同時更新索引。這裏的索引更新涉及WAL,即主表數據更新時,會把索引數據也同步更新到WAL,只有當WAL同步到磁盤時纔會去更新實際的primary/index數據,以保證當中間任何一個環節異常時可經過WAL來恢復主表和索引表數據。

4、性能

在官網,有做一個性能測試,主要是將Phoenix和Hive、Impala做一個對比。
先來看下和Hive的性能對比,測試基準以下:

select count(1) from table over 10M and 100M rows. Data is 5 narrow columns. Number of Region Servers: 4 (HBase heap: 10GB, Processor: 6 cores @ 3.3GHz Xeon)

測試結果:

9

圖6.Phoenix性能對比

從圖中可看出,帶有Key過濾的Phoenix耗時最少,不帶Key過濾的Phoenix和基於HDFS的Hive性能差很少,直接基於HBase的Hive性能最差。

再來看下和Impala的對比,測試基準以下:

select count(1) from table over 1M and 5M rows. Data is 3 narrow columns. Number of Region Server: 1 (Virtual Machine, HBase heap: 2GB, Processor: 2 cores @ 3.3GHz Xeon)

測試結果:

10

圖7.Phoenix性能對比Impala

從圖中可看出,Impala執行時間比Phoenix長不少,緣由大概有幾點:Impala基於內存進行並行計算,容易內存吃緊,對HBase和HDFS的支持也還遠遠不夠,性能比較差。

我在本身的HBase測試集羣也做了下測試,主要測試數據插入和一些SQL操做的查詢時耗。測試集羣以下:

![11]
圖8.測試集羣

先來測試下插入100萬記錄的測試基準,以下所示:

  • 1.建立基本表,表主鍵由4個字段組成,HOST字段稱爲First PK,DOMAIN爲Second PK, 依此類推,SPLIT ON指定8個分區。
CREATE TABLE IF NOT EXISTS %s (HOST CHAR(2) NOT NULL,
DOMAIN VARCHAR NOT NULL, 
FEATURE VARCHAR NOT NULL,
DATE DATE NOT NULL,
USAGE.CORE BIGINT,
USAGE.DB BIGINT,
STATS.ACTIVE_VISITOR INTEGER
CONSTRAINT PK PRIMARY KEY (HOST, DOMAIN, FEATURE, DATE))  
SPLIT ON   ('CSGoogle','CSSalesforce','EUApple','EUGoogle','EUSalesforce','NAApple','NAGoogle','NASalesforce')
  • 2.插入100萬行記錄
  • 3.執行以下查詢條件測試
Query # 1 - Count - SELECT COUNT(1) FROM PERFORMANCE_1000000;
Query # 2 - Group By First PK - SELECT HOST FROM PERFORMANCE_1000000 GROUP BY HOST;
Query # 3 - Group By Second PK - SELECT DOMAIN FROM PERFORMANCE_1000000 GROUP BY DOMAIN;
Query # 4 - Truncate + Group By - SELECT TRUNC(DATE,'DAY') DAY FROM PERFORMANCE_1000000 GROUP BY TRUNC(DATE,'DAY');
Query # 5 - Filter + Count - SELECT COUNT(1) FROM PERFORMANCE_1000000 WHERE CORE<10;

測試結果以下:

    1. 插入100萬條記錄耗時70s
    1. Query #1 耗時1.032s
    1. Query #2 耗時0.025s
    1. Query #3 耗時0.615s
    1. Query #4 耗時0.608s
    1. Query #5 耗時1.026s

具體結果以下:

csv columns from database.
CSV Upsert complete. 1000000 rows upserted
Time: 69.672 sec(s)

                                COUNT(1) 
---------------------------------------- 
                                 1000000 
Time: 1.032 sec(s)

HO 
-- 
CS 
EU 
NA 
Time: 0.025 sec(s)

DOMAIN                                   
---------------------------------------- 
Apple.com                                
Google.com                               
Salesforce.com                           
Time: 0.615 sec(s)

DAY                     
----------------------- 
2018-01-28 00:00:00.000 
2018-01-29 00:00:00.000 
2018-01-30 00:00:00.000 
2018-01-31 00:00:00.000 
2018-02-01 00:00:00.000 
2018-02-02 00:00:00.000 
2018-02-03 00:00:00.000 
2018-02-04 00:00:00.000 
2018-02-05 00:00:00.000 
2018-02-06 00:00:00.000 
2018-02-07 00:00:00.000 
2018-02-08 00:00:00.000 
2018-02-09 00:00:00.000 
Time: 0.608 sec(s)

                                COUNT(1) 
---------------------------------------- 
                                   20209 
Time: 1.026 sec(s)

還做了下三種不一樣數量級下的性能對比,做了5種SQL查詢操做對比,如上測試基準第3條所描述的查詢條件,結果以下:

12

圖9.Phoenix不一樣數據量級測試對比

從結果看,隨着數量級的增長,查詢時耗也隨之增長,有一個例外,就是當用First PK索引字段做聚合查詢時,用時相差不大。總的來講,Phoenix在用到索引時查詢性能會比較好。那對於Count來講,若是不用Phoenix,用HBase自帶的Count耗時是怎樣的呢,測了一下,HBase Count 100萬須要33s, 500萬須要139s,1000萬須要284s,性能仍是不好的。對於大表來講基本不能用Count來統計行數,還得依賴於基於Coprocessor機制來統計。

從上面測試來看下,Phoenix的性能不能說最好,也存在各類問題,就如開篇說的,版本不穩定,BUG過多,容易影響集羣穩定性。

5、總結

總的來講,目前並無一種很完美的方案來解決SQL查詢、二級索引問題,都或多或少存在各類問題。不過HBase的Coprocessor是個好東西,不少功能能夠基於此特性進行二次開發,後續能夠深刻研究一下。

6、參考

[1] https://community.hortonworks.com/articles/61705/art-of-phoenix-secondary-indexes.html

[2] https://github.com/forcedotcom/phoenix/wiki/Secondary-Indexing

[3] http://phoenix.apache.org/secondary_indexing.html

相關文章
相關標籤/搜索