Phoenix使用建議


轉自:阿里雲社區

 

Phoenix是什么

 

Phoenix查詢引擎支持使用SQL進行HBase數據的查詢,會將SQL查詢轉換為一個或多個HBase API,協同處理器與自定義過濾器的實現,並編排執行。使用Phoenix進行簡單查詢,其性能量級是毫秒。

 

更多的信息可以參考官網

 

Phoenix使用建議

1、 二級索引使用指南

二級索引是Phoenix中非常重要的功能,用戶在使用前需要深入了解,可以參考資料:二級索引社區文檔二級索引中文文檔全局索引設計實踐

下面介紹一些在使用二級索引過程中的注意事項:

 

1)是否需要使用覆蓋索引?

覆蓋索引需要將查詢返回字段加入到索引表中,這樣在命中索引時,只需要查詢一次索引表即可,非覆蓋索引,要想拿到完整結果則需要回查主表。不難理解,覆蓋索引查詢性能更好,但是會浪費一定存儲空間,影響一定寫性能。非覆蓋索引使用時,有時執行計划並不能默認命中索引,此時,用戶需要加索引Hint

 

2)應該使用local Index還是global Index?實現上,一個global index表對應着一個hbase 表,local index是在主表上新增一列存儲索引數據。

適用場景上,global index 適用於多讀的場景,但存在同步索引時帶來網絡開銷較大的問題。而local由於和原數據存儲在一張表中同步索引數據會相對快一點。雖然local index也有一定適用場景,但仍然推薦使用global index, 其原因有以下幾點:

  1. 當前版本的phoneix的local index的實現相對global index不太完善,問題較多,使用存在一定的風險。
  2. local index不太完善,大的改動后,可能會存在不兼容,升級流程比較復雜。
  3. 在大數據量下,原始數據和索引數據放在一起會加劇region分裂,且分裂后索引數據的本地性也會喪失。

因此,在阿里雲HBase SQL服務中LOCAL INDEX功能已經被禁止!

 

3)索引表最多可以創建多少個?

索引會保證實時同步,也會引來寫放大問題,一般建議不超過10個,如果超過建議使用HBase全文索引功能

 

4) 構建索引需要注意哪些事項?

使用創建索引語句(CREATE INDEX)時,如果指定async參數,則為異步構建,語句完成時,會在SYSTEM.CATALOG表中簡歷索引表的元信息,並建立跟主表的關系,但是狀態是building,索引表中沒有數據,也不可查,需要后續用REBUILD語句來構建,或者借助MR工具來構建索引,並將狀態更新為active。如果不指定,則為同步構建,一般建議數據量小於1億行都使用同步構建即可,比較簡單。

2、 加鹽注意事項

注意:本特性有副作用,不建議使用。使用前請確保您已經充分了解其原理與適用場景。如果您無法確定您的場景是否適合,請釘釘與我們聯系,我們會幫您進行評估。

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

加鹽有較強的適用場景要求,場景不合適將適得其反:

  • 寫熱點或寫不均衡:比如以時間作為第一列主鍵,永遠寫表頭或者表尾
  • 需要范圍查詢:要按第一列主鍵進行范圍查詢,故不能使用hash打散

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

一般來說,嚴格滿足上述條件的業務場景並不常見。大多數場景都可以找到其他的業務字段來協助散列。考慮到其嚴重的副作用,我們不建議使用這個特性。


副作用:

  • 寫瓶頸:一般全表只有buckets個region用於承擔寫。當業務體量不斷增長時,因為無法調整bucket數量,不能有更多的region幫助分擔寫,會導致寫入吞吐無法隨集群擴容而線性增加。導致寫瓶頸,從而限制業務發展。
  • 讀擴散:select會按buckets數量進行拆分和並發,每個並發都會在執行時占用一個線程。select本身一旦並發過多會導致線程池迅速耗盡或導致QueryServer因過高的並發而FGC。同時,本應一個RPC完成的簡單查詢,現在也會拆分成多個,使得查詢RT大大增加。

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


