Hbase與Phoenix整合
一.簡介
Phoenix是HBase的開源SQL面板,可以理解為一個HBase的客戶端工具。
好處
1)可以使用標準JDBC API代替HBase客戶端API來建立表,插入資料和查詢HBase資料
2)操作簡單:DML命令以及通過DDL命令建立和操作表和版本化增量更改;
3)支援HBase二級索引建立。
二.安裝
1)官網地址
2)Phoenix部署
(1)上傳並解壓tar包
[hadoop@hadoop102 module]$ tar -zxvf /opt/software/apache-phoenix-5.0.0-HBase-2.0-bin.tar.gz -C /opt/module [hadoop@hadoop102 module]$ mv apache-phoenix-5.0.0-HBase-2.0-bin.tar.gz phoenix
(2)配置環境變數,soure一下
#phoenix
export PHOENIX_HOME=/opt/module/phoenix
export PHOENIX_CLASSPATH=$PHOENIX_HOME
export PATH=$PATH:$PHOENIX_HOME/bin
(3)複製server包並分發到各個節點的hbase/lib
[hadoop@hadoop102 module]$ cd /opt/module/phoenix/ [hadoop@hadoop102 module]$ cd /opt/module/phoenix/ [hadoop@hadoop102 phoenix]$ cp phoenix-5.0.0-HBase-2.0-server.jar /opt/module/hbase/lib/ [hadoop@hadoop102 phoenix]$ xsync/opt/module/hbase/lib/
(4)配置檔案
修改%Phoenix_HOME%/bin/hbase-site.xml ,然後分發各個HBase節點
<!-- 二級索引相關配置 --> <property> <name>hbase.region.server.rpc.scheduler.factory.class</name> <value>org.apache.hadoop.hbase.ipc.PhoenixRpcSchedulerFactory</value> <description>Factory to create the Phoenix RPC Scheduler that uses separate queues for index and metadata updates</description> </property> <property> <name>hbase.rpc.controllerfactory.class</name> <value>org.apache.hadoop.hbase.ipc.controller.ServerRpcControllerFactory</value> <description>Factory to create the Phoenix RPC Scheduler that uses separate queues for index and metadata updates</description> </property> <property> <name>hbase.coprocessor.abortonerror</name> <value>false</value> </property> <property> <name>hbase.regionserver.wal.codec</name> <value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value> </property> <!-- 開啟phoenix對hbase的表的對映 --> <property> <name>phoenix.schema.isNamespaceMappingEnabled</name> <value>true</value> </property> <property> <name>phoenix.schema.mapSystemTablesToNamespace</name> <value>true</value> </property>
修改%HBASE_HOME%/conf/hbase-site.xml檔案
<!-- 開啟phoenix對hbase的表的對映 -->
<property>
<name>phoenix.schema.isNamespaceMappingEnabled</name>
<value>true</value>
</property>
<property>
<name>phoenix.schema.mapSystemTablesToNamespace</name>
<value>true</value>
</property>
(5)啟動zk,HDFS,Hbase,然後連線Phoenix
[hadoop@hadoop102 phoenix]$ bin/sqlline.py hadoop102,hadoop103,hadoop104:2181
[hadoop@hadoop102 ~]$ sqlline.py hadoop102,hadoop103,hadoop104:2181
Setting property: [incremental, false]
Setting property: [isolation, TRANSACTION_READ_COMMITTED]
issuing: !connect jdbc:phoenix:hadoop102,hadoop103,hadoop104:2181 none none org.apache.phoenix.jdbc.PhoenixDriver
Connecting to jdbc:phoenix:hadoop102,hadoop103,hadoop104:2181
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/opt/module/phoenix/phoenix-5.0.0-HBase-2.0-client.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/opt/module/hadoop-3.1.3/share/hadoop/common/lib/slf4j-log4j12-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
20/07/17 18:10:47 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
Connected to: Phoenix (version 5.0)
Driver: PhoenixEmbeddedDriver (version 5.0)
Autocommit status: true
Transaction isolation: TRANSACTION_READ_COMMITTED
Building list of tables and columns for tab-completion (set fastconnect to true to skip)...
148/148 (100%) Done
Done
sqlline version 1.2.0
0: jdbc:phoenix:hadoop102,hadoop103,hadoop104>
三.Phoenix Shell操作
語法操作可參考官網:https://phoenix.apache.org/language/index.html#
在phoenix中,預設情況下,庫名,表名,欄位名等會自動轉換為大寫,若要小寫,使用雙引號,如"us_population"。
SCHEMA操作
Phoenix中將HBase的namespace叫做SCHEMA,想到於mysql中的庫的概念。
1.建立schema
CREATE SCHEMA IF NOT EXISTS "庫名"
2.使用schema
USE "庫名"
預設表
USE DEFAULT
3.刪除schema
DROP SCHEMA "庫名" --前提表都刪完。
表操作
建立一個新表。如果
HBase
表和所引用的列族不存在,則將建立它們。在建立時,為了提高查詢效能,如果沒有明確定義任何列族,則將一個空鍵值作為預設列族
1.顯示所有表
!table
2.建立表
Hbase中不存在表名相同的表,不然就成了對映HBase的表。
CREATE TABLE IF NOT EXISTS "student"(
id VARCHAR primary key,
name VARCHAR,
age VARCHAR);
3.表資料的增刪改查
upsert into "student" values('1001','zhangsan','20');
upsert into "student" values('1002','lisi','22');
select * from "student" ;
delete from "student" where id='1002';
注意:
1)upsert:表中的主鍵不存在就是插入,存在就是更新
2)where的欄位值要加單引號 ' ', 欄位名的小寫是加雙引號,別弄混了
對比Hbase中的表結構看一下
4.刪除表
drop table student;
5.退出命名行
!quit
表對映
預設情況下,直接在Hbase中建立的表,通過phoenix是檢視不到的。如果需要在phoenix中操作直接在hbase中建立的表,則需要在phoenix中進行表的對映。對映方式有兩種:檢視對映和表對映
在Hbase中建立測試表:(名稱空間:bigdata , 表名:map_test)
表結構
info1 | info2 | |
---|---|---|
rowkey | name | address |
1001 | zhangsan | BeiJing |
1002 | lisi | ShenZhen |
$ cd /home/hadoop/hbase/bin
$ ./hbase shell 進入hbase命令列
hbase(main):008:0> create 'bigdata:map_test','info1','info2'
hbase(main):011:0> put 'bigdata:map_test','1001','info1:name','zhangsan'
hbase(main):012:0> put 'bigdata:map_test','1001','info1:address','BeiJing'
hbase(main):013:0> put 'bigdata:map_test','1002','info1:name','lisi'
hbase(main):014:0> put 'bigdata:map_test','1002','info2:address','ShenZhen'
#Scan驗證一下
hbase(main):015:0> scan 'bigdata:map_test'
ROW COLUMN+CELL
1001 column=info1:address, timestamp=1594985273813, value=BeiJing
1001 column=info1:name, timestamp=1594985243947, value=zhangsan
1002 column=info1:name, timestamp=1594985290799, value=lisi
1002 column=info2:address, timestamp=1594985311918, value=ShenZhen
1.檢視對映
Phoenix建立的檢視是隻讀的,所以只能用來做查詢,無法通過檢視對源資料進行修改等操作。
1)建立檢視
create view"bigdata"."map_test"(
"empid" varchar primary key,
"info1"."name" varchar,
"info2"."adress"varchar);
2)刪除檢視
drop view "bigdata"."map_test";
注意=:建立檢視前,如果HBase不是在預設名稱空間,需要在Phoenix建立對應的Schema。
2.表對映
使用Apache Phoenix建立對HBase的表對映,有兩種方法:
a)當HBase中不存在表時,可以直接使用create table指令建立需要的表,系統將會自動在Phoenix和HBase中建立表,並會根據指令內的引數對錶結構進行初始化。
b)當HBase中已經存在表時,可以以類似建立檢視的方式建立關聯表,只需要將create view改為create table即可。
注意後面加column_encoded_bytes=0,不然Phoenix用自己的編碼列的值就無法檢視。
錯誤示例
正確示例
比較坑,如果建立錯了,重新對映的話,只能刪除對映表,會把HBase的表也刪 了。所以不要建立錯......
create table "bigdata"."map_test"(
"empid" varchar primary key,
"info1"."name" varchar,
"info2"."adress"varchar)column_encoded_bytes=0;
四.Phoenix Java API 操作
pom依賴
<dependencies>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>2.0.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.phoenix</groupId>
<artifactId>phoenix-queryserver-client</artifactId>
<version>5.0.0-HBase-2.0</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-server</artifactId>
<version>2.0.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<version>1.10.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
操作Phoenix
package com.bigdata.phoenix;
import org.apache.phoenix.queryserver.client.Driver;
import org.apache.phoenix.queryserver.client.ThinClientUtil;
import org.junit.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class PhoenixDemo {
@Test
public void createTable() throws Exception{
//1、載入驅動
Class.forName("org.apache.phoenix.queryserver.client.Driver");
//2、獲取連線
String url = ThinClientUtil.getConnectionUrl("hadoop102", 8765);
System.out.println(url);
Connection connection = DriverManager.getConnection(url);
//3、建立Statement物件
String sql = "create table xxx(" +
"id varchar primary key," +
"name varchar," +
"age varchar)COLUMN_ENCODED_BYTES=0";
PreparedStatement statement = connection.prepareStatement(sql);
//4、執行sql操作
statement.execute();
//5、關閉
statement.close();
connection.close();
}
/**
*
* @throws Exception
*/
@Test
public void insert() throws Exception{
//1、載入驅動
Class.forName("org.apache.phoenix.queryserver.client.Driver");
//2、獲取連線
String url = ThinClientUtil.getConnectionUrl("hadoop102", 8765);
System.out.println(url);
Connection connection = DriverManager.getConnection(url);
//connection.setAutoCommit(true);
//3、獲取statement物件
PreparedStatement statement = connection.prepareStatement("upsert into xxx values(?,?,?)");
//4、給引數賦值
statement.setString(1,"1001");
statement.setString(2,"zhangsan");
statement.setString(3,"20");
//5、執行插入
statement.execute();
connection.commit();
//6、關閉
statement.close();
connection.close();
}
@Test
public void query() throws Exception{
//1、載入驅動
Class.forName("org.apache.phoenix.queryserver.client.Driver");
//2、獲取連線
String url = ThinClientUtil.getConnectionUrl("hadoop102", 8765);
System.out.println(url);
Connection connection = DriverManager.getConnection(url);
//connection.setAutoCommit(true);
//3、獲取statement物件
PreparedStatement statement = connection.prepareStatement("select * from xxx");
ResultSet resultSet = statement.executeQuery();
while (resultSet.next()){
String id = resultSet.getString("id");
String name = resultSet.getString("name");
String age = resultSet.getString("age");
System.out.println("id="+id+",name="+name+",age="+age);
}
statement.close();
connection.close();
}
}
五.二級索引
思考:為啥建立二級索引?
現在有一個map_test表
0: jdbc:phoenix:hadoop102,hadoop103,hadoop104> select * from "map_test";
+--------+-----------+------------+
| empid | name | adress |
+--------+-----------+------------+
| 1001 | zhangsan | BeiJing |
| 1002 | lisi | ShenZhen |
| 1003 | wangwu | GuangZhou |
+--------+-----------+------------+
對於Hbase,如果想精確定位到某行記錄,唯一的辦法就是通過rowkey查詢。如果不通過rowkey查詢資料,就必須逐行比較每一行的值,對於較大的表,全表掃描的代價是不可接受的。
沒建立索引前:
當我們針對某一列的值進行查詢的話,如name=“zhangsan”,沒建立二級索引之前只能全表掃描過濾。
建立索引後:
那麼我們可以針對map_test表建議對應得索引表。
create index "map_test_index" on "bigdata"."map_test" ("info1"."name");
全域性索引
Global Index是預設的索引格式,建立全域性索引時,會在HBase中建立一張新表。也就是說索引資料和資料表是存放在不同的表中的,因此全域性索引適用於多讀少寫的業務場景。
寫資料的時候會消耗大量開銷,因為索引表也要更新,而索引表是分佈在不同的資料節點上的,跨節點的資料傳輸帶來了較大的效能消耗。
在讀資料的時候Phoenix會選擇索引表來降低查詢消耗的時間。
1)建立單個欄位的全域性索引
CREATE INDEX my_index ON my_table (my_col);
示例:
create index "map_test_index" on "bigdata"."map_test" ("info1"."name");
2)建立攜帶其他欄位的全域性索引
CREATE INDEX my_index ON my_table (v1) INCLUDE (v2);
示例:
create index "map_test_index" on "bigdata"."map_test" ("info1"."name") include ("info2"."adress");
3)刪除索引表
示例:
drop index map_test_index on "bigdata"."map_test";
去Hbase中看一下
可以發現建立了兩張表,索引表的的rowkey=索引欄位的值+原表的rowkey,Value是inclue的列值
本地索引
Local Index適用於寫操作頻繁的場景。
索引資料和資料表的資料是存放在同一張表中(且是同一個Region),避免了在寫操作的時候往不同伺服器的索引表中寫索引帶來的額外開銷。查詢的欄位不是索引欄位索引表也會被使用,這會帶來查詢速度的提升。
建立:local關鍵字
create index "map_test_index" on "bigdata"."map_test" ("info1"."name");
去Hbase中看一下
可以發現在Hbase中並沒有單獨的創一個索引表,在原表中為每條資料增加一個索引。
總結:
二級索引其實可以理解就是在原表的基礎上,再建立一張索引表,索引包的rowkey=索引欄位+原表rowkey。當查詢的列值的時候,會先走索引表找到列值對應得原表中的rowkey,然後根據rowkey再去原表中查對應得資料。