Phoenix 5.1.3
星期一, 3月 17, 2025 | 20分钟阅读

关于Phoenix的学习。
简介
官网:Phoenix
Apache Phoenix 使用 Apache HBase 作为后台存储,是一个大规模并行,关系型数据库引擎,支持 OLTP for Hadoop。Phoenix 提供了一个 JDBC 驱动程序,该驱动程序隐藏了 NoSQL 存储的复杂性,使用户可以创建,删除和更改 SQL 表、视图、索引等。通过 SQL 来查询数据,批量插入和删除。Phoenix 将查询和其他语句编译到本机的 NoSQL 存储区 API 中,而不是使用 MapReduce 在 NoSQL 存储区之上构建低延迟应用程序。
Apache Phoenix 是一个开放源码的 HBase SQL 皮肤。能让我们使用标准的 JDBC API 而不是 HBase 客户端 API 来创建表,插入数据和对 HBase 数据进行查询。Phoenix 完全使用 Java 编写,作为 HBase 内嵌的 JDBC 驱动。Phoenix 查询引擎会将 SQL 查询转换为一个或多个 HBase 扫描,并编排执行以生成标准的 JDBC 结果集。
Apache Phoenix 与 Spark、Hive、Pig、Flume、Map Reduce 等 Hadoop 产品完全集成。
架构
重客户端
Phoenix 是 HBase 之上的 SQL 层,它为 HBase 赋予了 NewSQL 的特性,支持了大多数的标准 SQL 特性,并提供了JDBC 的访问接口,使得我们在应用程序中能够方便的集成使用。其架构如图:
从其架构来看,Phoenix 结构上划分为客户端和服务端两部分:
- 客户端包括应用程序开发,将 SQL 进行解析优化生成 QueryPlan,进而转化为 HBase Scans,调用 HBase API 下发查询计算请求,并接收返回结果;
- 服务端主要是利用 HBase 的协处理器(Phoenix-core 包里面包含 hbase-client,以及 hbase-server 包),处理二级索引、聚合及 JOIN 计算等。 这种架构我们称之为重客户端架构,也是目前 Phoenix 使用最广泛的方式,但是这种方式存在一些使用上的缺陷:
- 应用程序与 Phoenix core 绑定使用,需要引入 Phoenix 内核依赖,目前一个单独 Phoenix 重客户端集成包已高达190M;
- 运维不便,Phoenix 仍在不断优化和发展,一旦 Phoenix 版本更新,那么应用程序也需要对应升级版本并重新发布;
- 仅支持 Java API,其他语言开发者不能使用 Phoenix。
轻客户端
针对重客户端使用问题,Phoenix 社区引入了轻客户端架构(4.4 版本),如图所示:
轻客户端架构将 Phoenix 分为三部分:
- 轻客户端是用户最小依赖的 JDBC 驱动程序,与 Phoenix 依赖进行解耦,支持 Java、Python、Go 等多种语言客户端;
- QueryServer 是一个单独部署的 HTTP 服务,接收轻客户端的 RPC 请求,并将 SQL 转发给 Phoenix Core 进行解析优化执行;
- Phoenix Server 与重客户端架构相同。
QueryServer
QueryServer 基于 Calcite 的 Avatica 组件实现,内部嵌入了独立的 Jetty HttpServer,支持 Protobuf 和 JSON 两种 RPC传输协议,其中 Protobuf 是默认协议,提供比 JSON 更高效的通信方式。
由于 QueryServer 是无状态的,可以部署在 HBase 集群的每台 HRegionServer 上,通过 HTTP 负载均衡器将多个客户端的请求分发在多个 QueryServer 上。
总结
Phoenix 轻客户端使业务端应用程序更加轻薄,业务开发人员无需再花费精力在底层 Phoenix 升级及运维,更加专注于业务本身,同时提供给非 Java 开发人员使用 Phoenix 的一种途径。 相比较重客户端,轻客户端实现增加了 RPC 请求链路,在性能上略有降低。
性能
特性
-
表抽样。通过实现一个过滤器来支持 TABLESAMPLE 子句,该过滤器使用由统计信息收集建立的路标仅返回一定百分比的行。4.12 版本。
-
减少磁盘存储。通过以下方式减少磁盘存储,以提高性能:①将所有值打包到每个列族的单个单元中;②提供列名和列限定符之间的间接寻址。4.10 版本。
-
原子更新。现在可以在 UPSERT VALUES 语句中进行原子更新,以支持计数器和其他用例。4.9 版本。
-
默认声明。现在在定义列时,可以为初始值提供 DEFAULT 声明。4.9 版本。
-
命名空间映射。将 Phoenix 模式映射到 HBase 命名空间,以改善不同模式之间的隔离。4.8 版本。
-
Hive 整合。使 Hive 与 Phoenix 一起使用,以支持将大型表连接到其他大型表。4.8 版本。
-
改进本地索引。重新设计了本地索引实现,以确保表和索引数据的共置,并使用受支持的 HBase API 以获得更好的可维护性。4.8 版本。
-
DISTINCT 查询优化。在主键的前导部分上将查找逻辑推入服务器,以进行 SELECT DISTINCT 和 COUNT DISTINCT 查询,从而带来显着更好的性能。4.8 版本。
-
事务支持。通过与 Tephra 集成来支持事务。4.7 版本。
-
时间序列优化。优化对时间序列数据的查询,这里为更详细地解释。4.6 版本。
-
二级索引。在 HBase 中,你有一个按主行键字典顺序排序的索引。除了通过 RowKey 以外,以任何方式访问记录都可能需要扫描表中的所有行。使用二级索引,索引的列或表达式形成一个备用行键,以允许沿着这个新轴进行点查找和范围扫描。4.5 版本。
-
UDFs。允许用户创建自定义或特定于域的用户定义功能并将其部署到群集。4.4 版本。
-
函数索引。允许将索引定义为表达式,而不仅仅是列名,并在查询包含该表达式时使用索引。4.3 版本。
-
集成 MapReduce。Phoenix 支持从 MapReduce 作业中检索和写入 Phoenix 表。框架现在提供了自定义的 InputFormat和 OutputFormat 类 PhoenixInputFormat, PhoenixOutputFormat。3.3 / 4.3 版本。
-
统计收集。收集表的统计信息以改善查询并行化。3.2 版本。
-
JOIN 支持。Phoenix 现在支持标准的 SQL 连接语法(有一些限制),可以基于具有公共值的字段来组合来自两个或多个表的记录。
-
子查询。支持 WHERE 子句中的独立子查询和相关子查询以及 FROM 子句中的子查询。3.2 版本。
-
追踪。允许查看 UPSERT 或 SELECT 语句的各个步骤,以及每个步骤在集群中所有计算机上花费的时间。4.1 版本。
-
本地索引。使用本地索引,索引和表数据共存于同一服务器上,因此在写入过程中不会发生网络开销。即使查询未完全覆盖,也可以使用局部索引(即 Phoenix 通过针对数据表的指向获取自动检索不在索引中的列)。4.1 版本。
-
集成 Pig。允许用户在 Pig 脚本中从 Phoenix 支持的 HBase 表中读取数据。3.1 版本。
-
视图支持。Phoenix 现在支持标准的 SQL 视图语法(有一些限制),从而允许多个虚拟表共享相同的底层物理 HBase表。这在 HBase 中很重要,因为 HBase 可以管理的区域数量是有限制的。限制表的数量可以帮助限制集群中区域的数量。3.0 版本。
-
多租户。允许不同租户在每个连接的基础上创建独立视图,这些视图均共享相同的物理 HBase 表。3.0 版本。
-
序列支持。序列是一种标准的 SQL 特性,它允许生成单调递增的数字,通常用于形成 ID。3.0 版本。
-
分页查询。Phoenix 支持标准 SQL 构造来启用分页查询。
-
CSV 批量加载。通过 Map-Reduce 或客户端脚本将 CSV 文件批量加载到 HBase 中。 类型添加。现在支持 FLOAT,DOUBLE,TINYINT 和 SMALLINT。
-
CSV 批量加载。当使用前导行键列的查询中出现 IN(或等效的 OR)和 LIKE 时,请将其编译为跳过扫描过滤器,以更有效地检索查询结果。
-
支持主键列的 ASC / DESC 声明。允许将主键列声明为升序(默认)或降序,以使行键顺序可以匹配所需的排序顺序(从而防止进行额外的排序)。
-
盐化行键。为了防止写入时出现热点,可以通过在行密钥中插入前导字节来“加盐”,这是整个行密钥的 N 个存储桶上的 mod 模。当行键是单调递增的值(通常是表示当前时间的时间戳)时,这可确保写入的均匀分布。
-
TopN 查询。当与 TopN 结合使用时,通过对 ORDER BY 的支持,支持返回前 N 行的查询。
-
动态列。对于某些用例,很难预先对模式进行建模,可能有一些只想在查询时指定的列。在 HBase 中这是可能的,因为每一行(和列族)都包含一个带有可在运行时指定的键的值的映射。
-
Apache Bigtop 包含。有关更多信息,请参见 BIGTOP-993。
-
……
下载
下载地址:https://phoenix.apache.org/download.html
历史版本:http://archive.apache.org/dist/phoenix/
重客户端:
轻客户端:
安装
目标环境
重客户端安装
Phoenix 是以 JDBC 驱动方式嵌入到 HBase 中的,在部署时只有一个 Jar 包,直接放到 HBase 的 lib 目录即可。
- 重客户端为:
phoenix-server-hbase-2.5-5.1.3.jar
- 轻客户端为:
phoenix-queryserver-6.0.0.jar
上传/解压/拷贝
将重客户端安装包上传至服务器。解压后将 phoenix-server-hbase-xx.jar
包拷贝至三台 HBase 的 lib 目录下。
# 解压
[root@node01 ~]# tar -zxvf phoenix-hbase-2.5-5.1.3-bin.tar.gz -C /opt/yjx/
[root@node01 ~]# rm phoenix-hbase-2.5-5.1.3-bin.tar.gz -rf
# 切换目录
[root@node01 ~]# cd /opt/yjx/phoenix-hbase-2.5-5.1.3-bin/
# 拷贝
[root@node01 phoenix-hbase-2.5-5.1.3-bin]# cp phoenix-server-hbase-2.5-5.1.3.jar /opt/yjx/hbase-
2.5.3/lib/
[root@node01 phoenix-hbase-2.5-5.1.3-bin]# scp phoenix-server-hbase-2.5-5.1.3.jar
root@node02:/opt/yjx/hbase-2.5.3/lib/
[root@node01 phoenix-hbase-2.5-5.1.3-bin]# scp phoenix-server-hbase-2.5-5.1.3.jar
root@node03:/opt/yjx/hbase-2.5.3/lib/
修改配置文件
修改 Phoenix 的配置文件vim /opt/yjx/phoenix-hbase-2.5-5.1.3-bin/bin/hbase-site.xml
。
配置文件的 configuration
节点完整内容如下。
<!-- 二级索引 -->
<property>
<name>hbase.regionserver.wal.codec</name>
<value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>
</property>
<!-- 开启 Schema 与 NameSpace 的映射 -->
<property>
<name>phoenix.schema.isNamespaceMappingEnabled</name>
<value>true</value>
</property>
<property>
<name>phoenix.schema.mapSystemTablesToNamespace</name>
<value>true</value>
</property>
修改 HBase 的配置文件 vim /opt/yjx/hbase-2.5.3/conf/hbase-site.xml
。
<!-- 二级索引 -->
<property>
<name>hbase.regionserver.wal.codec</name>
<value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>
</property>
<!-- 开启 Schema 与 NameSpace 的映射 -->
<property>
<name>phoenix.schema.isNamespaceMappingEnabled</name>
<value>true</value>
</property>
<property>
<name>phoenix.schema.mapSystemTablesToNamespace</name>
<value>true</value>
</property>
拷贝至其他节点
将 Hbase 的 hbase-site.xml 拷贝至 node02 和 node03。
[root@node01 ~]# scp /opt/yjx/hbase-2.5.3/conf/hbase-site.xml root@node02:/opt/yjx/hbase-2.5.3/conf/
[root@node01 ~]# scp /opt/yjx/hbase-2.5.3/conf/hbase-site.xml root@node03:/opt/yjx/hbase-2.5.3/conf/
# 或者使用分发脚本
[root@node01 ~]# yjxrsync /opt/yjx/hbase-2.5.3/conf/hbase-site.xml
将 node01 已配置好的 Phoenix 拷贝至 node02 和 node03。
[root@node02 ~]# scp -r root@node01:/opt/yjx/phoenix-hbase-2.5-5.1.3-bin/ /opt/yjx/
[root@node03 ~]# scp -r root@node01:/opt/yjx/phoenix-hbase-2.5-5.1.3-bin/ /opt/yjx/
# 或者使用分发脚本
[root@node01 ~]# yjxrsync /opt/yjx/phoenix-hbase-2.5-5.1.3-bin
修改环境变量
三个节点修改环境变量 vim /etc/profile
,在文件末尾添加以下内容:
export PHOENIX_HOME=/opt/yjx/phoenix-hbase-2.5-5.1.3-bin
export PATH=$PHOENIX_HOME/bin:$PATH
修改完成后 source /etc/profile
重新加载环境变量。
启动
重启 HBase 即可,因为使用 Phoenix 重客户端无需单独启动服务,会随着 HBase 的启动一起启动。
连接
[root@node01 ~]# sqlline.py node01,node02,node03:2181
首次连接时会在 HBase 中自动生成 8 张表,如下:
SYSTEM.CATALOG
SYSTEM.CHILD_LINK
SYSTEM.FUNCTION
SYSTEM.LOG
SYSTEM.MUTEX
SYSTEM.SEQUENCE
SYSTEM.STATS
SYSTEM.TASK
退出
0: jdbc🐦🔥node01,node02,node03:2181> !quit
环境搭建成功后删除安装包,有序退出各组件,然后 shutdown -h now
关机拍摄快照。
数据模型
Phoenix 在数据模型上是将 HBase 非关系型形式转换成关系型数据模型 ,如下图所示。
对于 Phoenix 来说,HBase 的 RowKey 会被转换成 Primary Key;Column Family 如果不指定默认为 0;Qualifier 转换成表的字段名。 如下是创建一个 Phoenix 表的例子,以创建表 test 为例,主键为 id 即为 HBase 的 RowKey,Column Family 为 info,Qualifier 为 name 和 age。
CREATE TABLE test (
"id" VARCHAR(20) PRIMARY KEY,
"info"."name" VARCHAR(20),
"info"."age" VARCHAR(20)
);
Phoenix 还支持组合 Primary Key,即由多个字段联合组成主键。对于组合主键来说,在 HBase 底层会把主键的多个字段组合成 RowKey 显示,其它字段为 HBase 的 Qualifier 显示。如上面 test 表,假设 id 和 name 为主键,创建表语句又会变为:
CREATE TABLE test (
"id" VARCHAR(20),
"name" VARCHAR(20),
"info"."age" VARCHAR(20),
CONSTRAINT PK PRIMARY KEY("id", "name")
);
假设插入一条数据,如下所示:
UPSERT INTO test VALUES ('1', 'a', '18');
在 HBase 中,RowKey 即为 1a
,info:age 为 18。
这里,可能大家对双引号有点疑问,Phoenix 对于库名(NameSpace),表名,列族名,列名是区分大小写的,默认都会转为成大写。如果要屏蔽转换,需要在对应的字符上使用双引号,例如 "id"
。数据类型是字符串的话,要用单引号 '
包含。
常用命令
目前 Phoenix 已经支持关系型数据库的大部分语法,如下图所示:
具体使用可参考 Phoenix 官网。
注意:Phoenix 对于库名(NameSpace),表名,列族名,列名是区分大小写的,默认都会转为成大写。如果要屏蔽转换,需要在对应的字符上使用双引号,例如
"test"
。数据类型是字符串的话,要用单引号'
包含。
查看所有表
!tables
创建 Schema(数据库)
CREATE SCHEMA school;
CREATE SCHEMA "school";
注意大小写问题,HBase 视角如下:
创建表
在 Phoenix Shell 上建的表默认只有一个 HRegion,建表时要注意预分区(推荐使用 HBase 建表)。
# 创建完的表名和字段名都会自动转成大写,如需小写,需在建表时给表名和字段名前后加双引号
CREATE TABLE school.teacher (
tno INTEGER NOT NULL PRIMARY KEY,
tname VARCHAR,
age INTEGER
);
============================== 华丽的分割线 ==============================
CREATE TABLE "school"."teacher" (
"tno" INTEGER NOT NULL PRIMARY KEY,
"info"."tname" VARCHAR,
"info"."age" INTEGER
);
Phoenix 视角:
HBase 视角如下:
插入记录
UPSERT INTO school.teacher (tno, tname, age) VALUES (1, 'zhangsan', 18);
UPSERT INTO school.teacher (tno, tname, age) VALUES (2, 'lisi', 18);
UPSERT INTO school.teacher (tno, tname, age) VALUES (3, 'wangwu', 20);
============================== 华丽的分割线 ==============================
UPSERT INTO "school"."teacher" ("tno", "info"."tname", "info"."age") VALUES (1, 'zhangsan', 18);
UPSERT INTO "school"."teacher" ("tno", "info"."tname", "info"."age") VALUES (2, 'lisi', 18);
UPSERT INTO "school"."teacher" ("tno", "info"."tname", "info"."age") VALUES (3, 'wangwu', 20);
Phoenix 视角:
HBase 视角如下:
Column Family 如果不指定则为 0。
更新记录
UPSERT INTO school.teacher (tno, tname, age) VALUES (1, 'laozhang', 38);
SELECT tno, "0".tname, "0".age FROM school.teacher;
+-----+----------+-----+
| TNO | TNAME | AGE |
+-----+----------+-----+
| 1 | laozhang | 38 |
| 2 | lisi | 18 |
| 3 | wangwu | 20 |
+-----+----------+-----+
============================== 华丽的分割线 ==============================
UPSERT INTO "school"."teacher" ("tno", "info"."tname", "info"."age") VALUES (1, 'laozhang', 38);
SELECT "tno", "info"."tname", "info"."age" FROM "school"."teacher";
+-----+----------+-----+
| tno | tname | age |
+-----+----------+-----+
| 1 | laozhang | 38 |
| 2 | lisi | 18 |
| 3 | wangwu | 20 |
+-----+----------+-----+
查询记录
SELECT * FROM school.teacher;
SELECT tno, tname, age FROM school.teacher;
SELECT tno, "0".tname, "0".age FROM school.teacher;
============================== 华丽的分割线 ==============================
SELECT * FROM "school"."teacher";
SELECT "tno", "tname", "age" FROM "school"."teacher";
SELECT "tno", "info"."tname", "info"."age" FROM "school"."teacher";
删除记录
DELETE FROM school.teacher WHERE tno = 1;
SELECT tno, "0".tname, "0".age FROM school.teacher;
+-----+--------+-----+
| TNO | TNAME | AGE |
+-----+--------+-----+
| 2 | lisi | 18 |
| 3 | wangwu | 20 |
+-----+--------+-----+
============================== 华丽的分割线 ==============================
DELETE FROM "school"."teacher" WHERE "tno" = 1;
SELECT "tno", "info"."tname", "info"."age" FROM "school"."teacher";
+-----+--------+-----+
| tno | tname | age |
+-----+--------+-----+
| 2 | lisi | 18 |
| 3 | wangwu | 20 |
+-----+--------+-----+
查看索引
!tables school.teacher
删除表
DROP TABLE school.teacher;
DROP TABLE "school"."teacher";
删除 Schema(数据库)
DROP SCHEMA school;
DROP SCHEMA "school";
以上在 Phoenix 中的 DDL 和 DML 操作在 HBase 对应的表中也会同时触发,即操作通过 Phoenix 来操作 HBase。
退出
!quit
更多操作:https://phoenix.apache.org/language/index.html
二级索引
索引构建
同步索引
CREATE INDEX my_index ON SCHEMA_NAME.TABLE_NAME (BASICINFO."s1", BASICINFO."s2");
创建同步索引超时怎么办?在客户端配置文件 hbase-site.xml 中,把超时参数设置大一些。
<!-- 设置一次 RPC 请求的超时时间。如果某次 RPC 时间超过该值,客户端就会主动关闭 Socket,服务端会抛出:java.io.IOException: Connection reset by peer,默认为 60000ms,即 1min -->
<property>
<name>hbase.rpc.timeout</name>
<value>6000000</value>
</property>
<!-- 设置一次 Scan 中,一次 RPC 请求的超时时间(一次 Scan 可能有多次 RPC 请求),默认为 60000ms,即 1min --
>
<property>
<name>hbase.client.scanner.timeout.period</name>
<value>6000000</value>
</property>
<!-- 设置客户端发起一次数据操作直至响应之间的总超时时间,数据操作包括 get、append、increment、delete、put等,默认为 1200000ms,即 20min -->
<property>
<name>hbase.client.operation.timeout</name>
<value>12000000</value>
</property>
<!-- 设置在客户端上查询的超时毫秒数,默认为 600000ms,即 10min -->
<property>
<name>phoenix.query.timeoutMs</name>
<value>6000000</value>
</property>
异步索引
异步 Build 索引需要借助 MapReduce,创建异步索引语法和同步索引只差一个关键字:ASYNC。
CREATE INDEX async_index ON SCHEMA_NAME.TABLE_NAME (BASICINFO."s1", BASICINFO."s2") ASYNC;
命令执行
hbase org.apache.phoenix.mapreduce.index.IndexTool \
# 命名空间
--schema SCHEMA_NAME \
# 源数据表
--data-table TABLE_NAME \
# 索引表
--index-table async_index \
# MapReduce 任务写出的目标文件存放的 HDFS 路径
--output-path ASYNC_IDX_HFILES
注意:异步创建索引时,如果有新数据写入,会出现索引表数据丢失现象。创建完成后,数据正常写入不会丢失,所以,创建完成后可能需要往索引表补数据。
创建测试表
CREATE TABLE my_table (
rowkey VARCHAR NOT NULL PRIMARY KEY,
v1 VARCHAR,
v2 VARCHAR,
v3 VARCHAR
);
UPSERT INTO my_table VALUES ('rowkey1', 'value1', 'value2', '100010001');
UPSERT INTO my_table VALUES ('rowkey2', 'value1', 'value2', '100010002');
UPSERT INTO my_table VALUES ('rowkey3', 'value1', 'value2', '100010003');
UPSERT INTO my_table VALUES ('rowkey4', 'value1', 'value2', '100010004');
UPSERT INTO my_table VALUES ('rowkey5', 'value1', 'value2', '100010005');
UPSERT INTO my_table VALUES ('rowkey6', 'value1', 'value2', '100010006');
UPSERT INTO my_table VALUES ('rowkey7', 'value1', 'value2', '100010007');
UPSERT INTO my_table VALUES ('rowkey8', 'value1', 'value2', '100010008');
UPSERT INTO my_table VALUES ('rowkey9', 'value1', 'value2', '100010009');
UPSERT INTO my_table VALUES ('rowkey10', 'value1', 'value2', '100010010');
UPSERT INTO my_table VALUES ('rowkey11', 'value1', 'value2', '100010011');
UPSERT INTO my_table VALUES ('rowkey12', 'value1', 'value2', '100010012');
UPSERT INTO my_table VALUES ('rowkey13', 'value1', 'value2', '100010013');
UPSERT INTO my_table VALUES ('rowkey14', 'value1', 'value2', '100010014');
UPSERT INTO my_table VALUES ('rowkey15', 'value1', 'value2', '100010015');
UPSERT INTO my_table VALUES ('rowkey16', 'value1', 'value2', '100010016');
UPSERT INTO my_table VALUES ('rowkey17', 'value1', 'value2', '100010017');
UPSERT INTO my_table VALUES ('rowkey18', 'value1', 'value2', '100010018');
UPSERT INTO my_table VALUES ('rowkey19', 'value1', 'value2', '100010019');
UPSERT INTO my_table VALUES ('rowkey20', 'value1', 'value2', '100010020');
Global Indexes 全局索引
全局索引适合读多写少的场景。如果使用全局索引,读数据基本不损耗性能,所有的性能损耗都来源于写数据。数据表的添加、删除和修改都会更新相关的索引表(数据删除了,索引表中的数据也会删除;数据增加了,索引表的数据也会增加)。
注意:全局索引在默认情况下,查询语句中检索的列如果不在索引表中,Phoenix 不会使用索引表。除非使用 hint。
创建全局索引
CREATE INDEX my_index ON my_table (v3);
索引表中 RowKey 为 v3 + 原表 RowKey。索引原理:Scan + Filter 形成前缀过滤器。
查看索引
索引表也是表(在 Phoenix 端删除数据表时索引表会一起被删除)。
SELECT * FROM my_index;
+-----------+----------+
| 0:V3 | :ROWKEY |
+-----------+----------+
| 100010001 | rowkey1 |
| 100010002 | rowkey2 |
| 100010003 | rowkey3 |
| 100010004 | rowkey4 |
| 100010005 | rowkey5 |
| 100010006 | rowkey6 |
| 100010007 | rowkey7 |
| 100010008 | rowkey8 |
| 100010009 | rowkey9 |
| 100010010 | rowkey10 |
| 100010011 | rowkey11 |
| 100010012 | rowkey12 |
| 100010013 | rowkey13 |
| 100010014 | rowkey14 |
| 100010015 | rowkey15 |
| 100010016 | rowkey16 |
| 100010017 | rowkey17 |
| 100010018 | rowkey18 |
| 100010019 | rowkey19 |
| 100010020 | rowkey20 |
+-----------+----------+
测试
SELECT * FROM my_table WHERE v3 = '100010010';
+----------+--------+--------+-----------+
| ROWKEY | V1 | V2 | V3 |
+----------+--------+--------+-----------+
| rowkey10 | value1 | value2 | 100010010 |
+----------+--------+--------+-----------+
1 row selected (2.337 seconds)
SELECT v3 FROM my_table WHERE v3 = '100010010';
+-----------+
| V3 |
+-----------+
| 100010010 |
+-----------+
1 row selected (2.155 seconds)
CREATE INDEX my_index ON my_table (v3);
1,076,190 rows affected (33.875 seconds)
SELECT * FROM my_table WHERE v3 = '100010010';
+----------+--------+--------+-----------+
| ROWKEY | V1 | V2 | V3 |
+----------+--------+--------+-----------+
| rowkey10 | value1 | value2 | 100010010 |
+----------+--------+--------+-----------+
1 row selected (3.296 seconds)
SELECT v3 FROM my_table WHERE v3 = '100010010';
+-----------+
| V3 |
+-----------+
| 100010010 |
+-----------+
1 row selected (0.02 seconds)
使用 Hint 强制索引
SELECT /*+ INDEX(my_table local_index) */ v1 FROM my_table WHERE v3 = '100010010';
删除索引
DROP INDEX my_index ON my_table;
Local Indexes 本地索引
本地索引适合写多读少的场景,或者存储空间有限的场景。和全局索引一样,Phoenix 也会在查询的时候自动选择是否使用本地索引。Phoenix 会将索引数据和原数据放在同一个 HRegionServer上,从而保证索引查找是本地的。本地索引因为索引数据和原数据存储在同一台机器上,避免网络数据传输的开销,所以更适合写多的场景。由于无法提前确定数据在哪个 HRegion上,所以在读数据的时候,需要检查每个 HRegion 上的数据从而带来一些性能损耗。
注意:对于本地索引,查询中无论是否指定 hint,或者查询语句中检索的列是否在索引表中,都会使用索引表。相当 于之前的第二种将数据拿出来单独建一张表,并改变 RowKey。
创建本地索引
CREATE LOCAL INDEX local_index ON my_table (v3);
索引表中 RowKey 为 v3 + 原表 RowKey。
测试
SELECT * FROM my_table WHERE v3 = '100010010';
+----------+--------+--------+-----------+
| ROWKEY | V1 | V2 | V3 |
+----------+--------+--------+-----------+
| rowkey10 | value1 | value2 | 100010010 |
+----------+--------+--------+-----------+
1 row selected (3.545 seconds)
SELECT v3 FROM my_table WHERE v3 = '100010010';
+-----------+
| V3 |
+-----------+
| 100010010 |
+-----------+
1 row selected (2.946 seconds)
CREATE LOCAL INDEX local_index ON my_table (v3);
1,076,190 rows affected (24.67 seconds)
SELECT * FROM my_table WHERE v3 = '100010010';
+----------+--------+--------+-----------+
| ROWKEY | V1 | V2 | V3 |
+----------+--------+--------+-----------+
| rowkey10 | value1 | value2 | 100010010 |
+----------+--------+--------+-----------+
1 row selected (0.055 seconds)
SELECT v3 FROM my_table WHERE v3 = '100010010';
+-----------+
| V3 |
+-----------+
| 100010010 |
+-----------+
1 row selected (0.013 seconds)
删除索引
和删除全局索引的语法一样。
DROP INDEX local_index ON my_table;
Covered Indexes 覆盖索引
覆盖索引是把原数据存储在索引数据表中,这样在查询时不需要再去 HBase 的原表获取数据,直接返回查询结果。
注意:对于覆盖索引,查询时 SELECT 的列和 WHERE 的列都需要在索引表中出现。
创建覆盖索引
CREATE INDEX cover_index ON my_table (v2, v3) INCLUDE (v1);
与全局索引差不多,相当于 v2 + v3 + v1 + 原 RowKey 作为 RowKey。表中加了一个列 v1。
测试
SELECT * FROM my_table WHERE v3 = '100010010' AND v2 = 'value2';
+----------+--------+--------+-----------+
| ROWKEY | V1 | V2 | V3 |
+----------+--------+--------+-----------+
| rowkey10 | value1 | value2 | 100010010 |
+----------+--------+--------+-----------+
1 row selected (2.42 seconds)
CREATE INDEX cover_index ON my_table (v2, v3) INCLUDE (v1);
1,076,190 rows affected (47.432 seconds)
SELECT * FROM my_table WHERE v3 = '100010010' AND v2 = 'value2';
+----------+--------+--------+-----------+
| ROWKEY | V1 | V2 | V3 |
+----------+--------+--------+-----------+
| rowkey10 | value1 | value2 | 100010010 |
+----------+--------+--------+-----------+
1 row selected (0.031 seconds)
删除索引
和删除全局索引的语法一样。
DROP INDEX cover_index ON my_table;
Functional Indexes 函数索引
从 Phoenix 4.3 版本开始有函数索引,特点是索引的内容不局限于列,能根据表达式创建索引。适用于对查询表时过滤条件是表达式。如果你使用的表达式正好就是索引的话,数据也可以直接从这个索引获取,而不需要从数据库获取。
创建函数索引
CREATE INDEX func_index ON my_table (SUBSTR(v3, 5, 5)) INCLUDE (v1);
注意:使用函数索引,查询语句中带上 hint 也没有作用。
测试
SELECT v1, SUBSTR(v3, 5, 5) FROM my_table WHERE SUBSTR(v3, 5, 5) = '10006';
+--------+------------------+
| V1 | SUBSTR(V3, 5, 5) |
+--------+------------------+
| value1 | 10006 |
+--------+------------------+
1 row selected (3.656 seconds)
SELECT v1, v3 FROM my_table WHERE SUBSTR(v3, 5, 5) = '10006';
+--------+-----------+
| V1 | V3 |
+--------+-----------+
| value1 | 100010006 |
+--------+-----------+
1 row selected (3.969 seconds)
CREATE INDEX func_index ON my_table (SUBSTR(v3, 5, 5)) INCLUDE (v1);
1,076,190 rows affected (45.833 seconds)
SELECT v1, v3 FROM my_table WHERE SUBSTR(v3, 5, 5) = '10006';
+--------+-----------+
| V1 | V3 |
+--------+-----------+
| value1 | 100010006 |
+--------+-----------+
1 row selected (3.44 seconds)
SELECT v1, v3, SUBSTR(v3, 5, 5) FROM my_table WHERE SUBSTR(v3, 5, 5) = '10006';
+--------+-----------+------------------+
| V1 | V3 | SUBSTR(V3, 5, 5) |
+--------+-----------+------------------+
| value1 | 100010006 | 10006 |
+--------+-----------+------------------+
1 row selected (3.327 seconds)
SELECT v1, SUBSTR(v3, 5, 5) FROM my_table WHERE SUBSTR(v3, 5, 5) = '10006';
+--------+-------------------+
| V1 | " SUBSTR(V3,5,5)" |
+--------+-------------------+
| value1 | 10006 |
+--------+-------------------+
1 row selected (0.013 seconds)
SELECT v1 FROM my_table WHERE SUBSTR(v3, 5, 5) = '10006';
+--------+
| V1 |
+--------+
| value1 |
+--------+
1 row selected (0.011 seconds)
删除索引
和删除全局索引的语法一样。
DROP INDEX func_index ON my_table;
索引失效与重建
失效原因
Phoenix 端数据写入时,HBase 宕机或重启,导致写入中断,写入更新索引表失败,导致索引失效,很多情况下是重启 HBase 没有停 Phoenix 服务导致索引数据不一致。
查看索引
执行 !tables
查看索引表状态,当 INDEX_ST
为 PENDING_DISABLE
时表示索引不可用,需要进行修复。
也可在 SYSTEM.CATALOG
表中查看索引状态,如下:
SELECT
TABLE_NAME, -- 表名(索引表也是个表)
DATA_TABLE_NAME, -- 数据表(我们保存数据的表,不包括索引表,虽然他也存数据)
INDEX_TYPE, -- 索引类型
INDEX_STATE, -- 索引状态
INDEX_DISABLE_TIMESTAMP -- DISABLE 的时间
FROM SYSTEM.CATALOG
WHERE INDEX_TYPE IS NOT NULL;
+------------+-----------------+------------+-------------+-------------------------+
| TABLE_NAME | DATA_TABLE_NAME | INDEX_TYPE | INDEX_STATE | INDEX_DISABLE_TIMESTAMP |
+------------+-----------------+------------+-------------+-------------------------+
| MY_INDEX | MY_TABLE | 1 | a | 0 |
+------------+-----------------+------------+-------------+-------------------------+
INDEX_STATE
会有 a, w, x, b
等枚举。如果为 a, e
则为正常服务;x, d
则为索引挂起,表示不可使用需要重建或者修复;i
为不可用,但在自动修复; b
为重建中。
修复索引
自动修复
一般索引失效 Phoenix 会自动修复,但是基本上修复不好。
新建索引
新建一张和失效索引一样的索引表。数据量比较小可以新建一张和原来一样的索引表,以解燃眉之急。但是如果数据量比较大那就 GG 了,可能会建很久(几个小时)。
手动修复
先找到失效的索引。
SELECT
TABLE_NAME, -- 表名(索引表也是个表)
DATA_TABLE_NAME, -- 数据表(我们保存数据的表,不包括索引表,虽然他也存数据)
INDEX_TYPE, -- 索引类型
INDEX_STATE, -- 索引状态
INDEX_DISABLE_TIMESTAMP -- DISABLE 的时间
FROM SYSTEM.CATALOG
WHERE INDEX_TYPE IS NOT NULL AND INDEX_STATE IN ('w', 'x', 'i', 'd');
设置索引为 DISABLE。
ALTER INDEX my_index ON my_table DISABLE;
重建索引。
ALTER INDEX IF EXISTS my_index ON my_table REBUILD;
总结:数据量小推荐方法二,生产推荐方法二和三,其中方法三有可能会重建失败。
有可能重建索引失败,失败的原因可能是表的数据量太大,或者生产环境中正在对该表进行操作。多尝试几次即可。如果还是失败,建议删除索引,然后重新创建索引。
总结
这里总结一下比较常用的全局索引和本地索引。全局索引适合读多写少的场景,如果使用全局索引,读数据基本不损耗性能,所有的性能损耗都来源于写数据。本地索引适合写多读少,或者存储空间有限的场景。
索引定义完之后,一般来说,Phoenix 会判定使用哪个索引更加有效。但是,全局索引必须是查询语句中所有列都包含在全局索引中,它才会生效。 本地索引和全局索引不同的是,查询语句中,即使所有的列都不在索引定义中,它也会使用索引,这是本地索引的默认行为。Phoenix 会将索引数据和原数据放在同一个 HRegionServer上,从而保证索引查找是本地的。
映射表
Phoenix 支持通过视图 VIEW 映射 HBase 中的表,具体使用流程如下。
HBase 创建表
HBase 创建表。
# 创建 test 命令空间
create_namespace 'test'
# 创建 hbase_user 表并添加 personal 列族和 office 列族
create 'test:t_user', {NAME => 'personal', VERSIONS => 3}, {NAME => 'office', VERSIONS => 3}, {SPLITS
=> ['user-1', 'user-2', 'user-4']}
HBase 插入数据。
put 'test:t_user', 'user-1', 'personal:name', 'zhangsan'
put 'test:t_user', 'user-1', 'personal:gender', 1
put 'test:t_user', 'user-1', 'office:phone', '13800000000'
put 'test:t_user', 'user-1', 'office:address', 'beijing'
put 'test:t_user', 'user-1', 'office:phone', '13000000000'
put 'test:t_user', 'user-1', 'office:address', 'tianjin'
put 'test:t_user', 'user-1', 'office:phone', '13800000000'
put 'test:t_user', 'user-1', 'office:address', 'shanghai'
put 'test:t_user', 'user-10', 'personal:name', 'lisi'
put 'test:t_user', 'user-10', 'personal:gender', 0
put 'test:t_user', 'user-10', 'office:address', 'shanghai'
put 'test:t_user', 'user-11', 'personal:name', 'wangwu'
put 'test:t_user', 'user-11', 'personal:gender', 1
put 'test:t_user', 'user-11', 'personal:age', 20
put 'test:t_user', 'user-11', 'office:phone', '13900000000'
put 'test:t_user', 'user-11', 'office:address', 'guangzhou'
put 'test:t_user', 'user-2', 'personal:name', 'zhaoliu'
put 'test:t_user', 'user-2', 'personal:gender', 1
put 'test:t_user', 'user-2', 'personal:age', 19
put 'test:t_user', 'user-2', 'office:phone', '13100000000'
put 'test:t_user', 'user-3', 'personal:name', 'tianqi'
put 'test:t_user', 'user-3', 'personal:gender', 0
put 'test:t_user', 'user-3', 'office:address', 'shaanxi'
Phoenix 创建视图
CREATE VIEW "test"."t_user" (
user_id VARCHAR PRIMARY KEY,
"personal"."name" VARCHAR,
"personal"."gender" VARCHAR,
"personal"."age" VARCHAR,
"office"."phone" VARCHAR,
"office"."address" VARCHAR
);
Phoenix 查询数据
0: jdbc🐦🔥node01,node02,node03:2181> SELECT * FROM "test"."t_user";
+---------+----------+--------+-----+-------------+-----------+
| USER_ID | name | gender | age | phone | address |
+---------+----------+--------+-----+-------------+-----------+
| user-1 | zhangsan | 1 | | 13800000000 | shanghai |
| user-2 | zhaoliu | 1 | 19 | 13100000000 | |
| user-10 | lisi | 0 | | | shanghai |
| user-3 | tianqi | 0 | | | shaanxi |
| user-11 | wangwu | 1 | 20 | 13900000000 | guangzhou |
+---------+----------+--------+-----+-------------+-----------+
5 rows selected (0.032 seconds)
Phoenix 删除视图
Phoenix 中删除视图,HBase 的表不受影响。
DROP VIEW "test"."t_user";
JDBC
创建项目
完整 pom.xml 文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mrhelloworld</groupId>
<artifactId>phoenix-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- Phoenix 核心包 -->
<dependency>
<groupId>org.apache.phoenix</groupId>
<artifactId>phoenix-core</artifactId>
<version>5.1.3</version>
</dependency>
<!-- Phoenix 与 HBase 的兼容包 -->
<dependency>
<groupId>org.apache.phoenix</groupId>
<artifactId>phoenix-hbase-compat-2.5.0</artifactId>
<version>5.1.3</version>
<scope>runtime</scope>
</dependency>
<!-- JUnit 单元测试 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
创建连接
package com.yjxxt.phoenix;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
@DisplayName("Phoenix JDBC 测试类")
public class PhoenixJdbcTest {
private Connection connection = null;
private Statement statement = null;
@BeforeEach
public void init() throws IOException {
try {
Class.forName("org.apache.phoenix.jdbc.PhoenixDriver");
// 配置 ZooKeeper 地址
String zkUrl = "jdbc🐦🔥node01,node02,node03:2181";
// 开启 Schema 与 NameSpace 的映射
Properties properties = new Properties();
properties.setProperty("phoenix.schema.isNamespaceMappingEnabled", "true");
// 创建数据库连接
connection = DriverManager.getConnection(zkUrl, properties);
// 创建 Statement 对象
statement = connection.createStatement();
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
}
@AfterEach
public void destory() throws IOException {
try {
if (statement != null && !statement.isClosed()) {
statement.close();
}
if (connection != null && !connection.isClosed()) {
connection.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
创建库
@DisplayName("创建库")
@Test
public void testCreateSchema() throws SQLException {
// 编写 SQL(JDBC 代码中不需要分号,否则报错)
String sql = "CREATE SCHEMA school";
// 执行 SQL
statement.executeUpdate(sql);
connection.commit();
System.out.println("创建数据库成功!");
}
创建表
@DisplayName("创建表")
@Test
public void testCreateTable() throws SQLException {
// 编写 SQL
StringBuffer sql = new StringBuffer();
sql.append("CREATE TABLE school.teacher (")
.append("tno INTEGER NOT NULL PRIMARY KEY,")
.append("tname VARCHAR,")
.append("age INTEGER)");
// 执行 SQL
statement.executeUpdate(sql.toString());
connection.commit();
System.out.println("创建数据表成功!");
}
插入/更新记录
@DisplayName("插入或修改记录")
@Test
public void testUpsert() throws SQLException {
// 编写 SQL
String sql1 = "UPSERT INTO school.teacher (tno, tname, age) VALUES (1, 'zhangsan', 18)";
String sql2 = "UPSERT INTO school.teacher (tno, tname, age) VALUES (2, 'lisi', 18)";
String sql3 = "UPSERT INTO school.teacher (tno, tname, age) VALUES (3, 'wangwu', 20)";
// 执行 SQL
statement.executeUpdate(sql1);
statement.executeUpdate(sql2);
statement.executeUpdate(sql3);
// Phoenix 批量插入优化(一次 commit 和多次 commit)
// 相同环境下,同时插入 50 条,第二种速度是第一种的 5-10 倍
// 并且数据量越大,第二种效果更明显,甚至能达到几十倍几百倍
connection.commit();
System.out.println("插入或更新记录成功!");
}
删除记录
@DisplayName("删除记录")
@Test
public void testDelete() throws SQLException {
// 编写 SQL
String sql = "DELETE FROM school.teacher WHERE tno = 1";
// 执行 SQL 并接收执行成功条数
int result = statement.executeUpdate(sql);
connection.commit();
System.out.format("成功删除 %d 条记录!", result);
}
查询记录
@DisplayName("查询记录")
@Test
public void testSelect() throws SQLException {
// 编写 SQL
String sql = "SELECT tno, \"0\".tname, \"0\".age FROM school.teacher";
// 执行 SQL 并接收返回对象
ResultSet resultSet = statement.executeQuery(sql);
connection.commit();
while (resultSet.next()) {
// 获取字段值
int tno = resultSet.getInt("tno");
String tname = resultSet.getString("tname");
int age = resultSet.getInt("age");
System.out.println("tno = " + tno);
System.out.println("tname = " + tname);
System.out.println("age = " + age);
}
resultSet.close();
}
更多操作请参考 Phoenix 官网或原生 JDBC。
附录
轻客户端安装
轻客户端的环境搭建依赖于重客户端,轻客户端内置了一个 Jetty 服务器,通过 Jetty 服务器连接至 Phoenix。
上传/解压/拷贝
将轻客户端安装包上传至服务器。解压后将phoenix-queryserver-xx.jar
包拷贝至三台 HBase 的 lib 目录下。
# 解压
[root@node01 ~]# tar -zxvf phoenix-queryserver-6.0.0-bin.tar.gz -C /opt/yjx/
# 切换目录
[root@node01 ~]# cd /opt/yjx/phoenix-queryserver-6.0.0/
# 拷贝
[root@node01 phoenix-queryserver-6.0.0]# cp phoenix-queryserver-6.0.0.jar /opt/yjx/hbase-2.5.3/lib/
[root@node01 phoenix-queryserver-6.0.0]# scp phoenix-queryserver-6.0.0.jar root@node02:/opt/yjx/hbase-
2.5.3/lib/
[root@node01 phoenix-queryserver-6.0.0]# scp phoenix-queryserver-6.0.0.jar root@node03:/opt/yjx/hbase-
2.5.3/lib/
轻客户端自身还需要重客户端的 phoenix-server-hbase-2.5-5.1.3.jar
包和 phoenix-client-hbase-2.5-5.1.3.jar
包,否则无法启动。
将重客户端的 phoenix-server-hbase-2.5-5.1.3.jar
包拷贝至三台 HBase 的 lib 目录下。
[root@node01 ~]# cp phoenix-server-hbase-2.5-5.1.3.jar /opt/yjx/hbase-2.5.3/lib/
[root@node01 ~]# scp phoenix-server-hbase-2.5-5.1.3.jar root@node02:/opt/yjx/hbase-2.5.3/lib/
[root@node01 ~]# scp phoenix-server-hbase-2.5-5.1.3.jar root@node03:/opt/yjx/hbase-2.5.3/lib/
将重客户端的 phoenix-client-hbase-2.5-5.1.3.jar
包拷贝至轻客户端根目录。
[root@node01 ~]# mv /root/phoenix-client-hbase-2.5-5.1.3.jar /opt/yjx/phoenix-queryserver-6.0.0/
修改配置文件
修改 HBase 的配置文件 vim /opt/yjx/hbase-2.5.3/conf/hbase-site.xml
。
<!-- 二级索引 -->
<property>
<name>hbase.regionserver.wal.codec</name>
<value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>
</property>
<!-- 开启 Schema 与 NameSpace 的映射 -->
<property>
<name>phoenix.schema.isNamespaceMappingEnabled</name>
<value>true</value>
</property>
<property>
<name>phoenix.schema.mapSystemTablesToNamespace</name>
<value>true</value>
</property>
拷贝至其他节点
将 Hbase 的 hbase-site.xml 拷贝至 node02 和 node03。
[root@node01 ~]# scp /opt/yjx/hbase-2.5.3/conf/hbase-site.xml root@node02:/opt/yjx/hbase-2.5.3/conf/
[root@node01 ~]# scp /opt/yjx/hbase-2.5.3/conf/hbase-site.xml root@node03:/opt/yjx/hbase-2.5.3/conf/
# 或者使用分发脚本
[root@node01 ~]# yjxrsync /opt/yjx/hbase-2.5.3/conf/hbase-site.xml
将 node01 已配置好的 Phoenix 拷贝至 node02 和 node03。
[root@node02 ~]# scp -r root@node01:/opt/yjx/phoenix-queryserver-6.0.0/ /opt/yjx/
[root@node03 ~]# scp -r root@node01:/opt/yjx/phoenix-queryserver-6.0.0/ /opt/yjx/
# 或者使用分发脚本
[root@node01 ~]# yjxrsync /opt/yjx/phoenix-queryserver-6.0.0
创建目录
轻客户端 pid 文件默认生成在 /tmp/phoenix
下,如果不创建启动时会报错 IOError: [Errno 2] No such file or directory: '/tmp/phoenix/phoenix-root-queryserver.pid'
。
[root@node01 ~]# mkdir /tmp/phoenix
修改环境变量
三个节点修改环境变量 vim /etc/profile
,在文件末尾添加以下内容:
export PHOENIX_QUERYSERVER_HOME=/opt/yjx/phoenix-queryserver-6.0.0
export PATH=$PHOENIX_QUERYSERVER_HOME/bin:$PATH
修改完成后 source /etc/profile
重新加载环境变量。
启动
先重启 HBase,HBase 启动时 HMaster 会分配 HRegion 到具体的 HRegionServer,由于 HBase 是 CP 架构,此时间段无法提供服务,所以稍等几分钟再启动 Phoenix 轻客户端。
[root@node01 ~]# queryserver.py start
# 查看 Phoenix 日志是否正常启动
cat /opt/yjx/hbase-2.5.3/logs/phoenix-root-queryserver.log
# 如果看到以下两行信息说明已正常启动
INFO org.eclipse.jetty.server.Server: Started @1203ms
INFO org.apache.calcite.avatica.server.HttpServer: Service listening on port 8765.
连接
[root@node01 ~]# sqlline-thin.py http://node01:8765
首次连接时会在 HBase 中自动生成 8 张表,如下:
SYSTEM.CATALOG
SYSTEM.CHILD_LINK
SYSTEM.FUNCTION
SYSTEM.LOG
SYSTEM.MUTEX
SYSTEM.SEQUENCE
SYSTEM.STATS
SYSTEM.TASK
退出
0: jdbc🐦🔥thin:url=http://node01:8765> !quit
环境搭建成功后删除安装包,有序退出各组件,然后 shutdown -h now
关机拍摄快照。