個人主頁:http://www.linbingdong.com
簡書地址:http://www.jianshu.com/users/6cb45a00b49c/latest_articles
網上關於Phoenix的資料寥寥無幾,中文資料更是幾乎沒有。本人詳細閱讀Phoenix官網,整理成此篇中文文檔,供后人參考。如有翻譯錯誤的地方,請批評指出。
1. Phoenix定義
Phoenix最早是saleforce的一個開源項目,后來成為Apache基金的頂級項目。
Phoenix是構建在HBase上的一個SQL層,能讓我們用標准的JDBC APIs而不是HBase客戶端APIs來創建表,插入數據和對HBase數據進行查詢。
put the SQL back in NoSQL
Phoenix完全使用Java編寫,作為HBase內嵌的JDBC驅動。Phoenix查詢引擎會將SQL查詢轉換為一個或多個HBase掃描,並編排執行以生成標准的JDBC結果集。直接使用HBase API、協同處理器與自定義過濾器,對於簡單查詢來說,其性能量級是毫秒,對於百萬級別的行數來說,其性能量級是秒。
HBase的查詢工具有很多,如:Hive、Tez、Impala、Spark SQL、Phoenix等。
Phoenix通過以下方式使我們可以少寫代碼,並且性能比我們自己寫代碼更好:
- 將SQL編譯成原生的HBase scans。
- 確定scan關鍵字的最佳開始和結束
- 讓scan並行執行
- ...
使用Phoenix的公司

