Phoenix使用
Phoenix
Hbase适合存储大量的对关系运算要求低的NOSQL数据,受Hbase 设计上的限制不能直接使用原生的API执行在关系数据库中普遍使用的条件判断和聚合等操作。Hbase很优秀,一些团队寻求在Hbase之上提供一种更面向普通开发人员的操作方式,Apache Phoenix即是。
Phoenix 基于Hbase给面向业务的开发人员提供了以标准SQL的方式对Hbase进行查询操作,并支持标准SQL中大部分特性:条件运算,分组,分页,等高级查询语法。
Phoenix1、Phoenix 搭建1、关闭hbase集群,在master中执行2、上传解压配置环境变量3、将phoenix-4.15.0-HBase-1.4-server.jar复制到所有节点的hbase lib目录下 4、启动hbase , 在master中执行5、配置环境变量2、Phoenix使用1、连接sqlline2、常用命令3、phoenix表映射3.1、视图映射3.2、表映射3、Phoenix二级索引1、开启索引支持2、创建索引2.1、全局索引2.2、本地索引2.3、覆盖索引4、Phoenix JDBC
1、Phoenix 搭建
Phoenix 4.15 HBase 1.4.6 hadoop 2.7.6
1、关闭hbase集群,在master中执行
stop-hbase.sh
2、上传解压配置环境变量
解压
tar -xvf apache-phoenix-4.15.0-HBase-1.4-bin.tar.gz -C /usr/local/soft/
改名
mv apache-phoenix-4.15.0-HBase-1.4-bin phoenix-4.15.0
mv phoenix-4.15.0-HBase-1.4-server.jar ../hbase-1.4.6/lib/
3、将phoenix-4.15.0-HBase-1.4-server.jar复制到所有节点的hbase lib目录下
scp /usr/local/soft/phoenix-4.15.0/phoenix-4.15.0-HBase-1.4-server.jar master:/usr/local/soft/hbase-1.4.6/lib/ scp /usr/local/soft/phoenix-4.15.0/phoenix-4.15.0-HBase-1.4-server.jar node1:/usr/local/soft/hbase-1.4.6/lib/ scp /usr/local/soft/phoenix-4.15.0/phoenix-4.15.0-HBase-1.4-server.jar node2:/usr/local/soft/hbase-1.4.6/lib/
4、启动hbase , 在master中执行
start-hbase.sh
5、配置环境变量
vim /etc/profile
2、Phoenix使用
1、连接sqlline
sqlline.py master,node1,node2 # 出现 163/163 (100%) Done Done sqlline version 1.5.0 0: jdbc:phoenix:master,node1,node2>
2、常用命令
# 1、创建表 CREATE TABLE IF NOT EXISTS STUDENT ( id VARCHAR NOT NULL PRIMARY KEY, name VARCHAR, age BIGINT, gender VARCHAR , clazz VARCHAR ); # 2、显示所有表 !table # 3、插入数据 upsert into STUDENT values('1500100004','葛德曜',24,'男','理科三班'); upsert into STUDENT values('1500100005','宣谷芹',24,'男','理科六班'); upsert into STUDENT values('1500100006','羿彦昌',24,'女','理科三班'); # 4、查询数据,支持大部分sql语法, select * from STUDENT ; select * from STUDENT where age=24; select gender ,count(*) from STUDENT group by gender; select * from student order by gender; # 5、删除数据 delete from STUDENT where id='1500100006'; # 6、删除表 drop table STUDENT; # 7、退出命令行 !quit 更多语法参照官网 https://phoenix.apache.org/language/index.html#upsert_select
3、phoenix表映射
默认情况下,直接在hbase中创建的表,通过phoenix是查看不到的
如果需要在phoenix中操作直接在hbase中创建的表,则需要在phoenix中进行表的映射。映射方式有两种:视图映射和表映射
3.1、视图映射
Phoenix创建的视图是只读的,所以只能用来做查询,无法通过视图对源数据进行修改等操作
# hbase shell 进入hbase命令行 hbase shell # 创建hbase表 create 'test','name','company' # 插入数据 put 'test','001','name:firstname','zhangsan1' put 'test','001','name:lastname','zhangsan2' put 'test','001','company:name','数加' put 'test','001','company:address','合肥' # 在phoenix创建视图, primary key 对应到hbase中的rowkey create view "test"( empid varchar primary key, "name"."firstname" varchar, "name"."lastname" varchar, "company"."name" varchar, "company"."address" varchar )column_encoded_bytes=0; CREATE view "student" ( id VARCHAR NOT NULL PRIMARY KEY, "info"."name" VARCHAR, "info"."age" VARCHAR, "info"."gender" VARCHAR , "info"."clazz" VARCHAR ) column_encoded_bytes=0; # 在phoenix查询数据,表名通过双引号引起来 select * from "test"; # 删除视图 drop view "test";
3.2、表映射
使用Apache Phoenix创建对HBase的表映射,有两类:
1) 当HBase中已经存在表时,可以以类似创建视图的方式创建关联表,只需要将create view改为create table即可。
2)当HBase中不存在表时,可以直接使用create table指令创建需要的表,并且在创建指令中可以根据需要对HBase表结构进行显示的说明。
第1)种情况下,如在之前的基础上已经存在了test表,则表映射的语句如下:
create table "test" ( empid varchar primary key, "name"."firstname" varchar, "name"."lastname"varchar, "company"."name" varchar, "company"."address" varchar )column_encoded_bytes=0; upsert into "test" values('1','2','3','4','5'); CREATE table "student" ( id VARCHAR NOT NULL PRIMARY KEY, "info"."name" VARCHAR, "info"."age" VARCHAR, "info"."gender" VARCHAR , "info"."clazz" VARCHAR ) column_encoded_bytes=0; upsert into "student" values('1500110004','葛德曜','24','n ü','理科三班');
使用create table创建的关联表,如果对表进行了修改,源数据也会改变,同时如果关联表被删除,源表也会被删除。但是视图就不会,如果删除视图,源数据不会发生改变。
3、Phoenix二级索引
对于Hbase,如果想精确定位到某行记录,唯一的办法就是通过rowkey查询。如果不通过rowkey查找数据,就必须逐行比较每一行的值,对于较大的表,全表扫描的代价是不可接受的。
1、开启索引支持
# 关闭hbase集群 stop-hbase.sh # 在/usr/local/soft/hbase-1.4.6/conf/hbase-site.xml中增加如下配置 <property> <name>hbase.regionserver.wal.codec</name> <value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value> </property> <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> # 同步到所有节点 scp hbase-site.xml node1:`pwd` scp hbase-site.xml node2:`pwd` # 修改phoenix目录下的bin目录中的hbase-site.xml <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> # 启动hbase start-hbase.sh # 重新进入phoenix客户端 sqlline.py master,node1,node2
2、创建索引
2.1、全局索引
(全局索引创建完成后,默认在hbase中是可以看到的,可以操作,如scan、count等,可以获取数据)
全局索引适合读多写少的场景。如果使用全局索引,读数据基本不损耗性能,所有的性能损耗都来源于写数据。数据表的添加、删除和修改都会更新相关的索引表(数据删除了,索引表中的数据也会删除;数据增加了,索引表的数据也会增加)
注意: 对于全局索引在默认情况下,在查询语句中检索的列如果不在索引表中,Phoenix不会使用索引表将,除非使用hint。
# 创建DIANXIN.sql CREATE TABLE IF NOT EXISTS DIANXIN ( mdn VARCHAR , start_date VARCHAR , end_date VARCHAR , county VARCHAR, x DOUBLE , y DOUBLE, bsid VARCHAR, grid_id VARCHAR, biz_type VARCHAR, event_type VARCHAR , data_source VARCHAR , CONSTRAINT PK PRIMARY KEY (mdn,start_date) ) column_encoded_bytes=0; # 上传数据DIANXIN.csv # 导入数据 psql.py master,node1,node2 DIANXIN.sql DIANXIN.csv # 创建全局索引 CREATE INDEX DIANXIN_INDEX ON DIANXIN ( end_date ); # 查询数据 ( 索引未生效) select * from DIANXIN where end_date = '20180503154014'; # 强制使用索引 (索引生效) hint select /*+ INDEX(DIANXIN DIANXIN_INDEX) */ * from DIANXIN where end_date = '20180503154014'; select /*+ INDEX(DIANXIN DIANXIN_INDEX) */ * from DIANXIN where end_date = '20180503154014' and start_date = '20180503154614'; # 取索引列,(索引生效) select end_date from DIANXIN where end_date = '20180503154014'; # 创建多列索引 CREATE INDEX DIANXIN_INDEX1 ON DIANXIN ( end_date,COUNTY ); 注意:查询的时候,如果前面出现的某一列既不在rowkey中,也不在索引列中,默认是不会生效的。 # 多条件查询 (索引生效) select end_date,MDN,COUNTY from DIANXIN where end_date = '20180503154014' and COUNTY = '8340104'; # 查询所有列 (索引未生效) select * from DIANXIN where end_date = '20180503154014' and COUNTY = '8340104'; # 查询所有列 (索引生效) select /*+ INDEX(DIANXIN DIANXIN_INDEX1) */ * from DIANXIN where end_date = '20180503154014' and COUNTY = '8340104'; # 单条件 (索引未生效) select end_date from DIANXIN where COUNTY = '8340103'; # 单条件 (索引生效) end_date 在前 select COUNTY from DIANXIN where end_date = '20180503154014'; # 删除索引 drop index DIANXIN_INDEX on DIANXIN;
2.2、本地索引
本地索引适合写多读少的场景,或者存储空间有限的场景。和全局索引一样,Phoenix也会在查询的时候自动选择是否使用本地索引。本地索引因为索引数据和原数据存储在同一台机器上,避免网络数据传输的开销,所以更适合写多的场景。由于无法提前确定数据在哪个Region上,所以在读数据的时候,需要检查每个Region上的数据从而带来一些性能损耗。
注意:对于本地索引,查询中无论是否指定hint或者是查询的列是否都在索引表中,都会使用索引表。
# 创建本地索引 CREATE LOCAL INDEX DIANXIN_LOCAL_IDEX ON DIANXIN(grid_id); # 索引生效 select grid_id from dianxin where grid_id='117285031820040'; # 索引生效 select * from dianxin where grid_id='117285031820040';
2.3、覆盖索引
覆盖索引是把原数据存储在索引数据表中,这样在查询时不需要再去HBase的原表获取数据就,直接返回查询结果。(相当于全局索引+数据)
注意:查询是 select 的列和 where 的列都需要在索引中出现。
# 创建覆盖索引 CREATE INDEX DIANXIN_INDEX_COVER ON DIANXIN ( x,y ) INCLUDE ( county ); # 查询所有列 (索引未生效) select * from dianxin where x=117.288 and y =31.822; select * from DIANXIN where x=117.288 and y =31.822; # 强制使用索引 (索引生效) select /*+ INDEX(DIANXIN DIANXIN_INDEX_COVER) */ * from dianxin where x=117.288 and y =31.822; # 查询索引中的列 (索引生效) mdn是DIANXIN表的RowKey中的一部分 select x,y,county from dianxin where x=117.288 and y =31.822; select mdn,x,y,county from dianxin where x=117.288 and y =31.822; # 查询条件必须放在索引中 select 中的列可以放在INCLUDE (将数据保存在索引中) select /*+ INDEX(DIANXIN DIANXIN_INDEX_COVER) */ x,y,count(*) from dianxin group by x,y;
4、Phoenix JDBC
# 导入依赖 <dependency> <groupId>org.apache.phoenix</groupId> <artifactId>phoenix-core</artifactId> <version>4.15.0-HBase-1.4</version> </dependency>
Connection conn = DriverManager.getConnection("jdbc:phoenix:master,node1,node2:2181"); PreparedStatement ps = conn.prepareStatement("select /*+ INDEX(DIANXIN DIANXIN_INDEX) */ * from DIANXIN where end_date=?"); ps.setString(1, "20180503212649"); ResultSet rs = ps.executeQuery(); while (rs.next()) { String mdn = rs.getString("mdn"); String start_date = rs.getString("start_date"); String end_date = rs.getString("end_date"); String x = rs.getString("x"); String y = rs.getString("y"); String county = rs.getString("county"); System.out.println(mdn + "\t" + start_date + "\t" + end_date + "\t" + x + "\t" + y + "\t" + county); } ps.close(); conn.close();