Apache Phoenix系列 | 從入門到精通(轉載)

原文地址:https://cloud.tencent.com/developer/article/1498057javascript

來源: 雲棲社區java

做者: 瑾謙node

By 大數據技術與架構sql

文章簡介:Phoenix是一個開源的HBASE SQL層。它不只可使用標準的JDBC API替代HBASE client API建立表,插入和查詢HBASE,也支持二級索引、事物以及多種SQL層優化。數據庫

此係列文章將會從Phoenix的語法和功能特性、相關工具、實踐經驗以及應用案例多方面從淺入深的闡述。但願對Phoenix入門、在作架構設計和技術選型的同窗能有一些幫助。express

關鍵詞:Phoenix Hbase SQLapache

大綱

1、快速入門

Phoenix做爲應用層和HBASE之間的中間件,如下特性使它在大數據量的簡單查詢場景有着獨有的優點。json

  • 二級索引支持(global index + local index)
  • 編譯SQL成爲原生HBASE的可並行執行的scan
  • 在數據層完成計算,server端的coprocessor執行聚合
  • 下推where過濾條件到server端的scan filter上
  • 利用統計信息優化、選擇查詢計劃(5.x版本將支持CBO)
  • skip scan功能提升掃描速度

通常可使用如下三種方式訪問Phoenix數組

  1. JDBC API
  2. 使用Python編寫的命令行工具(sqlline, sqlline-thin和psql等)
  3. SQuirrel

一、命令行工具psql使用示例

1)建立一個建表的sql腳本文件us_population.sql:

CREATE TABLE IF NOT EXISTS us_population ( state CHAR(2) NOT NULL, city VARCHAR NOT NULL, population BIGINT CONSTRAINT my_pk PRIMARY KEY (state, city));

2)建立csv格式的數據文件us_population.csv:

NY,New York,8143197 CA,Los Angeles,3844829 IL,Chicago,2842518 TX,Houston,2016582 PA,Philadelphia,1463281 AZ,Phoenix,1461575 TX,San Antonio,1256509 CA,San Diego,1255540 TX,Dallas,1213825 CA,San Jose,912332

3)建立一個查詢sql腳本文件us_population_queries.sql

SELECT state as "State",count(city) as "City Count",sum(population) as "Population Sum" FROM us_population GROUP BY state ORDER BY sum(population) DESC;

4) 執行psql.py工具運行sql腳本

./psql.py <your_zookeeper_quorum> us_population.sql us_population.csv us_population_queries.sql

二、JDBC API使用示例

1)使用Maven構建工程時,須要添加如下依賴

<dependencies> <dependency> <groupId>com.aliyun.phoenix</groupId> <artifactId>ali-phoenix-core</artifactId> <version>${version}</version> </dependency> </dependencies>

2) 建立名爲test.java的文件

import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.PreparedStatement; import java.sql.Statement; public class test { public static void main(String[] args) throws SQLException { Statement stmt = null; ResultSet rset = null; Connection con = DriverManager.getConnection("jdbc:phoenix:[zookeeper]"); stmt = con.createStatement(); stmt.executeUpdate("create table test (mykey integer not null primary key, mycolumn varchar)"); stmt.executeUpdate("upsert into test values (1,'Hello')"); stmt.executeUpdate("upsert into test values (2,'World!')"); con.commit(); PreparedStatement statement = con.prepareStatement("select * from test"); rset = statement.executeQuery(); while (rset.next()) { System.out.println(rset.getString("mycolumn")); } statement.close(); con.close(); } }

3)執行test.java

javac test.java

java -cp "../phoenix-[version]-client.jar:." test

2、數據類型

目前Phoenix支持24種簡單數據類型和1個一維Array的複雜類型。如下是對支持數據類型的說明:緩存

1. INTEGER
2. UNSIGNED_INT
3. BIGINT
4. UNSIGNED_LONG
5. TINYINT
6. UNSIGNED_TINYINT
7. SMALLINT
8. UNSIGNED_SMALLINT
9. FLOAT
10. UNSIGNED_FLOAT
11. DOUBLE
12. UNSIGNED_DOUBLE
13. DECIMAL
14. BOOLEAN
15. TIME
16. DATE
17. TIMESTAMP
18. UNSIGNED_TIME
19. UNSIGNED_DATE
20. UNSIGNED_TIMESTAMP
21. VARCHAR
22. CHAR
23. BINARY
24. VARBINARY
25. ARRAY

類型名

序號

對應的java類型

取值範圍

說明

 

1

INTEGER

INTEGER

[-2147483648, 2147483647]

binary表示是4個byte的整數, 符號位被翻轉(爲了讓負數排在正數前面)

2

UNSIGNED_INT

Integer

[ 0,2147483647]

binary表示是4個byte的整型。這個類型主要用做序列化映射到已經存在Hbase表的數據,適配HBase Bytes.toBytes(int)方法。

3

BIGINT

Long

[-9223372036854775808 ,9223372036854775807]

binary表示是8位byte的Long類型, 符號位被翻轉(爲了讓負數排在正數前面)

4

UNSIGNED_LONG

Long

[0 ,9223372036854775807]

binary表示是8位byte的Long類型。這個類型主要用做序列化映射到已經存在Hbase表的數據,適配HBase Bytes.toBytes(long)方法。

5

TINYINT

Byte

[-128,127]

binary表示是單個byte,爲了排序符號位被翻轉。

6

UNSIGNED_TINYINT

Byte

[0,127]

binary表示是單個byte。這個類型主要用做序列化映射到已經存在Hbase表的數據,適配 HBase Bytes.toBytes(byte)方法。

7

SMALLINT

Short

[-32768,32767]

binary表示是兩個byte,爲了排序符號位被翻轉。

8

UNSIGNED_SMALLINT

Short

[0,32767]

binary表示是兩個byte。這個類型主要用做序列化映射到已經存在Hbase表的數據,適配HBase Bytes.toBytes(short)方法。

9

FLOAT

Float

[-3.402823466 E + 38,3.402823466 E + 38]

binary表示是四個byte, 爲了排序符號位被翻轉。

10

