Phoenix介绍及使用
一、Phoenix
1、常用命令
注意事项
1.创建表:
CREATE TABLE IF NOT EXISTS students_p1 (
id VARCHAR NOT NULL PRIMARY KEY,
name VARCHAR,
age BIGINT,
gender VARCHAR ,
clazz VARCHAR
);
2.显示所有表
!table
3.插入数据
upsert into "STUDENTS_P1" values('1500101001','小星',18,'男','理科一班');
upsert into "STUDENTS_P1" values('1500101002','小赵',22,'男','理科三班');
upsert into "STUDENTS_P1" values('1500101003','小月',21,'女','理科二班班');
upsert into "STUDENTS_P1" values('1500101004','小阳',18,'男','理科一班');
4.查询数据,支持大部分sql语法
select * from students_p1 ;
select * from students_p1 where age=18;
select gender ,count(*) from students_p1 group by gender;
select * from students_p1 order by gender;
5.删除数据
delete from students_p1 where id='1500101001';
6.删除表
drop table STUDENTS_P1;
7.退出命令行
!quit
更多语法参照官网
https://phoenix.apache.org/language/index.html#upsert_select
2、Phoenix表映射
默认情况下,直接在hbase中创建的表,通过phoenix是查不到的
如果需要在phoenix中操作直接在hbase中创建的表,则需要在phoenix中进行表的映射。映射方式有两种:试图映射和表映射
2.1 视图映射
phoenix创建的视图是只读的,所以只能用来做查询,无法通过视图对源数据进行修改等操作。
1.创建test表
create 'test','name','company'
2.插入数据
put 'test','001','name:firstname','zhangsan1'
put 'test','001','name:lastname','zhangsan2'
put 'test','001','company:name','星空'
put 'test','001','company:address','合肥'
3.在phoenix创建视图, primary key 对应到hbase中的rowkey
create view "test"(
empid varchar primary key,
"name"."firstname" varchar,
"name"."lastname" varchar,
"company"."name" varchar,
"company"."address" varchar
);
CREATE view "students" (
id VARCHAR NOT NULL PRIMARY KEY,
"info"."name" VARCHAR,
"info"."age" VARCHAR,
"info"."gender" VARCHAR,
"info"."clazz" VARCHAR
) column_encoded_bytes=0;
!在hbase中可以添加view表中的数据,但是phoenix中只能读表数据
4.在phoenix中查询数据,表名通过双引号引起来(除主键外都需要使用双引号)
select empid,"name" from "test";
5.删除视图
drop view "test";
2.2 表映射
使用Phoenix创建对HBase的表映射,有两类:
1.当HBase中已经存在表时,可以一类似创建视图的方式关联表,只需要将create view改成create table即可。
2.当HBase中不存在表时,可以直接使用create table指令创建需要的表,并且在创建指令中可以根据需要对HBase表结构进行显示的说明。
1.Hbase中已经存在test表
create table "test" (
empid varchar primary key,
"name"."firstname" varchar,
"name"."lastname"varchar,
"company"."name" varchar,
"company"."address" varchar
)column_encoded_bytes=0;
#表映射可进行增删改查不像view
#表映射只能对表进行查询操作,而且删除时并不会删除HBase中的表
#表映射,可以对表进行增删改查,删除表时HBase中的表也会被删除
upsert into "test" values('1001','xiaohu','张三','虚空','合肥');
#创建学生表映射
CREATE table "students" (
id VARCHAR NOT NULL PRIMARY KEY,
"info"."name" VARCHAR,
"info"."age" VARCHAR,
"info"."gender" VARCHAR,
"info"."clazz" VARCHAR
) column_encoded_bytes=0;
#创建分数表映射
CREATE table "scores" (
id VARCHAR NOT NULL PRIMARY KEY,
"info"."subject_score" VARCHAR
) column_encoded_bytes=0;
#删除表映射
DELETE from SYSTEM.CATALOG where TABLE_NAME ='scores';
#切分regexp_split,下标从1开始,
select regexp_split(id,'-')[1] as id,regexp_split(id,'-')[2] as subject_id,"subject_score" from "scores";
#求每个学生的总分,使用to_number将string转化int,使用to_char将int转为字符串
select t1.student_id as student_id,sum(to_number(t1.score)) as sum_score from (select regexp_split(id,'-')[1] as student_id,regexp_split(id,'-')[2] as subject_id,"subject_score" as score from "scores") t1 group by t1.student_id;
#注意点
通过这个例子遇到的注意点:
1、切分字符串的函数不是split,而是regexp_split
2、Phoenix中,数组的索引是从1开始的
3、给字段起别名之后的嵌套查询,就不需要再加双引号了,主键本身就可以不用加
4、sum函数中的数据类型必须是数值类型,如果是10的整数倍,会以科学计数法进行标识 580->5.8E+2(5.8*10^2)
5、to_number()转数值 to_char()转字符串
二、bulkLoad实现批量导入
1.优缺点
优点:
- 如果我们一次性入库hbase巨量数据,处理速度慢不说,还特别占用Region资源, 一个比较高效便捷的方法就是使用 “Bulk Loading”方法,即HBase提供的HFileOutputFormat类。
- 它是利用hbase的数据信息按照特定格式存储在hdfs内这一原理,直接生成这种hdfs内存储的数据格式文件,然后上传至合适位置,即完成巨量数据快速入库的办法。配合mapreduce完成,高效便捷,而且不占用region资源,增添负载。
限制:
- 仅适合初次数据导入,即表内数据为空,或者每次入库表内都无数据的情况。
- HBase集群与Hadoop集群为同一集群,即HBase所基于的HDFS为生成HFile的MR的集群
2.代码编写
提前在Hbase中创建好表
生成Hfile基本流程:
- 设置Mapper的输出KV类型:
K: ImmutableBytesWritable(代表行键)
V: KeyValue (代表cell)
2. 开发Mapper
读取你的原始数据,按你的需求做处理
输出rowkey作为K,输出一些KeyValue(Put)作为V
3. 配置job参数
a. Zookeeper的连接地址
b. 配置输出的OutputFormat为HFileOutputFormat2,并为其设置参数
4. 提交job
导入HFile到RegionServer的流程
构建一个表描述对象
构建一个region定位工具
然后用LoadIncrementalHFiles来doBulkload操作
3.说明
说明
最终输出结果,无论是map还是reduce,输出部分key和value的类型必须是: < ImmutableBytesWritable, KeyValue>或者< ImmutableBytesWritable, Put>。
最终输出部分,Value类型是KeyValue 或Put,对应的Sorter分别是KeyValueSortReducer或PutSortReducer。
MR例子中HFileOutputFormat2.configureIncrementalLoad(job, dianxin_bulk, regionLocator);自动对job进行配置。SimpleTotalOrderPartitioner是需要先对key进行整体排序,然后划分到每个reduce中,保证每一个reducer中的的key最小最大值区间范围,是不会有交集的。因为入库到HBase的时候,作为一个整体的Region,key是绝对有序的。
MR例子中最后生成HFile存储在HDFS上,输出路径下的子目录是各个列族。如果对HFile进行入库HBase,相当于move HFile到HBase的Region中,HFile子目录的列族内容没有了,但不能直接使用mv命令移动,因为直接移动不能更新HBase的元数据。
HFile入库到HBase通过HBase中 LoadIncrementalHFiles的doBulkLoad方法,对生成的HFile文件入库
三、HBase中rowkey的设计(重点!!)
1.HBase的RowKey设计
HBase是三维有序存储的,通过rowkey(行键),column key(column family和qualifier)和TimeStamp(时间戳)这个三个维度可以对HBase中的数据进行快速定位。
HBase中rowkey可以唯一标识一行记录,在HBase查询的时候,有两种方式:
通过get方式,指定rowkey获取唯一一条记录
通过scan方式,设置startRow和stopRow参数进行范围匹配
全表扫描,即直接扫描整张表中所有行记录
2.rowkey长度原则
rowkey是一个二进制码流,可以是任意字符串,最大长度 64kb ,实际应用中一般为10-100bytes,以 byte[] 形式保存,一般设计成定长。
建议越短越好,不要超过16个字节,原因如下:
数据的持久化文件HFile中是按照KeyValue存储的,如果rowkey过长,比如超过100字节,1000w行数据,光rowkey就要占用100*1000w=10亿个字节,将近1G数据,这样会极大影响HFile的存储效率;
MemStore将缓存部分数据到内存,如果rowkey字段过长,内存的有效利用率就会降低,系统不能缓存更多的数据,这样会降低检索效率。
目前操作系统都是64位系统,内存8字节对齐,控制在16个字节,8字节的整数倍利用了操作系统的最佳特性。
3.rowkey散列原则
如果rowkey按照时间戳的方式递增,不要将时间放在二进制码的前面,建议将rowkey的高位作为散列字段,由程序随机生成,低位放时间字段,这样将提高数据均衡分布在每个RegionServer,以实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息,所有的数据都会集中在一个RegionServer上,这样在数据检索的时候负载会集中在个别的RegionServer上,造成热点问题,会降低查询效率。
4.rowkey唯一原则
必须在设计上保证其唯一性,rowkey是按照字典顺序排序存储的,因此,设计rowkey的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块。
5.热点问题
HBase中的行是按照rowkey的字典顺序排序的,这种设计优化了scan操作,可以将相关的行以及会被一起读取的行存取在临近位置,便于scan。然而糟糕的rowkey设计是热点的源头。 热点发生在大量的client直接访问集群的一个或极少数个节点(访问可能是读,写或者其他操作)。大量访问会使热点region所在的单个机器超出自身承受能力,引起性能下降甚至region不可用,这也会影响同一个RegionServer上的其他region,由于主机无法服务其他region的请求。 设计良好的数据访问模式以使集群被充分,均衡的利用。
为了避免写热点,设计rowkey使得不同行在同一个region,但是在更多数据情况下,数据应该被写入集群的多个region,而不是一个。
下面是一些常见的避免热点的方法以及它们的优缺点:
加盐(随机的)
这里所说的加盐不是密码学中的加盐,而是在rowkey的前面增加随机数,具体就是给rowkey分配一个随机前缀以使得它和之前的rowkey的开头不同。分配的前缀种类数量应该和你想使用数据分散到不同的region的数量一致。加盐之后的rowkey就会根据随机生成的前缀分散到各个region上,以避免热点。
哈希(固定的)
哈希会使同一行永远用一个前缀加盐。哈希也可以使负载分散到整个集群,但是读却是可以预测的。使用确定的哈希可以让客户端重构完整的rowkey,可以使用get操作准确获取某一个行数据
反转
第三种防止热点的方法时反转固定长度或者数字格式的rowkey。这样可以使得rowkey中经常改变的部分(最没有意义的部分)放在前面。这样可以有效的随机rowkey,但是牺牲了rowkey的有序性。
反转rowkey的例子以手机号为rowkey,可以将手机号反转后的字符串作为rowkey,这样的就避免了以手机号那样比较固定开头导致热点问题
时间戳反转
一个常见的数据处理问题是快速获取数据的最近版本,使用反转的时间戳作为rowkey的一部分对这个问题十分有用,可以用 Long.Max_Value - timestamp 追加到key的末尾,例如 [key]reverse_timestamp , [key] 的最新值可以通过scan [key]获得[key]的第一条记录,因为HBase中rowkey是有序的,第一条记录是最后录入的数据。
比如需要保存一个用户的操作记录,按照操作时间倒序排序,在设计rowkey的时候,可以这样设计
[userId反转]Long.Max_Value - timestamp,在查询用户的所有操作记录数据的时候,直接指定反转后的userId,startRow是[userId反转]000000000000,stopRow是[userId反转]Long.Max_Value - timestamp
如果需要查询某段时间的操作记录,startRow是[user反转]Long.Max_Value - 起始时间,stopRow是[userId反转]Long.Max_Value - 结束时间
其他一些建议
尽量减少行和列的大小在HBase中,value永远和它的key一起传输的。当具体的值在系统间传输时,它的rowkey,列名,时间戳也会一起传输。如果你的rowkey和列名很大,甚至可以和具体的值相比较,那么你将会遇到一些有趣的问题。HBase storefiles中的索引(有助于随机访问)最终占据了HBase分配的大量内存,因为具体的值和它的key很大。可以增加block大小使得storefiles索引再更大的时间间隔增加,或者修改表的模式以减小rowkey和列名的大小。压缩也有助于更大的索引。
列族尽可能越短越好,最好是一个字符
冗长的属性名虽然可读性好,但是更短的属性名存储在HBase中会更好
四、二级索引
1.二级索引介绍
二级索引的本质就是建立各列值与行键之间的映射关系
Hbase的局限性:
HBase本身只提供基于行键和全表扫描的查询,而行键索引单一,对于多维度的查询困难。
2.常见的二级索引
HBase的一级索引就是rowkey,我们只能通过rowkey进行检索。如果我们相对hbase里面列族的列列进行一些组合查询,就需要采用HBase的二级索引方案来进行多条件的查询。
1. MapReduce方案
2. ITHBASE(Indexed-Transanctional HBase)方案
3. IHBASE(Index HBase)方案
4. Hbase Coprocessor(协处理器)方案
5. Solr+hbase方案 redis+hbase 方案6. CCIndex(complementalclustering index)方案
3.二级索引种类
1、创建单列索引
2、同时创建多个单列索引
3、创建联合索引(最多同时支持3个列)
4、只根据rowkey创建索引
2.1 单表建立二级索引
1.首先disable ‘表名’
2.然后修改表
alter 'LogTable',METHOD=>'table_att','coprocessor'=>'hdfs:///写好的Hbase协处理器(coprocessor)的jar包名|类的绝对路径名|1001'
3. enable '表名'
二级索引的本质就是建立各列值与行键之间的映射关系
如上图1,当要对F:C1这列建立索引时,只需要建立F:C1各列值到其对应行键的映射关系,如C11->RK1等,这样就完成了对F:C1列值的二级索引的构建,当要查询符合F:C1=C11对应的F:C2的列值时(即根据C1=C11来查询C2的值,图1青色部分)
其查询步骤如下:
根据C1=C11到索引数据中查找其对应的RK,查询得到其对应的RK=RK1
得到RK1后就自然能根据RK1来查询C2的值了 这是构建二级索引大概思路,其他组合查询的联合索引的建立也类似。
4. Mapreduce的方式创建二级索引
使用整合MapReduce的方式创建hbase索引。主要的流程如下:
1.1扫描输入表,使用hbase继承类TableMapper
1.2获取rowkey和指定字段名称和字段值
1.3创建Put实例, value=” “, rowkey=班级,column=学号
1.4使用IdentityTableReducer将数据
步骤:
1.在hbase创建索引表Student_index
2.编写MapReduce代码(TableMapper和TableReduce)


