HBase系列(八)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

  1. 进入
 bin/sqlline.py localhost:2181
  1. 退出
0: jdbc:phoenix:localhost:2181> !quit
或
0: jdbc:phoenix:localhost:2181> !exit
  1. 创建表格
    注意:
  • 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;
  1. 删除表格
    删除表格的时候遇到一个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"
  1. 查看所有表
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 |
+------------+--------------+-------------+---------------+----------+------------+----------------------------+-----------------+--------------+----+
  1. 查看表结构和列出metadata信息
0: jdbc:phoenix:localhost:2181> !desc "user_info"
  1. 修改表结构,添加列
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');
  1. 查询数据
    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
  1. 修改数据
    必须要带上主键id,值只能用单引号括起来
0: jdbc:phoenix:localhost:2181> upsert into "user_info"("id","base_info"."age") values('1','30');
  1. 删除记录
    字符串值只能用单引号括起来
0: jdbc:phoenix:localhost:2181> delete from "user_info" where "base_info"."name"='ZhangSan';
  1. 创建索引
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>
  1. 删除索引
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
  1. 准备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;
  1. 准备数据: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
  1. 执行脚本
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)
  1. 查询数据
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语法

  1. select
  2. upsert values
  3. upsert select
  4. delete

加盐表

  1. 什么是加盐?
    在密码学中,加盐是指在散列之前将散列内容(例如:密码)的任意固定位置插入特定的字符串。这个在散列中加入字符串的方式称为“加盐”。其作用是让加盐后的散列结果和没有加盐的结果不相同,在不同的应用情景中,这个处理可以增加额外的安全性。而Phoenix中加盐是指对pk对应的byte数组插入特定的byte数据。
  2. 加盐能解决什么问题?
    盐能解决HBASE读写热点问题,例如:单调递增rowkey数据的持续写入,使得负载集中在某一个RegionServer上引起的热点问题。
  3. 如何对表加盐?
    在创建表的时候指定属性值:SALT_BUCKETS,其值表示所分buckets(region)数量, 范围是1~256。
CREATE TABLE mytable (my_key VARCHAR PRIMARY KEY, col VARCHAR) SALT_BUCKETS = 8;
  1. 加盐的原理是什么?
    加盐的过程就是在原来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 &lt; offset + length; i++) {
        result = 31 * result + a[i];
    }
    return result;
}
  1. 一个表加多少盐合适?
  • 当可用block cache的大小小于表数据大小时,较优的slated bucket是和region server数量相同,这样可以得到更好的读写性能。
  • 当表的数量很大时,基本上会忽略blcok cache的优化收益,大部分数据仍然需要走磁盘IO。比如对于10个region server集群的大表,可以考虑设计64~128个slat buckets。
  1. 加盐是需要注意什么?
  • 创建加盐表时不能再指定split key。
  • 加盐属性不等同于split key, 一个bucket可以对应多个region。
  • 太大的slated buckets会减小range查询的灵活性,甚至降低查询性能。

二级索引

目前HBASE只有基于字典序的主键索引,对于非主键过滤条件的查询都会变成扫全表操作,为了解决这个问题Phoenix引入了二级索引功能。然而此二级索引又有别于传统关系型数据库的二级索引,本文将详细描述了Phoenix中二级索引功能、用法和原理。


版权声明:本文为Wei_HHH原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。