UNSIGNED_FLOAT

Float

[0,3.402823466 E + 38]

binary表示是四個byte。這個類型主要用做序列化映射到已經存在Hbase表的數據,適配HBase Bytes.toBytes(float)方法。

11

DOUBLE

DOUBLE

[-1.7976931348623158 E + 308,1.7976931348623158 E + 308]

binary表示是8個byte,爲了排序符號位被翻轉。

12

UNSIGNED_DOUBLE

DOUBLE

[0,1.7976931348623158 E + 308]

binary表示是8個byte。這個類型主要用做序列化映射到已經存在Hbase表的數據,適配HBase Bytes.toBytes(double)方法。

13

DECIMAL(precision,scale)

BigDecimal

最大精度38位

binary是可比較的邊長格式。若是用於rowkey。當它不是最後一列時,比較終結符號是null byte

14

BOOLEAN

BOOLEAN

0或1

binary表示0是flase, 1是true

15

TIME

java.sql.Time

格式:yyyy-MM-dd hh:mm:ss

二進制表示是8位byte的long類型數據, 數據內容是客戶端時區自1970-01-01 00:00:00 UTC到如今的毫秒大小(GMT)。此類型與 SQL 92中的Time類型不兼容

16

DATE

java.sql.Date

格式:yyyy-MM-dd hh:mm:ss

二進制表示是8位byte的long類型數據, 數據內容是客戶端時區自1970-01-01 00:00:00 UTC到如今的毫秒大小(GMT)。此類型與 SQL 92中的DATE類型不兼容。

17

TIMESTAMP

java.sql.Timestamp

格式:yyyy-MM-dd hh:mm:ss[.nnnnnnnnn]

二進制表示是8位byte的long類型和4位整型納秒。8位byte的long類型數據是客戶端時區自1970-01-01 00:00:00 UTC到如今的毫秒大小(GMT)。

18

UNSIGNED_TIME

java.sql.Time

格式:yyyy-MM-dd hh:mm:ss

二進制表示是8位byte的long類型數據, 數據內容是客戶端時區自1970-01-01 00:00:00 UTC到如今的毫秒大小(GMT)。這個類型主要用做序列化映射到已經存在Hbase表的數據,適配HBase Bytes.toBytes(long)方法。

19

UNSIGNED_DATE

java.sql.Date

格式:yyyy-MM-dd hh:mm:ss

二進制表示是8位byte的long類型數據, 數據內容是客戶端時區自1970-01-01 00:00:00 UTC到如今的毫秒大小(GMT)。這個類型主要用做序列化映射到已經存在Hbase表的數據,適配HBase Bytes.toBytes(long)方法。

20

UNSIGNED_TIMESTAMP

java.sql.Timestamp

格式:yyyy-MM-dd hh:mm:ss[.nnnnnnnnn]

二進制表示是8位byte的long類型和4位整型納秒。8位byte的long類型數據是客戶端時區自1970-01-01 00:00:00 UTC到如今的毫秒大小(GMT)。這個類型主要用做序列化映射到已經存在Hbase表的數據,適配HBase Bytes.toBytes(long)方法。

21

VARCHAR(precisionInt)

java.lang.String

變長,可選最大長度

對應UTF-8字符經過HBase Bytes.toBytes(String)轉換的二進制。若是用於rowkey。當它不是最後一列時,比較終結符號是null byte

22

CHAR ( precisionInt )

java.lang.String

定長

對應UTF-8字符經過HBase Bytes.toBytes(String)轉換的二進制。

23

BINARY ( precisionInt )

byte[]

定長

定長byte數組

24

VARBINARY

byte[]

變長

變長byte數組

25

ARRAY [dimension]

java.sql.Array

-

Java原始類型數組,只支持一維數組。例如:VARCHAR ARRAY, CHAR(10) ARRAY [5],INTEGER [],INTEGER [100]

3、DML語法

雲HBASE上Phoenix支持的DML

  1. select
  2. upsert values
  3. upsert select
  4. delete

1. SELECT

從一個或者多個表中查詢數據。 LIMIT(或者FETCH FIRST) 在ORDER BY子句後將轉換爲top-N查詢。 OFFSET子句指定返回查詢結果前跳過的行數。

示例

SELECT * FROM TEST LIMIT 1000; SELECT * FROM TEST LIMIT 1000 OFFSET 100; SELECT full_name FROM SALES_PERSON WHERE ranking >= 5.0 UNION ALL SELECT reviewer_name FROM CUSTOMER_REVIEW WHERE score >= 8.0

2. UPSERT VALUES

此處upsert語義有異於標準SQL中的Insert,當寫入值不存在時,表示寫入數據,不然更新數據。其中列的聲明是能夠省略的,當省略時,values指定值的順序和目標表中schema聲明列的順序須要一致。

ON DUPLICATE KEY是4.9版本中的功能,表示upsert原子寫入的語義,在寫入性能上弱於非原子語義。相同的row在同一batch中按照執行順序寫入。

示例

UPSERT INTO TEST VALUES('foo','bar',3); UPSERT INTO TEST(NAME,ID) VALUES('foo',123); UPSERT INTO TEST(ID, COUNTER) VALUES(123, 0) ON DUPLICATE KEY UPDATE COUNTER = COUNTER + 1; UPSERT INTO TEST(ID, MY_COL) VALUES(123, 0) ON DUPLICATE KEY IGNORE;

3. UPSERT SELECT

從另一張表中讀取數據寫入到目標表中,若是數據存在則更新,不然插入數據。插入目標表的值順序和查詢表指定查詢字段一致。當auto commit被打開而且select子句沒有聚合時,寫入目標表這個過程是在server端完成的,不然查詢的數據會先緩存在客戶端再寫入目標表中(phoenix.mutate.upsertBatchSize表示從客戶端一次commit的行數,默認10000行)。

示例

UPSERT INTO test.targetTable(col1, col2) SELECT col3, col4 FROM test.sourceTable WHERE col5 < 100 UPSERT INTO foo SELECT * FROM bar;

4. DELETE

刪除選定的列。若是auto commit打開,刪除操做將在server端執行。

示例

