「從零單排HBase 12」HBase二級索引Phoenix使用與最佳實踐
Phoenix是構建在HBase上的一個SQL層,能讓我們用標準的JDBC APIs對HBase數據進行增刪改查,構建二級索引。當然,開源產品嘛,自然需要注意「避坑」啦,阿丸會把使用方式和最佳實踐都告訴你。
1.什麼是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的過程,如下圖所示。
這種架構存在使用上的缺陷:
- 應用程序與Phoenix core綁定使用,需要引入Phoenix內核依賴,一個單獨Phoenix重客戶端集成包已達120多M;
- 運維不便,Phoenix仍在不斷優化和發展,一旦Phoenix版本更新,那麼應用程序也需要對應升級版本並重新發佈;
- 僅支持Java API,其他語言開發者不能使用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(歷史文章查閱非常方便)