2. 歷史演進
- 3.0/4.0 release
ARRAY Type. 支持標准的JDBC數組類型
Sequences. 支持 CREATE/DROP SEQUENCE, NEXT VALUE FOR, CURRENT VALUE FOR也實現了
Multi-tenancy. 同一張HBase物理表上,不同的租戶可以創建相互獨立的視圖
Views. 同一張HBase物理表上可以創建不同的視圖
- 3.1/4.1 release
Apache Pig Loader . 通過pig來處理數據時支持pig加載器來利用Phoenix的性能
Derived Tables. 允許在一個FROM子句中使用SELECT子句來定義一張衍生表
Local Indexing. 后面介紹
Tracing. 后面介紹
- 3.2/4.2 release
Subqueries 支持在WHERE和FROM子句中的獨立子查詢和相關子查詢
Semi/anti joins. 通過標准的[NOT] IN 和 [NOT] EXISTS關鍵字來支持半/反連接
Optimize foreign key joins. 通過利用跳躍掃描過濾器來優化外鍵連接
Statistics Collection. 通過收集表的統計信息來提高並行查詢能力
- 3.3/4.3 release
Many-to-many joins. 支持兩邊都太大以至於無法放進內存的連接
Map-reduce Integration. 支持Map-reduce集成
Functional Indexes. 后面介紹
- 4.4 release
User Defined Functions. 后面介紹
- 4.5 release
Asynchronous Index Population. 通過一個Map-reduce job,索引可以被異步創建
- 4.6 release
Time series Optimization. 優化針對時間序列數據的查詢
- 4.7 release
Transaction Support. 后面介紹
- 4.8 release
DISTINCT Query Optimization. 使用搜索邏輯來大幅提高 SELECT DISTINCT 和 COUNT DISTINCT的查詢性能
Local Index Improvements. Reworked 后面介紹
Hive Integration. 能夠在Phoenix內使用Hive來支持大表和大表之間的連接
Namespace Mapping. 將Phoenix schema映射到HBase的命名空間來增強不同schema之間的隔離性
3. 特性
3.1 Transactions (beta) 事務
該特性還處於beta版,並非正式版。通過集成Tephra,Phoenix可以支持ACID特性。Tephra也是Apache的一個項目,是事務管理器,它在像HBase這樣的分布式數據存儲上提供全局一致事務。HBase本身在行層次和區層次上支持強一致性,Tephra額外提供交叉區、交叉表的一致性來支持可擴展性。
要想讓Phoenix支持事務特性,需要以下步驟:
- 配置客戶端hbase-site.xml
<property>
<name>phoenix.transactions.enabled</name>
<value>true</value>
</property>
- 配置服務端hbase-site.xml
<property>
<name>data.tx.snapshot.dir</name>
<value>/tmp/tephra/snapshots</value>
</property>
<property>
<name>data.tx.timeout</name>
<value>60</value>
<description> set the transaction timeout (time after which open transactions become invalid) to a reasonable value.</description>
</property>
- 配置$HBASE_HOME並啟動Tephra
./bin/tephra
通過以上配置,Phoenix已經支持了事務特性,但創建表的時候默認還是不支持的。如果想創建一個表支持事務特性,需要顯示聲明,如下:
CREATE TABLE my_table (k BIGINT PRIMARY KEY, v VARCHAR) TRANSACTIONAL=true;
就是在建表語句末尾增加 TRANSACTIONAL=true。
原本存在的表也可以更改成支持事務的,需要注意的是,事務表無法改回非事務的,因此更改的時候要小心。一旦改成事務的,就改不回去了。
ALTER TABLE my_other_table SET TRANSACTIONAL=true;
3.2 User-defined functions(UDFs) 用戶定義函數
3.2.1 概述
Phoenix從4.4.0版本開始支持用戶自定義函數。
用戶可以創建臨時或永久的用戶自定義函數。這些用戶自定義函數可以像內置的create、upsert、delete一樣被調用。臨時函數是針對特定的會話或連接,對其他會話或連接不可見。永久函數的元信息會被存儲在一張叫做SYSTEM.FUNCTION的系統表中,對任何會話或連接均可見。
3.2.2 配置
- hive-site.xml
<property>
<name>phoenix.functions.allowUserDefinedFunctions</name>
<value>true</value>
</property>
<property>
<name>fs.hdfs.impl</name>
<value>org.apache.hadoop.hdfs.DistributedFileSystem</value>
</property>
<property>
<name>hbase.rootdir</name>
<value>${hbase.tmp.dir}/hbase</value>
<description>The directory shared by region servers and into
which HBase persists. The URL should be 'fully-qualified'
to include the filesystem scheme. For example, to specify the
HDFS directory '/hbase' where the HDFS instance's namenode is
running at namenode.example.org on port 9000, set this value to:
hdfs://namenode.example.org:9000/hbase. By default, we write
to whatever ${hbase.tmp.dir} is set too -- usually /tmp --
so change this configuration or else all data will be lost on
machine restart.</description>
</property>
<property>
<name>hbase.dynamic.jars.dir</name>
<value>${hbase.rootdir}/lib</value>
<description>
The directory from which the custom udf jars can be loaded
dynamically by the phoenix client/region server without the need to restart. However,
an already loaded udf class would not be un-loaded. See
HBASE-1936 for more details.
</description>
</property>
后兩個配置需要跟hbse服務端的配置一致。
以上配置完后,在JDBC連接時還需要執行以下語句:
Properties props = new Properties();
props.setProperty("phoenix.functions.allowUserDefinedFunctions", "true");
Connection conn = DriverManager.getConnection("jdbc:phoenix:localhost", props);
以下是可選的配置,用於動態類加載的時候把jar包從hdfs拷貝到本地文件系統
<property>
<name>hbase.local.dir</name>
<value>${hbase.tmp.dir}/local/</value>
<description>Directory on the local filesystem to be used
as a local storage.</description>
</property>
3.3 Secondary Indexing 二級索引
在HBase中,只有一個單一的按照字典序排序的rowKey索引,當使用rowKey來進行數據查詢的時候速度較快,但是如果不使用rowKey來查詢的話就會使用filter來對全表進行掃描,很大程度上降低了檢索性能。而Phoenix提供了二級索引技術來應對這種使用rowKey之外的條件進行檢索的場景。
- Covered Indexes
只需要通過索引就能返回所要查詢的數據,所以索引的列必須包含所需查詢的列(SELECT的列和WHRER的列)
- Functional Indexes
從Phoeinx4.3以上就支持函數索引,其索引不局限於列,可以合適任意的表達式來創建索引,當在查詢時用到了這些表達式時就直接返回表達式結果
- Global Indexes
Global indexing適用於多讀少寫的業務場景。
使用Global indexing的話在寫數據的時候會消耗大量開銷,因為所有對數據表的更新操作(DELETE, UPSERT VALUES and UPSERT SELECT),會引起索引表的更新,而索引表是分布在不同的數據節點上的,跨節點的數據傳輸帶來了較大的性能消耗。在讀數據的時候Phoenix會選擇索引表來降低查詢消耗的時間。在默認情況下如果想查詢的字段不是索引字段的話索引表不會被使用,也就是說不會帶來查詢速度的提升。
- Local Indexes
Local indexing適用於寫操作頻繁的場景。
與Global indexing一樣,Phoenix會自動判定在進行查詢的時候是否使用索引。使用Local indexing時,索引數據和數據表的數據是存放在相同的服務器中的避免了在寫操作的時候往不同服務器的索引表中寫索引帶來的額外開銷。使用Local indexing的時候即使查詢的字段不是索引字段索引表也會被使用,這會帶來查詢速度的提升,這點跟Global indexing不同。一個數據表的所有索引數據都存儲在一個單一的獨立的可共享的表中。
3.4 Statistics Collection 統計信息收集
UPDATE STATISTICS可以更新某張表的統計信息,以提高查詢性能
3.5 Row timestamp 時間戳
從4.6版本開始,Phoenix提供了一種將HBase原生的row timestamp映射到Phoenix列的方法。這樣有利於充分利用HBase提供的針對存儲文件的時間范圍的各種優化,以及Phoenix內置的各種查詢優化。
3.6 Paged Queries 分頁查詢
Phoenix支持分頁查詢:
- Row Value Constructors (RVC)
- OFFSET with limit
3.7 Salted Tables 散步表
如果row key是自動增長的,那么HBase的順序寫會導致region server產生數據熱點的問題,Phoenix的Salted Tables技術可以解決region server的熱點問題
3.8 Skip Scan 跳躍掃描
可以在范圍掃描的時候提高性能
3.9 Views 視圖
標准的SQL視圖語法現在在Phoenix上也支持了。這使得能在同一張底層HBase物理表上創建多個虛擬表。
3.10 Multi tenancy 多租戶
通過指定不同的租戶連接實現數據訪問的隔離
3.11 Dynamic Columns 動態列
Phoenix 1.2, specifying columns dynamically is now supported by allowing column definitions to included in parenthesis after the table in the FROM clause on a SELECT statement. Although this is not standard SQL, it is useful to surface this type of functionality to leverage the late binding ability of HBase.
3.12 Bulk CSV Data Loading 大量CSV數據加載
加載CSV數據到Phoenix表有兩種方式:1. 通過psql命令以單線程的方式加載,數據量少的情況下適用。 2. 基於MapReduce的bulk load工具,適用於數據量大的情況
3.13 Query Server 查詢服務器
Phoenix4.4引入的一個單獨的服務器來提供thin客戶端的連接
3.14 Tracing 追蹤
從4.1版本開始Phoenix增加這個特性來追蹤每條查詢的蹤跡,這使用戶能夠看到每一條查詢或插入操作背后從客戶端到HBase端執行的每一步。
3.15 Metrics 指標
Phoenix提供各種各樣的指標使我們能夠知道Phoenix客戶端在執行不同SQL語句的時候其內部發生了什么。這些指標在客戶端JVM中通過兩種方式來收集:
- Request level metrics - collected at an individual SQL statement
level - Global metrics - collected at the client JVM level
4. 架構和組成
- Phoenix架構

- Phoenix在Hadoop生態系統中的位置

5. 數據存儲
Phoenix將HBase的數據模型映射到關系型世界

6. 對QL的支持
支持的命令如下:
- SELECT
Example:
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
- UPSERT VALUES
Example:
UPSERT INTO TEST VALUES('foo','bar',3);
UPSERT INTO TEST(NAME,ID) VALUES('foo',123);
- UPSERT SELECT
Example:
UPSERT INTO test.targetTable(col1, col2) SELECT col3, col4 FROM test.sourceTable WHERE col5 < 100
UPSERT INTO foo SELECT * FROM bar;
- DELETE
Example:
DELETE FROM TEST;
DELETE FROM TEST WHERE ID=123;
DELETE FROM TEST WHERE NAME LIKE 'foo%';
- CREATE TABLE
CREATE TABLE my_schema.my_table ( id BIGINT not null primary key, date)
CREATE TABLE my_table ( id INTEGER not null primary key desc, date DATE not null,m.db_utilization DECIMAL, i.db_utilization) m.DATA_BLOCK_ENCODING='DIFF'
CREATE TABLE stats.prod_metrics ( host char(50) not null, created_date date not null,txn_count bigint CONSTRAINT pk PRIMARY KEY (host, created_date) )
CREATE TABLE IF NOT EXISTS "my_case_sensitive_table"
( "id" char(10) not null primary key, "value" integer)
DATA_BLOCK_ENCODING='NONE',VERSIONS=5,MAX_FILESIZE=2000000 split on (?, ?, ?)
CREATE TABLE IF NOT EXISTS my_schema.my_table (org_id CHAR(15), entity_id CHAR(15), payload binary(1000),CONSTRAINT pk PRIMARY KEY (org_id, entity_id) )TTL=86400
- DROP TABLE
Example:
DROP TABLE my_schema.my_table;
DROP TABLE IF EXISTS my_table;
DROP TABLE my_schema.my_table CASCADE;
- CREATE FUNCTION
Example:
CREATE FUNCTION my_reverse(varchar) returns varchar as 'com.mypackage.MyReverseFunction' using jar 'hdfs:/localhost:8080/hbase/lib/myjar.jar'
CREATE FUNCTION my_reverse(varchar) returns varchar as 'com.mypackage.MyReverseFunction'
CREATE FUNCTION my_increment(integer, integer constant defaultvalue='10') returns integer as 'com.mypackage.MyIncrementFunction' using jar '/hbase/lib/myincrement.jar'
CREATE TEMPORARY FUNCTION my_reverse(varchar) returns varchar as 'com.mypackage.MyReverseFunction' using jar 'hdfs:/localhost:8080/hbase/lib/myjar.jar'
- DROP FUNCTION
Example:
DROP FUNCTION IF EXISTS my_reverse
DROP FUNCTION my_reverse
- CREATE VIEW
Example:
CREATE VIEW "my_hbase_table"( k VARCHAR primary key, "v" UNSIGNED_LONG) default_column_family='a';
CREATE VIEW my_view ( new_col SMALLINT ) AS SELECT * FROM my_table WHERE k = 100;
CREATE VIEW my_view_on_view AS SELECT * FROM my_view WHERE new_col > 70;
- DROP VIEW
Example:
DROP VIEW my_view
DROP VIEW IF EXISTS my_schema.my_view
DROP VIEW IF EXISTS my_schema.my_view CASCADE
- CREATE SEQUENCE
Example:
CREATE SEQUENCE my_sequence;
CREATE SEQUENCE my_sequence START WITH -1000
CREATE SEQUENCE my_sequence INCREMENT BY 10
CREATE SEQUENCE my_schema.my_sequence START 0 CACHE 10
- DROP SEQUENCE
Example:
DROP SEQUENCE my_sequence
DROP SEQUENCE IF EXISTS my_schema.my_sequence
- ALTER
Example:
ALTER TABLE my_schema.my_table ADD d.dept_id char(10) VERSIONS=10
ALTER TABLE my_table ADD dept_name char(50), parent_id char(15) null primary key
ALTER TABLE my_table DROP COLUMN d.dept_id, parent_id;
ALTER VIEW my_view DROP COLUMN new_col;
ALTER TABLE my_table SET IMMUTABLE_ROWS=true,DISABLE_WAL=true;
- CREATE INDEX
Example:
CREATE INDEX my_idx ON sales.opportunity(last_updated_date DESC)
CREATE INDEX my_idx ON log.event(created_date DESC) INCLUDE (name, payload) SALT_BUCKETS=10
CREATE INDEX IF NOT EXISTS my_comp_idx ON server_metrics ( gc_time DESC, created_date DESC ) DATA_BLOCK_ENCODING='NONE',VERSIONS=?,MAX_FILESIZE=2000000 split on (?, ?, ?)
CREATE INDEX my_idx ON sales.opportunity(UPPER(contact_name))
- DROP INDEX
Example:
DROP INDEX my_idx ON sales.opportunity
DROP INDEX IF EXISTS my_idx ON server_metrics
- ALTER INDEX
Example:
ALTER INDEX my_idx ON sales.opportunity DISABLE
ALTER INDEX IF EXISTS my_idx ON server_metrics REBUILD
- EXPLAIN
Example:
EXPLAIN SELECT NAME, COUNT(*) FROM TEST GROUP BY NAME HAVING COUNT(*) > 2;
EXPLAIN SELECT entity_id FROM CORE.CUSTOM_ENTITY_DATA WHERE organization_id='00D300000000XHP' AND SUBSTR(entity_id,1,3) = '002' AND created_date < CURRENT_DATE()-1;
- UPDATE STATISTICS
Example:
UPDATE STATISTICS my_table
UPDATE STATISTICS my_schema.my_table INDEX
UPDATE STATISTICS my_index
UPDATE STATISTICS my_table COLUMNS
UPDATE STATISTICS my_table SET phoenix.stats.guidepost.width=50000000
- CREATE SCHEMA
Example:
CREATE SCHEMA IF NOT EXISTS my_schema
CREATE SCHEMA my_schema
- USE
Example:
USE my_schema
USE DEFAULT
- DROP SCHEMA
Example:
DROP SCHEMA IF EXISTS my_schema
DROP SCHEMA my_schema
7. 安裝部署
7.1 安裝預編譯的Phoenix
-
下載並解壓最新版的phoenix-[version]-bin.tar包
-
將phoenix-[version]-server.jar放入服務端和master節點的HBase的lib目錄下
-
重啟HBase
-
將phoenix-[version]-client.jar添加到所有Phoenix客戶端的classpath
7.2 使用Phoenix
7.2.1 命令行
若要在命令行執行交互式SQL語句:
1.切換到bin目錄
2.執行以下語句
$ sqlline.py localhost
若要在命令行執行SQL腳本
$ sqlline.py localhost ../examples/stock_symbol.sql

7.2.2 客戶端
SQuirrel是用來連接Phoenix的客戶端。
SQuirrel安裝步驟如下:
1. Remove prior phoenix-[*oldversion*]-client.jar from the lib directory of SQuirrel, copy phoenix-[*newversion*]-client.jar to the lib directory (*newversion* should be compatible with the version of the phoenix server jar used with your HBase installation)
2. Start SQuirrel and add new driver to SQuirrel (Drivers -> New Driver)
3. In Add Driver dialog box, set Name to Phoenix, and set the Example URL to jdbc:phoenix:localhost.
4. Type “org.apache.phoenix.jdbc.PhoenixDriver” into the Class Name textbox and click OK to close this dialog.
5. Switch to Alias tab and create the new Alias (Aliases -> New Aliases)
6. In the dialog box, Name: *any name*, Driver: Phoenix, User Name: *anything*, Password: *anything*
7. Construct URL as follows: jdbc:phoenix: *zookeeper quorum server*. For example, to connect to a local HBase use: jdbc:phoenix:localhost
8. Press Test (which should succeed if everything is setup correctly) and press OK to close.
9. Now double click on your newly created Phoenix alias and click Connect. Now you are ready to run SQL queries against Phoenix.

8. 測試
8.1 Pherf
Pherf是可以通過Phoenix來進行性能和功能測試的工具。Pherf可以用來生成高度定制的數據集,並且測試SQL在這些數據集上的性能。
8.1.1 構建Pherf
Pherf是在用maven構建Phoenix的過程中同時構建的。可以用兩種不同的配置來構建:
- 集群(默認)
This profile builds Pherf such that it can run along side an existing cluster. The dependencies are pulled from the HBase classpath.
- 獨立
This profile builds all of Pherf’s dependencies into a single standalone jar. The deps will be pulled from the versions specified in Phoenix’s pom.
- 構建全部的Phoenix。包含Pherf的默認配置。
mvn clean package -DskipTests
- 用Pherf的獨立配置來構建Phoenix。
mvn clean package -P standalone -DskipTests
8.1.2 安裝
用以上的Maven命令構建完Pherf后,會在該模塊的目標目錄下生成一個zip文件。
- 將該zip文件解壓到合適的目錄
- 配置
env.sh文件 - ./pherf.sh -h
- 想要在一個真正的集群上測試,運行如下命令:
./pherf.sh -drop all -l -q -z localhost -schemaFile .*user_defined_schema.sql -scenarioFile .*user_defined_scenario.xml
8.1.3 命令示例
- 列出所有可運行的場景文件
$./pherf.sh -listFiles
- 刪掉全部場景文件中存在的特定的表、加載和查詢數據
$./pherf.sh -drop all -l -q -z localhost
8.1.4 參數
-h Help
-l Apply schema and load data
-q Executes Multi-threaded query sets and write results
-z [quorum] Zookeeper quorum
-m Enable monitor for statistics
-monitorFrequency [frequency in Ms] _Frequency at which the monitor will snopshot stats to log file.
-drop [pattern] Regex drop all tables with schema name as PHERF. Example drop Event tables: -drop .(EVENT). Drop all: -drop .* or -drop all*
-scenarioFile Regex or file name of a specific scenario file to run.
-schemaFile Regex or file name of a specific schema file to run.
-export Exports query results to CSV files in CSV_EXPORT directory
-diff Compares results with previously exported results
-hint Executes all queries with specified hint. Example SMALL
-rowCountOverride
-rowCountOverride [number of rows] Specify number of rows to be upserted rather than using row count specified in schema
8.1.5 為數據生成增加規則
8.1.6 定義場景
8.1.7 結果
結果實時寫入結果目錄中。可以打開.jpg格式文件來實時可視化。
8.1.8 測試
Run unit tests: mvn test -DZK_QUORUM=localhost
Run a specific method: mvn -Dtest=ClassName#methodName test
More to come...
8.2 性能
Phoenix通過以下方法來奉行把計算帶到離數據近的地方的哲學:
-
協處理器
在服務端執行操作來最小化服務端和客戶端的數據傳輸 -
定制的過濾器
為了刪減數據使之盡可能地靠近源數據並最小化啟動代價,Phoenix使用原生的HBase APIs而不是使用Map/Reduce框架
8.2.1 Phoenix對比相近產品
8.2.1.1 Phoenix vs Hive (running over HDFS and HBase)

Query: 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)
8.2.1.2 Phoenix vs Impala (running over HBase)

Query: 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)
8.2.2 Latest Automated Performance Run
Latest Automated Performance Run | Automated Performance Runs History
8.2.3 Phoenix1.2性能提升
- Essential Column Family

- Skip Scan

- Salting

- Top-N

9. 參考資料
- http://phoenix.apache.org
- http://phoenix.apache.org/Phoenix-in-15-minutes-or-less.html
- http://hadooptutorial.info/apache-phoenix-hbase-an-sql-layer-on-hbase/
- http://www.phoenixframework.org/docs/resources
- https://en.wikipedia.org/wiki/Apache_Phoenix

歡迎進入博客 :linbingdong.com 獲取最新文章哦~

歡迎關注公眾號: FullStackPlan 獲取更多干貨哦~