DELETE FROM TABLENAME;
DELETE FROM TABLENAME WHERE PK=123; DELETE FROM TABLENAME WHERE NAME LIKE '%';

4、加鹽表

1. 什麼是加鹽?

在密碼學中,加鹽是指在散列以前將散列內容(例如:密碼)的任意固定位置插入特定的字符串。這個在散列中加入字符串的方式稱爲「加鹽」。其做用是讓加鹽後的散列結果和沒有加鹽的結果不相同,在不一樣的應用情景中,這個處理能夠增長額外的安全性。而Phoenix中加鹽是指對pk對應的byte數組插入特定的byte數據。

2. 加鹽能解決什麼問題?

加鹽能解決HBASE讀寫熱點問題,例如:單調遞增rowkey數據的持續寫入,使得負載集中在某一個RegionServer上引發的熱點問題。

3. 怎麼對錶加鹽?

在建立表的時候指定屬性值:SALT_BUCKETS,其值表示所分buckets(region)數量, 範圍是1~256。

CREATE TABLE mytable (my_key VARCHAR PRIMARY KEY, col VARCHAR) SALT_BUCKETS = 8;

4. 加鹽的原理是什麼?

加鹽的過程就是在原來key的基礎上增長一個byte做爲前綴,計算公式以下:

new_row_key = ((byte) (hash(key) % BUCKETS_NUMBER) + original_key

以上公式中 BUCKETS_NUMBER 表明建立表時指定的 salt buckets 大小,hash 函數的實際計算方式以下:

public static int hash (byte a[], int offset, int length) { if (a == null) return 0; int result = 1; for (int i = offset; i < offset + length; i++) { result = 31 * result + a[i]; } return result; }

5. 一個表「加多少鹽合適」?

  • 當可用block cache的大小小於表數據大小時,較優的slated bucket是和region server數量相同,這樣能夠獲得更好的讀寫性能。
  • 當表的數量很大時,基本上會忽略blcok cache的優化收益,大部分數據仍然須要走磁盤IO。好比對於10個region server集羣的大表,能夠考慮設計64~128個slat buckets。

6. 加鹽時須要注意

  • 建立加鹽表時不能再指定split key。
  • 加鹽屬性不等同於split key, 一個bucket能夠對應多個region。
  • 太大的slated buckets會減少range查詢的靈活性,甚至下降查詢性能。

5、二級索引

一、概要

目前HBASE只有基於字典序的主鍵索引,對於非主鍵過濾條件的查詢都會變成掃全表操做,爲了解決這個問題Phoenix引入了二級索引功能。然而此二級索引又有別於傳統關係型數據庫的二級索引,本文將詳細描述了Phoenix中二級索引功能、用法和原理。

二、二級索引

示例表以下(爲了可以容易經過HBASE SHELL對照表內容,咱們對屬性值COLUMN_ENCODED_BYTES設置爲0,不對column family進行編碼):

CREATE TABLE  TEST ( ID VARCHAR NOT NULL PRIMARY KEY, COL1 VARCHAR, COL2 VARCHAR ) COLUMN_ENCODED_BYTES=0; upsert into TEST values('1', '2', '3');

1)全局索引

全局索引更多的應用在讀較多的場景。它對應一張獨立的HBASE表。對於全局索引,在查詢中檢索的列若是不在索引表中,默認的索引表將不會被使用,除非使用hint。

建立全局索引:

CREATE INDEX IDX_COL1 ON TEST(COL1)

經過HBASE SHELL觀察生成的索引表IDX_COL1。咱們發現全局索引表的RowKey存儲了索引列的值和原表RowKey的值,這樣編碼更有利於提升查詢的性能。

hbase(main):001:0> scan 'IDX_COL1' ROW COLUMN+CELL 2\x001 column=0:_0, timestamp=1520935113031, value=x 1 row(s) in 0.1650 seconds

實際上全局索引的RowKey將會按照以下格式進行編碼。

  • SALT BYTE: 全局索引表和普通phoenix表同樣,能夠在建立索引時指定SALT_BUCKETS或者split key。此byte正是存儲着salt。
  • TENANT_ID: 當前數據對應的多租戶ID。
  • INDEX VALUE: 索引數據。
  • PK VALUE: 原表的RowKey。

2)本地索引

由於本地索引和原數據是存儲在同一個表中的,因此更適合寫多的場景。對於本地索引,查詢中不管是否指定hint或者是查詢的列是否都在索引表中,都會使用索引表。

建立本地索引:

create local index LOCAL_IDX_COL1 ON TEST(COL1);

經過HBASE SHELL觀察表'TEST', 咱們能夠看到表中多了一行column爲L#0:_0的索引數據。

hbase(main):001:0> scan 'TEST' ROW COLUMN+CELL \x00\x002\x001 column=L#0:_0, timestamp=1520935997600, value=_0 1 column=0:COL1, timestamp=1520935997600, value=2 1 column=0:COL2, timestamp=1520935997600, value=3 1 column=0:_0, timestamp=1520935997600, value=x 2 row(s) in 0.1680 seconds

本地索引的RowKey將會按照以下格式進行編碼:

  • REGION START KEY : 當前row所在region的start key。加上這個start key的好處是,可讓索引數據和原數據儘可能在同一個region, 減少IO,提高性能。
  • INDEX ID : 每一個ID對應不一樣的索引表。
  • TENANT ID :當前數據對應的多租戶ID。
  • INDEX VALUE: 索引數據。
  • PK VALUE: 原表的RowKey。

3)覆蓋索引

覆蓋索引的特色是把原數據存儲在索引數據表中,這樣在查詢到索引數據時就不須要再次返回到原表查詢,能夠直接拿到查詢結果。

建立覆蓋索引:

create  index IDX_COL1_COVER_COL2 on TEST(COL1) include(COL2);

經過HBASE SHELL 查詢表IDX_COL1_COVER_COL2, 咱們發現include的列的值被寫入到了value中。

hbase(main):003:0> scan 'IDX_COL1_COVER_COL2' ROW COLUMN+CELL 2\x001 column=0:0:COL2, timestamp=1520943893821, value=3 2\x001 column=0:_0, timestamp=1520943893821, value=x 1 row(s) in 0.0180 seconds

