「從零單排HBase 12」HBase二級索引Phoenix使用與最佳實踐

Phoenix是構建在HBase上的一個SQL層,能讓我們用標準的JDBC APIs對HBase數據進行增刪改查,構建二級索引。當然,開源產品嘛,自然需要注意「避坑」啦,阿丸會把使用方式和最佳實踐都告訴你。

1.什麼是Phoenix

「從零單排HBase 12」HBase二級索引Phoenix使用與最佳實踐

 

Phoenix完全使用Java編寫,將SQL查詢轉換為一個或多個HBase掃描,並編排執行以生成標準的JDBC結果集。Phoenix主要能做以下這些事情:

  • 將SQL查詢編譯為HBase掃描scan
  • 確定scan的開始和停止位置
  • 將scan並行執行
  • 將where子句中的謂詞推送到服務器端進行過濾
  • 通過服務器端掛鈎(稱為協處理器co-processors)執行聚合查詢

除了這些之外,phoenix還進行了一些有趣的增強,以進一步優化性能:

  • 二級索引,以提高非行鍵查詢的性能(這也是我們引入phoenix的主要原因)
  • 跳過掃描過濾器來優化IN,LIKE和OR查詢
  • 可選的對行鍵進行加鹽以實現負載均衡,避免熱點

2.Phoniex架構

Phoenix結構上劃分為客戶端和服務端兩部分:

  • 客戶端包括應用程序開發,將SQL進行解析優化生成QueryPlan,進而轉化為HBase Scans,調用HBase API下發查詢計算請求,並接收返回結果;
  • 服務端主要是利用HBase的協處理器,處理二級索引、聚合及JOIN計算等。

Phoiex的舊版架構採用重客戶端的模式,在客戶端執行一系列的parser、query plan的過程,如下圖所示。

「從零單排HBase 12」HBase二級索引Phoenix使用與最佳實踐

 

這種架構存在使用上的缺陷:

  • 應用程序與Phoenix core綁定使用,需要引入Phoenix內核依賴,一個單獨Phoenix重客戶端集成包已達120多M;
  • 運維不便,Phoenix仍在不斷優化和發展,一旦Phoenix版本更新,那麼應用程序也需要對應升級版本並重新發佈;
  • 僅支持Java API,其他語言開發者不能使用Phoenix。

因此,社區進行改造,引入了新的「輕客戶端」模式。

 

「從零單排HBase 12」HBase二級索引Phoenix使用與最佳實踐

 

輕客戶端架構將Phoenix分為兩部分:

  • 客戶端是用戶最小依賴的JDBC驅動程序,與Phoenix依賴進行解耦,支持Java、Python、Go等多種語言客戶端;
  • 將QueryServer部署為一個獨立的的HTTP服務,接收輕客戶端的請求,對SQL進行解析、優化、產生執行計劃;

3.基本使用

傳送門://phoenix.apache.org/language/index.html(平時多參考官方的語法)

3.1 建表

在phoenix shell上建的表默認只有一個region,建表時要注意預分區

// step1: 【phoenix shell】如果沒有創建過namespace的話,需要這一步
create schema "MDW_NS";
或者
create schema mdw_ns;


// step2: 【hbase shell】在原生HBase上創建一個表,為什麼需要這一步(可以使用預分區的功能,phoenix的建表語句在分區上的支持比較弱)
create 'MDW_NS:TABLE_DEMO', 'F1', {NUMREGIONS => 16, SPLITALGO => 'HexStringSplit'}


// step3: 【phoenix shell】創建一個同步的phoenix表
craete table if not exists mdw_ns.table_demo (
    id varchar not null primary key,
    f1.a varchar,
    f1.b varchar,
    f1.c varchar,
    f1.d varchar,
    f1.e varchar,
    f1.f varchar,
    f1.g varchar,
    f1.h varchar
)
TTL=86400,
UPDATE_CACHE_FREQUENCY=900000;

  

3.2 建索引

需要關注索引表的散列問題

// 方式1: split分區(適用於索引列滿足散列條件)
create index table_demo_a on mdw_ns.table_demo(f1.a) split on ('10000000','20000000','30000000','40000000','50000000','60000000','70000000','80000000','90000000','a0000000','b0000000','c0000000','d0000000','e0000000','f0000000');


// 方式2: salt分區(適用於索引列不滿足散列條件)
create index table_demo_a on mdw_ns.table_demo(f1.a) salt_buckets = 16;


// 注意: 索引列DESC的使用是不支持的,但對原表的主鍵是支持desc的
[x] create index table_demo_a on mdw_ns.table_demo(f1.a desc)
[√] craete table if not exists mdw_ns.table_demo (
    f1.a varchar,
    f1.b varchar,
    f1.c varchar,
    f1.d varchar,
    f1.e varchar,
    f1.f varchar,
    f1.g varchar,
    f1.h varchar,
    constraint pk primary key(a, b desc)
    );

  

3.3 select查詢

[√] select * from mdw_ns.table_demo limit 10;
// 主鍵查詢
[√] select * from mdw_ns.table_demo where id = '0000000000';
// 二級索引查詢必須指定列名
[x] select * from mdw_ns.table_demo where a = '0000000000';
[√] select id, a, b, c, d, e from mdw_ns.table_demo where a = '0000000000'

  

 

4.最佳實踐

4.1 大小寫問題

注意點:phoenix對於(表名,列名,namespace,cf)是區分大小寫,默認都會轉為成大寫。如果要屏蔽轉換,需要在對應的字符上用雙引號(”)。數據類型是字符串的話,要用單引號(’)包含。

