Phoenix 5.1.3

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

MyraZ
Phoenix 5.1.3

关于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 产品完全集成。

image-20250320151744263

架构

image-20250320151826506

重客户端

​ Phoenix 是 HBase 之上的 SQL 层,它为 HBase 赋予了 NewSQL 的特性,支持了大多数的标准 SQL 特性,并提供了JDBC 的访问接口,使得我们在应用程序中能够方便的集成使用。其架构如图:

image-20250320151904511

​ 从其架构来看,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 版本),如图所示:

image-20250320152044239

​ 轻客户端架构将 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 上。

image-20250320152205352

总结

​ Phoenix 轻客户端使业务端应用程序更加轻薄,业务开发人员无需再花费精力在底层 Phoenix 升级及运维,更加专注于业务本身,同时提供给非 Java 开发人员使用 Phoenix 的一种途径。 ​ 相比较重客户端,轻客户端实现增加了 RPC 请求链路,在性能上略有降低。

性能

image-20250320152307790

特性

  • 表抽样。通过实现一个过滤器来支持 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 连接语法(有一些限制),可以基于具有公共值的字段来组合来自两个或多个表的记录。

    • 多对多连接。双方都太大而无法容纳内存的支撑连接。3.3 版本。
    • 优化外键联接。利用我们的跳过扫描过滤器优化外键联接。3.2 版本。
    • 半/反联接。通过标准[NOT] IN和[NOT] EXISTS关键字支持半/反子查询。3.2 版本。
  • 子查询。支持 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/

​ 重客户端:

image-20250320154713499

​ 轻客户端:

image-20250320154742622

安装

目标环境

image-20250320154827988

重客户端安装

​ 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 非关系型形式转换成关系型数据模型 ,如下图所示。

image-20250320155602275

​ 对于 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 已经支持关系型数据库的大部分语法,如下图所示:

image-20250320155912764

​ 具体使用可参考 Phoenix 官网

注意:Phoenix 对于库名(NameSpace),表名,列族名,列名是区分大小写的,默认都会转为成大写。如果要屏蔽转换,需要在对应的字符上使用双引号,例如 "test" 。数据类型是字符串的话,要用单引号'包含。

查看所有表

!tables

image-20250320160310530

创建 Schema(数据库)

CREATE SCHEMA school;
CREATE SCHEMA "school";

​ 注意大小写问题,HBase 视角如下:

image-20250320160332622

创建表

在 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 视角:

image-20250320160457225

​ HBase 视角如下:

image-20250320160508519

插入记录

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 视角:

image-20250320160559736

image-20250320160621835

​ HBase 视角如下:

image-20250320160654142

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 形成前缀过滤器。

查看索引

image-20250320162103520

image-20250320162114282

​ 索引表也是表(在 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

创建项目

image-20250320164044004

​ 完整 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 关机拍摄快照。

© 2024 - 2025 雪中烛

 

在线工具资源网站

在线工具官网

ProcessOn:👉ProcessOn在线作图

腾讯文档:👉腾讯文档

FlowUs 息流:👉FlowUs 息流

Mermaid Live Editor:👉在线流程图和图表编辑器 - 美人鱼实时编辑器

ME2在线工具(加密工具):👉ME2在线工具-在线开发学习生活工具箱

Textln:👉Textln文本智能云平台-通用文本解析

MinerU:👉MinerU数据提取工具

Crontab:👉Cron Expression Examples - Crontab.guru

正则表达式测试网站:👉regex101: build, test, and debug regex

技术资源网站

技术资源官网

Spring:👉Spring官网

MyBatis中文网 :👉MyBatis 中文网

Redis中文网:👉Redis中文网

Zookeeper:👉Apache ZooKeeper官网

Raft算法:👉Raft Consensus Algorithm官网

Hadoop:👉Apache Hadoop官网

Hive:👉Apache Hive官网

Phoenix:👉Apache Phoenix官网

Bootstrap中文网:👉Bootstrap中文网

Element:👉组件 | Element

Layui :👉Layui - 极简模块化前端 UI 组件库(官方文档)

FreeMarker :👉FreeMarker 中文官方参考手册

ztree树插件: 👉zTree – jQuery 树插件官网

x File Storage:👉x File Storage官网