對於相似select col2 from TEST where COL1='2'的查詢,查詢一次索引表就能得到結果。其查詢計劃以下:

+--------------------------------------------------------------------------------------+-----------------+----------------+---+ | PLAN | EST_BYTES_READ | EST_ROWS_READ | E | +--------------------------------------------------------------------------------------+-----------------+----------------+---+ | CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN RANGE SCAN OVER IDX_COL1_COVER_COL2 ['2'] | null | null | n | +--------------------------------------------------------------------------------------+-----------------+----------------+---+

4)函數索引

函數索引的特色是能根據表達式建立索引,適用於對查詢表,過濾條件是表達式的表建立索引。例如:

//建立函數索引
CREATE INDEX CONCATE_IDX ON TEST (UPPER(COL1||COL2)) //查詢函數索引 SELECT * FROM TEST WHERE UPPER(COL1||COL2)='23'

三、什麼是Phoenix的二級索引?

Phoenix的二級索引咱們基本上已經介紹過了,咱們回過頭來繼續看Phoenix二級索引的官方定義:Secondary indexes are an orthogonal way to access data from its primary access path。經過如下例子咱們再理解下這個定義。

  1. 對錶TESTCOL1建立全局索引
CREATE INDEX IDX_COL1 ON TEST(COL1);
  1. 查詢全部字段。
select * from TEST where COL1='2';

以上查詢的查詢計劃以下:

+----------------------------------------------------------------+-----------------+----------------+--------------+ | PLAN | EST_BYTES_READ | EST_ROWS_READ | EST_INFO_TS | +----------------------------------------------------------------+-----------------+----------------+--------------+ | CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN FULL SCAN OVER TEST | null | null | null | | SERVER FILTER BY COL1 = '2' | null | null | null | +----------------------------------------------------------------+-----------------+----------------+--------------+
  1. 查詢id字段:
select id from TEST where  COL1='2';

查詢計劃以下

+---------------------------------------------------------------------------+-----------------+----------------+--------------+ | PLAN | EST_BYTES_READ | EST_ROWS_READ | EST_INFO_TS | +---------------------------------------------------------------------------+-----------------+----------------+--------------+ | CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN RANGE SCAN OVER IDX_COL1 ['2'] | null | null | null | | SERVER FILTER BY FIRST KEY ONLY | null | null | null | +---------------------------------------------------------------------------+-----------------+----------------+--------------+

兩個查詢都沒有經過hint強制指定索引表,查詢計劃顯示,查詢全部字段時發生了須要極力避免的掃全表操做(通常數據量在幾十萬級別的掃全表很容易形成集羣不穩定),而查詢id時利用索引表走了點查。從現象來看,當查詢中出現的字段都在索引表中時(能夠是索引字段或者數據表主鍵,也能夠是覆蓋索引字段),會自動走索引表,不然查詢會退化爲全表掃描。

在咱們實際應用中一個數據表會有多個索引表,爲了能讓咱們的查詢使用合理的索引表,目前都須要經過Hint去指定。

四、索引Building

Phoenix的二級索引建立有同步和異步兩種方式。

  1. 在執行CREATE INDEX IDX_COL1 ON TEST(COL1)時會進行索引數據的同步。此方法適用於數據量較小的狀況。
  2. 異步build索引須要藉助MR,建立異步索引語法和同步索引相差一個關鍵字:ASYNC
//建立異步索引
CREATE INDEX ASYNC_IDX ON DB.TEST (COL1) ASYNC //build 索引數據 ${HBASE_HOME}/bin/hbase org.apache.phoenix.mapreduce.index.IndexTool --schema DB --data-table TEST --index-table ASYNC_IDX --output-path ASYNC_IDX_HFILES

五、索引問題彙總

1)建立同步索引超時怎麼辦?

在客戶端配置文件hbase-site.xml中,把超時參數設置大一些,足夠build索引數據的時間。

<property> <name>hbase.rpc.timeout</name> <value>60000000</value> </property> <property> <name>hbase.client.scanner.timeout.period</name> <value>60000000</value> </property> <property> <name>phoenix.query.timeoutMs</name> <value>60000000</value> </property>

2)索引表最多能夠建立多少個?

建議不超過10個

3)爲何索引表多了,單條寫入會變慢?

索引表越多寫放大越嚴重。寫放大狀況能夠參考下圖。

6、MR在Ali-Phoenix上的使用

一、MR在Phoenix上的用途

1)利用MR對Phoenix表(可帶有二級索引表)進行Bulkload入庫, 其原理是直接生成主表(二級索引表)的HFILE寫入HDFS。相對於走API的數據導入方式,不只速度更快,並且對HBASE集羣的負載也會小不少。目前雲HBASE上的Phoenix支持如下數據源的Bulkload工具:

  1. CsvBulkLoadTool
  2. JsonBulkLoadTool
  3. RegexBulkLoadTool
  4. ODPSBulkLoadTool

2)利用MR Building二級索引。當主表數據量較大時,能夠經過建立異步索引,使用MR快速同步索引數據。

二、如何訪問雲HBASE的HDFS?

因爲雲HBASE上沒有MR,須要藉助外部的計算引擎(自建的HADOOP集羣或者EMR),而使用外部的計算引擎的首先面臨的問題是,如何跨集羣訪問HDFS。 1.因爲雲HBASE的HDFS端口默認是不開的,須要聯繫工做人員開通。 2.端口開通之後,要想順利的訪問HDFS是HA配置的雲HBASE集羣,須要向工做人員獲取雲HBASE的主備(emr-header-1,emr-header-2)namenode host/IP。參考以下配置模板,設置hadoop客戶端配置文件: hdfs-site.xml

  <configuration> <property> <name>dfs.nameservices</name> <value>emr-cluster</value> </property> <property> <name>dfs.client.failover.proxy.provider.emr-cluster</name> <value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value> </property> <property> <name>dfs.ha.automatic-failover.enabled.emr-cluster</name> <value>true</value> </property> <property> <name>dfs.ha.namenodes.emr-cluster</name> <value>nn1,nn2</value> </property> <property> <name>dfs.namenode.rpc-address.emr-cluster.nn1</name> <value>{emr-header-1-host}:8020</value> </property> <property> <name>dfs.namenode.rpc-address.emr-cluster.nn2</name> <value>{emr-header-2-host}:8020</value> </property> </configuration>

3.驗證訪問雲HBASE HDFS 在emr或自建集羣上訪問雲HBase集羣

hadoop dfs -ls hdfs://emr-cluster/

三、BULKLOAD PHOENIX表

以EMR訪問雲HBASE爲例。EMR集羣須要把雲HBASE HDFS的emr-cluster 相關配置和當前EMR的HDFS配置合在一塊兒造成新的配置文件,單獨存放在一個目錄(${conf-dir})下。 經過yarn/hadoop命令的--config參數指定新的配置目錄,使這些配置文件放在CLASSPATH最前面覆蓋掉當前EMR集羣hadoop_conf_dir下的配置,以便bulkload程序能識別到雲HBASE HA的HDFS URL。當在emr或自建集羣上可以訪問本身的HDFS(hadoop --config <confdir> dfs -ls /), 也可以訪問雲HBase的HDFS(hadoop --config <confdir> dfs -ls hdfs://emr-cluster/)說明配置成功了。

執行以下BULKLOAD命令

yarn --config ${CONF_DIR} \ jar ${PHOENIX_HOME}/phoenix-${version}-client.jar org.apache.phoenix.mapreduce.CsvBulkLoadTool \ --table "TABLENAME" \ --input "hdfs://emr-header-1.cluster-55090:9000/tmp/test_data" \ --zookeeper "zk1,zk2,zk3" \ --output "hdfs://emr-cluster/tmp/tmp_data"

注意: --output 配置的是雲HBASE的臨時文件,這樣直接把生成的HFILE存儲在雲HBASE的HDFS上,後續的只有簡單的move操做。不然,若是生成在EMR集羣還須要走網絡發送到雲HBASE HDFS上。

7、如何使用自增ID

在傳統關係型數據庫中設計主鍵時,自增ID常常被使用。不只可以保證主鍵的惟一,同時也能簡化業務層實現。Phoenix怎麼使用自增ID,是咱們這篇文章的重點。

一、語法說明

1)建立自增序列

CREATE SEQUENCE [IF NOT EXISTS] SCHEMA.SEQUENCE_NAME [START WITH number] [INCREMENT BY number] [MINVALUE number] [MAXVALUE number] [CYCLE] [CACHE number]
  • start用於指定第一個值。若是不指定默認爲1.
  • increment指定每次調用next value for後自增大小。若是不指定默認爲1。
  • minvaluemaxvalue通常與cycle連用, 讓自增數據造成一個環,從最小值到最大值,再從最大值到最小值。
  • cache默認爲100, 表示server端生成100個自增序列緩存在客戶端,能夠減小rpc次數。此值也能夠經過phoenix.sequence.cacheSize來配置。

示例

CREATE SEQUENCE my_sequence;-- 建立一個自增序列,初始值爲1,自增間隔爲1,將有100個自增值緩存在客戶端。 CREATE SEQUENCE my_sequence START WITH -1000 CREATE SEQUENCE my_sequence INCREMENT BY 10 CREATE SEQUENCE my_cycling_sequence MINVALUE 1 MAXVALUE 100 CYCLE; CREATE SEQUENCE my_schema.my_sequence START 0 CACHE 10

2)刪除自增序列

DROP SEQUENCE [IF EXISTS] SCHEMA.SEQUENCE_NAME

示例

DROP SEQUENCE my_sequence
DROP SEQUENCE IF EXISTS my_schema.my_sequence

二、案例

1)需求

對現有的書籍進行編號並存儲,要求編號是唯一的。存儲書籍信息的建表語句以下:

create table books( id integer not null primary key, name varchar, author varchar )SALT_BUCKETS = 8;

因爲自增ID做爲rowkey, 容易形成集羣熱點問題,因此在建立表時最好經過加鹽的方式解決這個問題

2)經過自增ID,實現惟一編碼,並簡化實現。

  • 建立自增序列,初始值爲10000,自增間隔爲1,緩存大小爲1000. CREATE SEQUENCE book_sequence START WITH 10000 INCREMENT BY 1 CACHE 1000;
  • 經過自增序列,寫入數據信息。 UPSERT INTO books(id, name, author) VALUES( NEXT VALUE FOR book_sequence,'DATA SCIENCE', 'JHONE'); UPSERT INTO books(id, name, author) VALUES( NEXT VALUE FOR book_sequence,'Effective JAVA','Joshua Bloch');
  • 查看結果

8、動態列

一、概要

動態列是指在查詢中新增字段,操做建立表時未指定的列。傳統關係型數據要實現動態列目前經常使用的方法有:設計表結構時預留新增字段位置、設計更通用的字段、列映射爲行和利用json/xml存儲字段擴展字段信息等,這些方法多少都存在一些缺陷,動態列的實現只能依賴邏輯層的設計實現。因爲Phoenix是HBase上的SQL層,藉助HBase特性實現的動態列,避免了傳統關係型數據庫動態列實現存在的問題。

二、動態列使用

示例表(用於語法說明)

CREATE TABLE EventLog ( eventId BIGINT NOT NULL, eventTime TIME NOT NULL, eventType CHAR(3) CONSTRAINT pk PRIMARY KEY (eventId, eventTime)) COLUMN_ENCODED_BYTES=0 

1) Upsert

在插入數據時指定新增列字段名和類型,並在values對應的位置設置相應的值。語法以下:

upsert into <tableName> (exists_col1, exists_col2, ... (new_col1 time, new_col2 integer, ...)) VALUES (v1, v2, ... (v1, v2, ...))

動態列寫入示例:

UPSERT INTO EventLog (eventId, eventTime, eventType, lastGCTime TIME, usedMemory BIGINT, maxMemory BIGINT) VALUES(1, CURRENT_TIME(), 'abc', CURRENT_TIME(), 512, 1024);

咱們來查詢看一下

查詢發現並沒新增列的數據,也就是經過動態列插入值時並無對錶的schema直接改變。HBase表中發生了怎麼樣的變化呢?

實際上HBase表中已經新增列以及數據。那經過動態列添加的數據怎麼查詢呢?

2)Select

動態列查詢語法

select [*|table.*|[table.]colum_name_1[AS alias1][,[table.]colum_name_2[AS alias2] …], <dy_colum_name_1>] FROM tableName (<dy_colum_name_1, type> [,<dy_column_name_2, type> ...]) [where clause] [group by clause] [having clause] [order by clause]

動態列查詢示例

SELECT eventId, eventTime, lastGCTime, usedMemory, maxMemory FROM EventLog(lastGCTime TIME, usedMemory BIGINT, maxMemory BIGINT) where eventId=1

查詢結果以下:

三、總結

Phoneix的動態列功能是非SQL標準語法,它給咱們帶來更多的靈活性,再也不爲靜態schema的字段擴展問題而困擾。然而咱們在實際應用中,應該根據本身的業務需求決定是否真的使用動態列,由於動態列的濫用會大幅度的增長咱們的維護成本。

9、分頁查詢

一、概述

所謂分頁查詢就是從符合條件的起始記錄,日後遍歷「頁大小」的行。數據庫的分頁是在server端完成的,避免客戶端一次性查詢到大量的數據,讓查詢數據數據分段展現在客戶端。對於Phoenix的分頁查詢,怎麼使用?性能怎麼樣?須要注意什麼?將會在文章中經過示例和數聽說明。

二、分頁查詢

1)語法說明

[ LIMIT { count } ] [ OFFSET start [ ROW | ROWS ] ] [ FETCH { FIRST | NEXT } [ count ] { ROW | ROWS } ONLY ]

Limit或者Fetch在order by子句後轉化爲爲top-N的查詢,其中offset子句表示從開始的位置跳過多少行開始掃描。

對於如下的offsset使用示例, 咱們可發現當offset的值爲0時,查詢結果從第一行記錄開始掃描limit指定的行數,當offset值爲1時查詢結果從第二行記錄開始開始掃描limit指定的行數...

0: jdbc:phoenix:localhost> select SS_CUSTOMER_SK from STORE_SALES where SS_ITEM_SK < 3600order by SS_ITEM_SK limit 6; +-----------------+ | SS_CUSTOMER_SK | +-----------------+ | 109734 | | null | | 168740 | | 344372 | | 249078 | | 241017 | +-----------------+ 6 rows selected (0.025 seconds) 0: jdbc:phoenix:localhost> select SS_CUSTOMER_SK from STORE_SALES where SS_ITEM_SK < 3600 order by SS_ITEM_SK limit 3 offset 0; +-----------------+ | SS_CUSTOMER_SK | +-----------------+ | 109734 | | null | | 168740 | +-----------------+ 3 rows selected (0.034 seconds) 0: jdbc:phoenix:localhost> select SS_CUSTOMER_SK from STORE_SALES where SS_ITEM_SK < 3600 order by SS_ITEM_SK limit 3 offset 1; +-----------------+ | SS_CUSTOMER_SK | +-----------------+ | null | | 168740 | | 344372 | +-----------------+ 3 rows selected (0.026 seconds) 0: jdbc:phoenix:localhost> select SS_CUSTOMER_SK from STORE_SALES where SS_ITEM_SK < 3600 order by SS_ITEM_SK limit 3 offset 2; +-----------------+ | SS_CUSTOMER_SK | +-----------------+ | 168740 | | 344372 | | 249078 | +-----------------+ 3 rows selected (0.017 seconds) 0: jdbc:phoenix:localhost> select SS_CUSTOMER_SK from STORE_SALES where SS_ITEM_SK < 3600 order by SS_ITEM_SK limit 3 offset 3; +-----------------+ | SS_CUSTOMER_SK | +-----------------+ | 344372 | | 249078 | | 241017 | +-----------------+ 3 rows selected (0.024 seconds) 

2)語法示例

SELECT * FROM TEST LIMIT 1000; SELECT * FROM TEST LIMIT 1000 OFFSET 100; SELECT * FROM TEST FETCH FIRST 100 ROWS ONLY;

三、性能測評

咱們對以下SQL的limit子句進行性能獲得如下結論。

select SS_CUSTOMER_SK  from STORE_SALES
where SS_ITEM_SK < 3600 order by SS_ITEM_SK limit <m> offset <n>

結論1:當limit的值必定時,隨着offset N的值越大,查詢性基本會線性降低。

結論2:當offset的值必定時,隨着Limit的值越大,查詢性能逐步降低。當limit的值相差一個數量級時,查詢性能也會有幾十倍的差距。

四、最後

大多數場景中分頁查詢都是和order by子句一塊兒使用的, 在這裏須要注意的是,order by的排序字段最好是主鍵,不然查詢性能會比較差。(這部分最好是在作業務層設計時就能考慮到)分頁查詢須要根據用戶的實際需求來設計,在現實產品中,通常不多有上萬行每頁的需求,頁數太大是不合理的,同時頁數太多也是不合理的。度量是否合理,仍須要根據實際需求出發。

10、全局索引設計實踐

一、概述

全局索引是Phoenix的重要特性,合理的使用二級索引能下降查詢延時,讓集羣資源得以充分利用。本文將講述如何高效的設計和使用索引。

二、全局索引說明

全局索引的根本是經過單獨的HBase表來存儲數據表的索引數據。咱們經過以下示例看索引數據和主表數據的關係。

-- 建立數據表
CREATE TABLE DATA_TABLE( A VARCHAR PRIMARY KEY, B VARCHAR, C INTEGER, D INTEGER); -- 建立索引 CREATE INDEX B_IDX ON DATA_TABLE(B)INCLUDE(C); -- 插入數據 UPSERT INTO DATA_TABLE VALUES('A','B',1,2);

當寫入數據到主表時,索引數據也會被同步到索引表中。索引表中的主鍵將會是索引列和數據表主鍵的組合值,include的列被存儲在索引表的普通列中,其目的是讓查詢更加高效,只須要查詢一次索引表就可以拿到數據,而不用去回查主表。其過程以下圖:

Phoenix表就是HBase表,而HBase Rowkey都是經過二進制數據的字典序排列存儲,也就意味着Row key前綴匹配度越高就越容易排在一塊兒。

三、全局索引設計

咱們繼續使用DATA_TABLE做爲示例表,建立以下組合索引。以前咱們已經提到索引表中的Row key是字典序存儲的,什麼樣的查詢適合這樣的索引結構呢?

CREATE INDEX B_C_D_IDX ON DATA_TABLE(B,C,D); 全部字段條件以=操做符爲例:

注:上表查詢中and條件不必定要和索引組合字段順序一致,能夠任意組合。

在實際使用中咱們也只推薦使用1~4,遵循前綴匹配原則,避免觸發掃全表。5~7條件就要掃描全表數據才能過濾出來符合這些條件的數據,因此是極力不推薦的。

四、其它

  • 對於order by字段或者group by字段仍然可以使用二級索引字段來加速查詢。
  • 儘可能經過合理的設計數據表的主鍵規避建更多的索引表,由於索引表越多寫放大越嚴重。
  • 使用了ROW_TIMESTAMP特性後不能使用全局索引
  • 對索引表適當的使用加鹽特性能提高查詢寫入性能,避免熱點。

11、查詢計劃詳解

一、概要

在數據庫中,執行計劃就是表示一條SQL將要執行的步驟,這些步驟按照不一樣的數據庫運算符號(算子)組成,具體的組成和執行方式由數據庫中的查詢優化器來決定。換而言之,執行計劃決定了SQL的執行效率。在數據庫的使用中瞭解其查詢計劃的構成,是進行查詢性能調優的必要條件。本文將詳細介紹Phoenix的查詢計劃語法、組成結構,以及一些注意事項。

二、查詢計劃

1)基本說明

在phoenix中,查詢計劃能告訴咱們以下的信息:

  • 將要掃描的CHUNK數量
  • 客戶端併發線程數量
  • 執行模式(並行或串行)
  • 查詢過濾字段或者掃描範圍
  • 將會查詢的表名
  • 估算掃描數據bytes大小(依賴stats信息)
  • 估算掃描數據量大小(依賴stats信息)
  • 估算數量bytes大小和數據量時間
  • 操做符被執行在客戶端或者服務端
  • 涉及的查詢operations(sort、filter, scan, merge, join, limit等)

2)語法

explain [select... | upsert ... select | delete...]

explain語法示例以下:

explain SELECT host FROM PTSDB WHERE host IN ('a','b'); explain UPSERT INTO t1 SELECT id FROM t2 ORDER BY K1, V1; 

3)如何選擇最優查詢計劃

檢查查詢計劃是否最優,核心有如下幾點能夠做爲參考:

  1. 儘可能避免出現FULL SCAN,尤爲對於不走索引表的單表查詢,不該該出現FULL SCAN
  2. 執行模式儘量使用並行(某些狀況必定是串行的執行模式)
  3. 儘量將對應表的過濾條件或計算下推到server端
  4. 儘量使用覆蓋索引,生成不須要回查數據表的查詢計劃

三、查詢計劃詳解

1. 操做符說明

  • UNION ALL: 表示union all查詢,操做符後面接查詢計劃中涉及查詢的數量
  • AGGREGATE INTO SINGLE ROW: 沒有groupby語句狀況下,聚合查詢結果到一行中。例如 count(*)
  • AGGREGATE INTO ORDERED DISTINCT ROWS:帶有group by的分組查詢
  • FILTER BY expression: 過濾出符合表達式條件的數據
  • INNER-JOIN: 多表Join
  • MERGE SORT: 進行merge sort排序,大可能是客戶端對多線程查詢結果進行排序
  • RANGE SCAN: 對主鍵進行範圍掃描,一般有指定start key和stop key
  • ROUND ROBIN: 對查詢沒有排序要求,併發的在客戶端發起掃描請求。
  • SKIP SCAN: Phoenix實現的一種掃描方式,一般能比Range scan得到更好的性能。
  • FULL SCAN: 全表掃描
  • LIMIT: 對查詢結果取TOP N
  • CLIENT: 在客戶端執行相關操做
  • X-CHUNK: 根據統計信息能夠把一個region分紅多個CHUNK, X在查詢計劃中表示將要掃描的CHUNK數量,此處是多線程併發掃描的,併發的數量是由客戶端線程池的大小來決定的
  • PARALLEL X-WAY:描述了有X個併發對scan作merge sort之類的客戶端操做
  • SERIAL: 單線程串行執行
  • SERVER: 在SERVER端(RS)執行相關操做

2. 查詢計劃示例說明

分組聚合查詢。查詢計劃中有5385個併發,並行對錶作範圍掃描,在server端以組合rowkey的第二列k2爲過濾條件過濾,並以k2列作聚合。

explain select count(k2) from OFFSET_TEST where k2 = '3343' group by k2; CLIENT 5385-CHUNK 2330168 ROWS 314572800 BYTES PARALLEL 5385-WAY RANGE SCAN OVER OFFSET_TEST [0] - [63] SERVER FILTER BY FIRST KEY ONLY AND K2 = '3343' SERVER AGGREGATE INTO DISTINCT ROWS BY [K2] CLIENT MERGE SORT

無排序查詢生成ROUND ROBIN查詢計劃。查詢計劃中有5385個併發,並行對錶作ROUND ROBIN的範圍掃描,在server端以組合rowkey的第二列k2爲過濾條件過濾。

explain select * from OFFSET_TEST where k2 = '3343'; CLIENT 5385-CHUNK 2330168 ROWS 314572800 BYTES PARALLEL 5385-WAY ROUND ROBIN RANGE SCAN OVER OFFSET_TEST [0] - [63] SERVER FILTER BY K2 = '3343'

有排序查詢。查詢計劃中有5385個併發,並行對錶作範圍掃描,在server端以組合rowkey的第二列k2爲過濾條件過濾並排序,最後在客戶端進行merge sort查詢結果。

explain select * from OFFSET_TEST where k2 = '3343' order by k2; CLIENT 5385-CHUNK 2330168 ROWS 314572800 BYTES PARALLEL 5385-WAY RANGE SCAN OVER OFFSET_TEST [0] - [63] SERVER FILTER BY K2 = '3343' SERVER SORTED BY [K2] CLIENT MERGE SORT

四、API訪問查詢計劃信息

String explainSql = "EXPLAIN SELECT * FROM T"; Long estimatedBytes = null; Long estimatedRows = null; Long estimateInfoTs = null; try (Statement statement = conn.createStatement(explainSql)) { int paramIdx = 1; ResultSet rs = statement.executeQuery(explainSql); //打印查詢計劃 System.out.println(QueryUtil.getExplainPlan(rs)); //獲取相關估算值 rs.next(); estimatedBytes = (Long) rs.getObject(PhoenixRuntime.EXPLAIN_PLAN_ESTIMATED_BYTES_READ_COLUMN); estimatedRows = (Long) rs.getObject(PhoenixRuntime.EXPLAIN_PLAN_ESTIMATED_ROWS_READ_COLUMN); estimateInfoTs = (Long) rs.getObject(PhoenixRuntime.EXPLAIN_PLAN_ESTIMATE_INFO_TS_COLUMN); }

五、注意事項

  • 當有兩個以上索引表時儘可能使用hint去指定查詢必需要使用的索引表,這樣能夠確保即便之後再加了索引不會影響到如今使用的查詢計劃
  • 能經過數據表組合主鍵覆蓋的查詢條件,儘可能避免建立索引表。索引表表越多,寫放大越嚴重,維護成本也會隨之增長
  • 在查詢計劃中Scan速度,SKIP SCAN > RANGE SCAN > FULL SCAN
  • 不是全部的查詢operations都能下推到server端
  • 查詢SERVER FILTER一個普通列,通常會在server端發生全表掃描操做,也須要謹慎檢查
  • 組合主鍵或者組合索引的非前綴列,做爲過濾條件列進行查詢時,通常會生成SCAN OVER的查詢計劃,但實際上這種查詢也極可能須要全表掃描,因此也須要根據實際狀況檢查確認

12、數據遷移

1. 概要

數據遷移工具是否豐富,也在必定程度上決定了數據庫的流行程度和它的生態圈。瞭解其相關工具,能讓咱們的數據遷移工做更加高效。本文主要介紹 Phoenix 的數據導入導出工具,但願給準備在 Phoenix 上作數據遷移的同窗一些幫助。

2. 數據導入導出說明

因爲在源端進行數據遷移,導入到 Phoenix 的過程當中會產生新的數據修改或寫入,這使得不停業務的實時遷移變的不簡單。如今開源的數據遷移工具都須要中止數據源端的業務來完成數據遷移。

對於準備遷移上阿里雲 HBase 的同窗這個都不是問題,咱們提供不停業務的實時遷移(HFile拷貝+WAL同步解析入庫)支持。

從導入方式上可分爲兩種:

3. BulkLoad 導入數據

經過 BulkLoad 方式導入數據能夠直接導入 Phoenix 表或者導入 HBase 表,而後經過建立 Phoenix 映射(此方法暫不作介紹)。直接導入 Phoenix 表的 Bulkload 工具,支持的數據源以下:

  • Csv數據入庫:CsvBulkloadTool
  • Json數據入庫:JsonBulkloadTool
  • 正則匹配文本入庫:RegexBulkloadTool
  • ODPS表: ODPSBulkLoadTool(僅雲HBase上支持)

其中 Csv/Json/Regex Bulkload,在開源 Phoenix 版本中已經提供了相應的工具類,具體使用參數能夠經過--help來查看,使用示例以下:

HADOOP_CLASSPATH=$(hbase mapredcp):/path/to/hbase/conf \ hadoop jar phoenix-<version>-client.jar \ org.apache.phoenix.mapreduce.CsvBulkLoadTool \ --table EXAMPLE \ --input /data/example.csv HADOOP_CLASSPATH=/path/to/hbase-protocol.jar:/path/to/hbase/conf \ hadoop jar phoenix-<version>-client.jar \ org.apache.phoenix.mapreduce.CsvBulkLoadTool \ --table EXAMPLE \ --input /data/example.csv hadoop jar phoenix-<version>-client.jar \ org.apache.phoenix.mapreduce.JsonBulkLoadTool \ --table EXAMPLE \ --input /data/example.json

4. API 數據導入導出

DataX是阿里內被普遍使用的離線數據同步工具/平臺,支持各類常見異構數據源之間高效的數據同步功能,其原理是經過 Datax 多線程同時讀取多個數據分片,使用 API 寫入到目標數據源中。 如今支持 Phoenix 4.12 版本以上的數據導出導出插件,能知足平常從關係型數據庫導入到 Phoenix,ODPS 導入到 Phoenix, Phoenix導出CSV文本等需求。

5. 總結

對於主鍵不重複的全量源數據,咱們都推薦藉助 MR 利用 Bulkload 方式導入 Phonenix(雲 HBase 自己不提供 MR 能力,須要藉助外部能訪問源集羣和目標集羣HDFS的Hadoop)。 對於天天增量數據的同步可使用 Datax(導入數據到 雲 HBase 須要提供一個能訪問源集羣和目標集羣的 ECS 運行 Datax)。

想要提升 Bulkload 的數據入庫速度,不只須要增長目標 Phoenix 表的 region 數量(新建表須要指定預分區數或者加鹽),還須要提高 MR 運行環境的集羣配置(scale out/ scale up)。DataX 提高入庫的方式主要是調整配置的線程數、batch數量,同時目標表的region數量也不能太少。

最後建議千萬級別的數量都用 Datax, 由於簡單好用。:)

原文發佈於微信公衆號 - 大數據技術與架構(import_bigdata)

原文發表時間: 2019-09-03

本文參與騰訊雲自媒體分享計劃,歡迎正在閱讀的你也加入,一塊兒分享。

發表於 2019-09-03
相關文章
相關標籤/搜索