3.打成jar包上传到hadoop中运行

4.查询结果(编写代码或hbase上直接查询)

5. Phoenix二级索引
5.1 开启索引支持
修改hbase中的hbase-site.xml 和phoenix中的hbase-site.xml文件
5.2 创建索引
5.2.1 全局索引
全局索引适合读多写少的场景。如果使用全局索引,读数据基本不损耗性能,所有的性能损耗都来源于写数据。数据表的添加、删除和修改都会更新相关的索引表(数据删除了,索引表中的数据也会删除;数据增加了,索引表的数据也会增加)
注意: 对于全局索引在默认情况下,在查询语句中检索的列如果不在索引表中,Phoenix不会使用索引表将,除非使用hint。
例:
# 创建students_psql.sql
CREATE TABLE IF NOT EXISTS students_psql (
id VARCHAR ,
name VARCHAR ,
age BIGINT ,
gender VARCHAR,
clazz VARCHAR ,
CONSTRAINT PK PRIMARY KEY (id)
) column_encoded_bytes=0;

# 上传数据students_psql.csv
#数据文件名,必须和脚本名/表名一致
# 导入数据
psql.py master,node1,node2 students_psql /usr/local/soft/bigdata/students_psql.csv

查询原表(换数据了),当查询时,where蓝色部分(行键)比where普通部分要快很多