// case1: 查詢「mdw_ns」(namespace)下「table_demo」(table),其中「f1」(列簇)+ 「A」(列名)為字符串「'0000000000'」 的記錄
select id, a, b, c from "mdw_ns"."table_demo" where "f1".a = '0000000000'


// case2: 查詢PS_NS(namespace)下「MODULE_REVISION」(table)的記錄
select * from ps_ns.module_revision;

  

 

4.2 加鹽注意事項

加鹽通常用來解決數據熱點和範圍查詢同時存在的場景,原理介紹可以參考Phoenix社區文檔。

一般我們只在同時滿足以下需求的時候,才使用加鹽:

  • 寫熱點或寫不均衡
  • 需要範圍查詢

有熱點就要打散,但打散就難以做範圍查詢。因此,要同時滿足這對相互矛盾的需求,必須有一種折中的方案:既能在一定程度上打散,又能保證一定程度的有序。這個解決方案就是加鹽,其實叫分桶(salt buckets)更準確。數據在桶內保序,桶之間隨機。寫入時按桶個數取模,數據隨機落在某個桶里,保證寫請求在桶之間是均衡的。查詢時讀取所有的桶來保證結果集的有序和完備。

副作用:

寫瓶頸:由於桶內保序,所以即使region不斷split變多,全表實際上還是只有數量為buckets的region用於承擔寫入。當業務體量不斷增長時,因為無法調整bucket數量,不能有更多的region幫助分擔寫,會導致寫入吞吐無法隨集群擴容而線性增加。導致寫瓶頸,從而限制業務發展。

讀擴散:select會按buckets數量進行拆分和並發,每個並發都會在執行時佔用一個線程。select本身一旦並發過多會導致線程池迅速耗盡或導致QueryServer因過高的並發而FGC。同時,本應一個RPC完成的簡單查詢,現在也會拆分成多個,使得查詢RT大大增加。

這兩個副作用會制約業務的發展,尤其對於大體量的、發展快速的業務。因為桶個數不能修改,寫瓶頸會影響業務的擴張。讀擴散帶來的RT增加也大大降低了資源使用效率。

 

一定不要為了預分區而使用加鹽特性,要結合業務的讀寫模式來進行表設計。

Buckets個數跟機型配置和數據量有關係,可以參考下列方式計算,其中 N 為 Core/RS 節點數量:

單節點內存 8G: 2*N

單節點內存 16G: 3*N

單節點內存 32G: 4*N

單節點內存 64G: 5*N

單節點內存 128G: 6*N

注意:索引表默認會繼承主表的鹽值;bucket的數目不能超過256;一個空的Region在內存中的數據結構大概2MB,用戶可以評估下單個RegionServer承載的總Region數目,有用戶發生過在低配置節點上,建大量加鹽表直接把集群內存耗光的問題。

 

4.3 慎用掃全表、OR、Join和子查詢

雖然Phoenix支持各種Join操作,但是Phoenix主要還是定位為在線數據庫,複雜Join,比如子查詢返回數據量特別大或者大表Join大表,在實際計算過程中十分消耗系統資源,會嚴重影響在線業務,甚至導致OutOfMemory異常。對在線穩定性和實時性要求高的用戶,建議只使用Phoenix的簡單查詢,且查詢都命中主表或者索引表的主鍵。另外,建議用戶在運行SQL前都執行下explain,確認是否命中索引,或者主鍵。

4.4 Phoenix不支持複雜查詢

Phoenix的二級索引本質還是前綴匹配,用戶可以建多個二級索引來增加對數據的查詢模式,二級索引的一致性是通過協處理器實現的,索引數據可以實時可見,但也會影響寫性能,特別是建多個索引的情況下。對於複雜查詢,比如任意條件的and/or組合,模糊查找,分詞檢索等Phoenix不支持,建議使用搜索引擎(如solr、es等)。當然,搜索引擎採用後台異步索引,必然會影響實時性,需要仔細權衡。

4.5 Phoenix不支持複雜分析

Phoenix定位為操作型分析(operational analytics),對於複雜分析,比如前面提到的複雜join則不適合,這種建議用Spark這種專門的大數據計算引擎來實現。

4.6 Phoenix支持映射已經存在的HBase表

參考社區相關文檔。用戶可以通過Phoenix創建視圖或者表映射已經存在的HBase表,注意如果使用表的方式映射HBase表,在Phoenix中執行DROP TABLE語句同樣也會刪除HBase表。另外,由於column family和列名是大小寫敏感的,必須一一對應才能映射成功。另外,Phoenix的字段編碼方式大部分跟HBase的Bytes工具類不一樣,一般建議如果只有varchar類型,才進行映射,包含其他類型字段時不要使用映射。

 

5.使用規範

  • 大小寫約定:由於phoenix對大小寫敏感,默認又會轉換成大寫,我們建表的時候都以大寫作為規範,避免不必要的麻煩
  • 索引名命名:每個索引都會在hbase集群建一張索引表,便於識別索引的歸屬,建議索引名按 {表名}_{索引標識} 的規範命名(如表名TABLE_DEMO,索引名可以為TABLE_DEMO_C)
  • 原表分區原則:建議使用原生HBase的預分區方式
  • 索引表分區原則:建議使用salt_buckets的分區方式
  • select使用原則:建議不要使用select * 方式
  • 建表注意點:不要使用默認的UPDATE_CACHE_FREQUENCY策略(ALWAYS),改成UPDATE_CACHE_FREQUENCY = 60000

 

看到這裡了,原創不易,點個關注、點個贊吧,你最好看了~

知識碎片重新梳理,構建Java知識圖譜://github.com/saigu/JavaKnowledgeGraph(歷史文章查閱非常方便)

Tags: