Phoenix操作HBase
一、Phoenix简介
Hbase适合存储大量的对关系运算要求低的NOSQL数据,受Hbase 设计上的限制不能直接使用原生的PAI执行在关系数据库中普遍使用的条件判断和聚合等操作。
Apache Phoenix 是 HBase 的 SQL 驱动。Phoenix 使得 HBase 支持通过 JDBC 的方式进行访问,并将你的 SQL 查询转成 HBase 的扫描和相应的动作。Phoenix 基于Hbase给面向业务的开发人员提供了以标准SQL的方式对Hbase进行查询操作,并支持标准SQL中大部分特性:条件运算,分组,分页,等高级查询语法。
二、安装部署
- Apache Phoenix官网下载安装包:apache-phoenix-4.15.0-HBase-1.3-bin.tar.gz
- 解压直 "/opt/"目录下
- 复制Phoenix目录下phoenix-4.15.0-HBase-1.3-server.jar到HBase安装目录lib目录中
- 复制HBase配置文件hbase.site.xml文件到Phoenix安装目录lib下
- 重启HBase
三、Phoenix实操
访问Phoenix
一般使用以下三种方式访问Phoenix:
- Linux Shell
- JDBC API
- 使用Python编写的命令行工具(sqlline, sqlline-thin和psql等)
- SQuirrel
Linux Shell
- 进入
bin/sqlline.py localhost:2181
- 退出
0: jdbc:phoenix:localhost:2181> !quit
或
0: jdbc:phoenix:localhost:2181> !exit
- 创建表格
注意:
- Hbase中表名和列族名是大小写敏感的,如果使用小写的表名那么必须加上双引号。
- 从v4.8.0之前,所有的表(有模式的和没模式的)都在默认的命名空间中创建。v4.8.0之后用户可以将其模式映射到命名空间,这样用模式创建的任何表都将在Hbase的相应命名空间中创建。
- 3在phoenix中与hbase的命名空间相对应的是schema概念,默认是没有开启的,需要在hbase的hbase-site.xml中增加以下配置项:
<property>
<name>phoenix.schema.isNamespaceMappingEnabled</name>
<value>true</value>
</property>
<property>
<name>phoenix.schema.mapSystemTablesToNamespace</name>
<value>true</value>
</property>
- Phoenix表的管理维护通过Phoenix创建的表,必须指定primary key(对应Hbase的rowkey),列最好指定列族名称,列类型最好指定为varchar。
- 在Phoenix中字段的类型所保存的byte数组和HBase中用Bytes工具了转换的byte数组可能会对不上,比如Bytes.toBytes(1)的结果和Phoenix中定义为INTEGER类的byte[]就不一样。因为Phoenix中的INTEGER是带符号的,要用无符号的话需要定义UNSIGNED_INT类型,UNSIGENT_INT类型的byte[]值和Bytes.toBytes(1)是一样的,但是UNSIGENT_INT无法存负数。
所以如果直接定义INTEGER类型的话是不能直接简单地用Bytes.toBytes来转换得到的,由于这种情况的存在就不能直接操作HBase然后直接在Phoenix查看,反之亦然。
一种情况:HBase中无表数据
这种情况由Phoenix来映射表,在Phoenix中创建的表删除时HBase中的表也会删除。
0: jdbc:phoenix:localhost:2181> create table "user_info"("id" varchar primary key,"base_info"."name" varchar(10),"base_info"."age" varchar,"base_info"."gender" char(1),"extra_info"."province" varchar(20), "extra_info"."city" varchar(30)) column_encoded_bytes=0;
一种情况:HBase已存在表
如果只需要查询数据,不需要对数据进行操作
这时需要创建视图,因为视图删除时不会影响到源数据
建映射视图和映射表需要遵循以下几点:
- Phoneix的表名必须和 hbase的 库名.表名一致
- Phoneix的表的主键的列名一般对应rowkey列,名称可以随意,但是类型得匹配!
- Phoneix的表的普通的列名,必须和hbase的列族.列名一致!
- 表映射的结尾,必须添加column_encoded_bytes=0,不然无法从hbase中查询到数据的!
create view if not exists "user_info"(
"id" varchar primary key,
"base_info"."name" varchar,
"base_info"."age" varchar
)column_encoded_bytes=0;
如果需要对数据进行操作,只能用映射表来操作
create table if not exists "user_info"(
"id" varchar primary key,
"base_info"."name" varchar,
"base_info"."age" varchar
)column_encoded_bytes=0;
- 删除表格
删除表格的时候遇到一个BUG,明明表是存在到,删除时提示:Error: ERROR 1012 (42M03): Table undefined. tableName=shanchuan.user_info (state=42M03,code=1012),重新创建同名表,又会提示表存在:Error: ERROR 1013 (42M04): Table already exists. tableName=shanchuan.user_info (state=42M04,code=1013)。再次删除才可以将表删除
需要在HBase端先删除表格,然后在Phoenix中查询下该表出现提示不存在到时候,再删除表就不报错:
0: jdbc:phoenix:localhost:2181> drop table "user_info"
- 查看所有表
0: jdbc:phoenix:localhost:2181> !tables
+------------+--------------+-------------+---------------+----------+------------+----------------------------+-----------------+--------------+----+
| TABLE_CAT | TABLE_SCHEM | TABLE_NAME | TABLE_TYPE | REMARKS | TYPE_NAME | SELF_REFERENCING_COL_NAME | REF_GENERATION | INDEX_STATE | IM |
+------------+--------------+-------------+---------------+----------+------------+----------------------------+-----------------+--------------+----+
| | SYSTEM | CATALOG | SYSTEM TABLE | | | | | | fa |
| | SYSTEM | CHILD_LINK | SYSTEM TABLE | | | | | | fa |
| | SYSTEM | FUNCTION | SYSTEM TABLE | | | | | | fa |
| | SYSTEM | LOG | SYSTEM TABLE | | | | | | tr |
| | SYSTEM | MUTEX | SYSTEM TABLE | | | | | | tr |
| | SYSTEM | SEQUENCE | SYSTEM TABLE | | | | | | fa |
| | SYSTEM | STATS | SYSTEM TABLE | | | | | | fa |
| | SYSTEM | TASK | SYSTEM TABLE | | | | | | fa |
| | | user_info | TABLE | | | | | | fa |
+------------+--------------+-------------+---------------+----------+------------+----------------------------+-----------------+--------------+----+
- 查看表结构和列出metadata信息
0: jdbc:phoenix:localhost:2181> !desc "user_info"
- 修改表结构,添加列
0: jdbc:phoenix:localhost:2181> alter table "user_info" add "base_info.group" varchar(20);
8)插入数据
注意values的"("之间不能有空格
0: jdbc:phoenix:localhost:2181> upsert into "user_info" values('1','ZhangSan','25','M','JiangSu','SuZhou','Han');
- 查询数据
Phoenix中查询数据
0: jdbc:phoenix:localhost:2181> select * from "user_info";
+-----+-----------+------+---------+-----------+---------+--------+
| id | name | age | gender | province | city | group |
+-----+-----------+------+---------+-----------+---------+--------+
| 1 | ZhangSan | 25 | M | JiangSu | SuZhou | Han |
+-----+-----------+------+---------+-----------+---------+--------+
HBase中查询数据
hbase(main):019:0> scan 'user_info'
ROW COLUMN+CELL
1 column=base_info:_0, timestamp=1611578659627, value=x
1 column=base_info:age, timestamp=1611578659627, value=25
1 column=base_info:gender, timestamp=1611578659627, value=M
1 column=base_info:group, timestamp=1611578659627, value=Han
1 column=base_info:name, timestamp=1611578659627, value=ZhangSan
1 column=extra_info:city, timestamp=1611578659627, value=SuZhou
1 column=extra_info:province, timestamp=1611578659627, value=JiangSu
1 row(s) in 0.1390 seconds
- 修改数据
必须要带上主键id,值只能用单引号括起来
0: jdbc:phoenix:localhost:2181> upsert into "user_info"("id","base_info"."age") values('1','30');
- 删除记录
字符串值只能用单引号括起来
0: jdbc:phoenix:localhost:2181> delete from "user_info" where "base_info"."name"='ZhangSan';
- 创建索引
0: jdbc:phoenix:localhost:2181> create index "test_index" on "user_info"("base_info"."name");
Error: ERROR 1029 (42Y88): Mutable secondary indexes must have the hbase.regionserver.wal.codec property set to org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec in the hbase-sites.xml of every region server. tableName=test_index (state=42Y88,code=1029)
配置HBase配置文件conf/hbase-site.xml,添加:
<property>
<name>hbase.regionserver.wal.codec</name>
<value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>
</property>
- 删除索引
0: jdbc:phoenix:localhost:2181> drop index "test_index" on "user_info";
命令行工具psql使用示例
Examples:
psql my_ddl.sql
psql localhost my_ddl.sql
psql localhost my_ddl.sql my_table.csv
psql -t MY_TABLE my_cluster:1825 my_table2012-Q3.csv
psql -t MY_TABLE -h COL1,COL2,COL3 my_cluster:1825 my_table2012-Q3.csv
psql -t MY_TABLE -h COL1,COL2,COL3 -d : my_cluster:1825 my_table2012-Q3.csv
- 准备sql脚本
创建一个建表到sql脚本:order_info.sql
create table if not exists order_info(
"order_id" varchar primary key,
"goods_info"."item" varchar,
"goods_info"."name" varchar,
"goods_info"."price" varchar,
"pay_info"."type" varchar,
"pay_info"."amount" varchar
);
创建查询sql脚本:queries.sql
select * from order_info;
- 准备数据:order_info.csv
5,休闲零食,乐事薯片,9.9,支付宝,9.9
8,饮料,可口可乐,5,微信,5
6,日用品,黑人牙膏,18.9,支付宝,17.3
2,学习用品,圆珠笔,2.9,支付宝,2.9
1,时令水果,车厘子,269.9,微信,269.9
3,玩具,泡泡机,15.9,支付宝,15.9
- 执行脚本
bin/psql.py localhost /home/huangwei/DataGripProjects/phoenix/order_info.sql /home/huangwei/input/order_info.csv
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/opt/phoenix-4.15-HBase-1.3/phoenix-4.15.0-HBase-1.3-client.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/opt/hadoop-2.7.3/share/hadoop/common/lib/slf4j-log4j12-1.7.10.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
21/01/25 22:23:07 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
no rows upserted
Time: 2.57 sec(s)
csv columns from database.
CSV Upsert complete. 6 rows upserted
Time: 0.127 sec(s)
- 查询数据
bin/psql.py localhost /home/huangwei/DataGripProjects/phoenix/queries.sql
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/opt/phoenix-4.15-HBase-1.3/phoenix-4.15.0-HBase-1.3-client.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/opt/hadoop-2.7.3/share/hadoop/common/lib/slf4j-log4j12-1.7.10.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
21/01/25 22:25:39 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
order_id item name price type amount
---------------------------------------- ---------------------------------------- ---------------------------------------- ---------------------------------------- ---------------------------------------- ----------------------------------------
1 时令水果 车厘子 269.9 微信 269.9
2 学习用品 圆珠笔 2.9 支付宝 2.9
3 玩具 泡泡机 15.9 支付宝 15.9
5 休闲零食 乐事薯片 9.9 支付宝 9.9
6 日用品 黑人牙膏 18.9 支付宝 17.3
8 饮料 可口可乐 5 微信 5
Time: 0.053 sec(s)
但在HBase中查询没有数据。
JDBC API
添加maven依赖
<dependency>
<groupId>org.apache.phoenix</groupId>
<artifactId>phoenix-core</artifactId>
<version>4.15.0-HBase-1.3</version>
</dependency>
代码
package com.hw.hbase.phoenix;
import java.sql.*;
import java.util.function.Predicate;
/**
* @Author: H.w
* @Date: 2021/1/26 下午8:31
* @Description:TODO Phoenix API
*/
public class HBaseOperators {
public static void main(String[] args) throws SQLException {
Statement statement = null;
ResultSet result = null;
Connection connection = DriverManager.getConnection("jdbc:phoenix:localhost");
statement = connection.createStatement();
// statement.executeUpdate("create table phoenix_test(id varchar not null primary key,columnFamily1.clo varchar)");
statement.executeUpdate("upsert into phoenix_test values('1','Phoenix')");
statement.executeUpdate("upsert into phoenix_test values('2','HBase')");
connection.commit();
//查询数据
PreparedStatement pstste = connection.prepareStatement("select * from phoenix_test");
// result = statement.executeQuery();
// while (result.next()) {
//
// }
statement.close();
connection.close();
}
}
注意:本地测试,需要在resources目录下复制一份hbase-site.xml
SQuirrel
数据类型
目前Phoenix支持24种简单数据类型和1个一维Array的复杂类型。以下是对支持数据类型的说明
DML语法
- select
- upsert values
- upsert select
- delete
加盐表
- 什么是加盐?
在密码学中,加盐是指在散列之前将散列内容(例如:密码)的任意固定位置插入特定的字符串。这个在散列中加入字符串的方式称为“加盐”。其作用是让加盐后的散列结果和没有加盐的结果不相同,在不同的应用情景中,这个处理可以增加额外的安全性。而Phoenix中加盐是指对pk对应的byte数组插入特定的byte数据。 - 加盐能解决什么问题?
盐能解决HBASE读写热点问题,例如:单调递增rowkey数据的持续写入,使得负载集中在某一个RegionServer上引起的热点问题。 - 如何对表加盐?
在创建表的时候指定属性值:SALT_BUCKETS,其值表示所分buckets(region)数量, 范围是1~256。
CREATE TABLE mytable (my_key VARCHAR PRIMARY KEY, col VARCHAR) SALT_BUCKETS = 8;
- 加盐的原理是什么?
加盐的过程就是在原来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;
}
- 一个表加多少盐合适?
- 当可用block cache的大小小于表数据大小时,较优的slated bucket是和region server数量相同,这样可以得到更好的读写性能。
- 当表的数量很大时,基本上会忽略blcok cache的优化收益,大部分数据仍然需要走磁盘IO。比如对于10个region server集群的大表,可以考虑设计64~128个slat buckets。
- 加盐是需要注意什么?
- 创建加盐表时不能再指定split key。
- 加盐属性不等同于split key, 一个bucket可以对应多个region。
- 太大的slated buckets会减小range查询的灵活性,甚至降低查询性能。
二级索引
目前HBASE只有基于字典序的主键索引,对于非主键过滤条件的查询都会变成扫全表操作,为了解决这个问题Phoenix引入了二级索引功能。然而此二级索引又有别于传统关系型数据库的二级索引,本文将详细描述了Phoenix中二级索引功能、用法和原理。