#创建索引表DIANXIN_INDEX索引名,DIANXIN原表,end_date原表中的一列
CREATE INDEX DIANXIN_INDEX ON DIANXIN ( end_date );
查询索引表,end_data作为行键,原先的行键作为其后面的列

根据end_data查询数据时,现根据索引表中的end_data行键找到原表的行键,在通过原表行键查询。
# 查询数据 ( 索引未生效)
select * from DIANXIN where end_date = '20180503154014';

# 强制使用索引 (索引生效) hint 语法糖(使用特殊格式实习某些功能)
#DIANXIN原表 DIANXIN_INDEX索引表
select /*+ INDEX(DIANXIN DIANXIN_INDEX) */ * from DIANXIN where end_date = '20180503154014';
#只要索引表中有的数据都可以用where查
select /*+ INDEX(DIANXIN DIANXIN_INDEX) */ * from DIANXIN where end_date = '20180503154014' and start_date = '20180503154614';

# 取索引列,(索引生效)
select end_date from DIANXIN where end_date = '20180503154014';
# 创建多列索引
#一张表可以拥有多个索引表,end_date和COUNTY一起作为行键,查询时必须都包含
CREATE INDEX DIANXIN_INDEX1 ON DIANXIN ( end_date,COUNTY );
# 多条件查询 (索引生效)
#查询时会先从原表的索引表中查询,如果存在同时包含两个查询条件的索引表会使用该索引表
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';
# 单条件 (索引未生效),不存在拥有单一count作为行键的索引表
select end_date from DIANXIN where COUNTY = '8340103';
# 单条件 (索引生效) 索引表一中end_data作为行键
select COUNTY from DIANXIN where end_date = '20180503154014';
# 删除索引
drop index DIANXIN_INDEX on DIANXIN;
5.2.2 覆盖索引
# 创建覆盖索引
CREATE INDEX DIANXIN_INDEX_COVER ON DIANXIN ( x,y ) INCLUDE ( county );
# 查询所有列 (索引未生效)
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;

country是普通列,覆盖过来,当需要找country时,不需要再从原表中查找
5.2.3 本地索引
本地索引适合写多读少的场景,或者存储空间有限的场景。和全局索引一样,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';

浙公网安备 33010602011771号