常見的使用誤區

  • 預分區:用分桶來實現建表的預分區最常見的誤用。這是因為Phoenix提供的split on預分區語法很難使用。目前可用hbase shell的建表,指定預分區,之后關聯為Phoenix表。在海量數據場景,合理的預分區是一個很有挑戰的事情,我們后續會對此進行專題討論,敬請期待。
  • 偽熱點:寫入熱點或不均衡大多數情況都是假象,通常還有其他字段可用於打散數據。比如監控數據場景,以metric名字的hash值做首列主鍵可有效解決寫入均衡的問題。

一定不要為了預分區而使用加鹽特性,要結合業務的讀寫模式來進行表設計。如果您無法做出准確的判斷,可以釘釘聯系雲HBase值班咨詢。

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數目,有用戶發生過在低配置節點上,建大量加鹽表直接把集群內存耗光的問題。

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

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

4、 Phoenix不支持復雜查詢

Phoenix的二級索引本質還是前綴匹配,用戶可以建多個二級索引來增加對數據的查詢模式,二級索引的一致性是通過協處理器實現的,索引數據可以實時可見,但也會影響寫性能,特別是建多個索引的情況下。對於復雜查詢,比如任意條件的and/or組合,模糊查找,分詞檢索等Phoenix不支持。

5、 Phoenix不支持復雜分析

Phoenix定位為操作型分析(operational analytics),對於復雜分析,比如前面提到的復雜join則不適合,這種建議用Spark這種專門的大數據計算引擎來實現,參考X-Pack Spark分析服務, HBase SQL(Phoenix)與Spark的選擇

6、 Phoenix是否支持映射已經存在的HBase表?

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

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
 
import com.google.common.base.Strings;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.RegionLocator;
import org.apache.hadoop.hbase.mapreduce.TableInputFormat;
import org.apache.hadoop.hbase.mapreduce.TableSplit;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.JobContext;
 
public class SaltRangeTableInputFormat extends TableInputFormat {
 
     @Override
     public List<InputSplit> getSplits(JobContext context) throws IOException {
         Configuration conf = context.getConfiguration();
 
         String tableName = conf.get(TableInputFormat.INPUT_TABLE);
         if (Strings.isNullOrEmpty(tableName)) {
             throw new IOException( "tableName must be provided." );
         }
 
         Connection connection = ConnectionFactory.createConnection(conf);
         val table = TableName.valueOf(tableName)
         RegionLocator regionLocator = connection.getRegionLocator(table);
 
 
         String scanStart = conf.get(TableInputFormat.SCAN_ROW_START);
         String scanStop = conf.get(TableInputFormat.SCAN_ROW_STOP);
 
         Pair< byte [][], byte [][]> keys = regionLocator.getStartEndKeys();
         if (keys == null || keys.getFirst() == null || keys.getFirst().length == 0 ) {
             throw new RuntimeException( "At least one region is expected" );
         }
         List<InputSplit> splits = new ArrayList<>(keys.getFirst().length);
         for ( int i = 0 ; i < keys.getFirst().length; i++) {
             String regionLocation = getTableRegionLocation(regionLocator, keys.getFirst()[i]);
             String regionSalt = null ;
             if (keys.getFirst()[i].length > 0 ) {
                 regionSalt = Bytes.toString(keys.getFirst()[i]).split( "-" )[ 0 ];
             }
 
             byte [] startRowKey = Bytes.toBytes(regionSalt + "-" + scanStart);
             byte [] endRowKey = Bytes.toBytes(regionSalt + "-" + scanStop);
 
             InputSplit split = new TableSplit(TableName.valueOf(tableName),
                     startRowKey, endRowKey, regionLocation);
             splits.add(split);
         }
         return splits;
     }
 
     private String getTableRegionLocation(RegionLocator regionLocator,
                                           byte [] rowKey) throws IOException {
         return regionLocator.getRegionLocation(rowKey).getHostname();
     }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM