Apache Phoenix为访问HBase提供了支持事务特性(ACID)的标准SQL和JDBC应用程序接口,同时又因以HBase作为数据存储,具备了NoSQL架构下的延迟绑定、读时模式等功能所带来的灵活性。 对于使用Hadoop系统且对延迟有要求的OLTP和业务分析等需求,Phoenix可以很好地满足。除了单纯支持HBase,它也整合到了Hadoop生态圈的其他产品,如Spark,Hive和MapReduce等。
在项目中使用Phoenix可以显著减少HBase的开发成本,程序员不需要再使用晦涩难懂的HBase原生Put/Get等操作来读写数据,而直接使用简单易懂的JDBC/SQL等方式来操作HBase表,也方便了日后的代码更新和维护;同样使用数据仓库/集市的数据分析师,也可以继续使用常用的SQL工具来快捷地进行业务数据分析,其在降低HBase使用门槛的同时又极大提高了生产力。 这里并不具体介绍Phoenix的SQL,而是针对在其使用过程中,对常用的工具和用法进行若干总结,让后来人少走弯路。
0. 获取Phoenix
若需要研究或更改Phoenix源码,可以下载源码包;若只是使用其客户端,直接下载二进制包即可。 下载地址: https://mirrors.tuna.tsinghua.edu.cn/apache/phoenix/
Phoenix是针对特定的HBase版本进行发布的。在下载的包名中,有Phoenix自己的版本号,以及对应的HBase的版本号,选择与要使用的HBase集群版本对应的Phoenix版本号即可(若对HBase版本不了解,可咨询运维人员)。但若官方未提供对应的HBase版本,则需要根据Phoenix下载源码来进行重新编译生成发行包。譬如业务使用的HBase集群是1.2.x的,那么用户需要下载的发行包应该是apache-phoenix-4.11.0-HBase-1.2-[bin/src].tar.gz。 正确下载了客户端包后,接下来进行客户端的配置操作。
1. 客户端环境配置
1.1 设置HADOOP_CONF_DIR
Phoenix需要读取HBase集群所使用的HDFS环境。若服务器上正好有这个HDFS环境的Hadoop的客户端,则可直接指定为$HADOOP_HOME/etc/hadoop目录。该目录下会有core-site.xml、hdfs-site.xml这2个访问HDFS需要的配置文件。若没有对应的客户端,则需要新建这2个配置文件,配置所需的参数。
1.2 设置HBASE_CONF_DIR
Phoenix需要配置相关参数才能访问HBase集群。如客户端服务器上正好有HBase的客户端,则可直接指定为$HBASE_HOME/conf目录。该目录下一般有hbase-site.xml文件,配置了访问HBase服务相关参数。如果客户端服务器上没有部署HBase、Hadoop等客户端,则需要单独配置hbase-site.xml文件,添加HBase以及HDFS的相关配置项即可。
1.3 设置LD_LIBRARY_PATH
如果HBase开启了Lzo/Snappy等压缩方式,需要指定客户端上的LD_LIBRARY_PATH环境变量来加载本地库,否则会出现数据读写异常。
1.4 使用Phoenix的客户端Jar包
若用户需要使用Phoenix的JDBC方式来访问HBase,那么只需要phoenix-4.x-HBase-1.x-client.jar这一个Jar包就够了。这个Jar包就在Phoenix的发行包下,将其添加到你的Java应用程序的ClassPath即可,访问HBase所需要的其他类都已经被包含在其中。需要注意的是,若直接使用hadoop jar方式来调用,在Hadoop版本不一致的情况下,可能会存在某些jar包版本冲突的风险。这时可以使用java直接来调用。
1.5 配置文件示例
core-site.xml
<configuration>
<!-- Secure HDFS-->
<property>
<name>hadoop.security.authentication</name>
<value>kerberos</value>
</property>
<property>
<name>hadoop.security.authorization</name>
<value>true</value>
</property>
</configuration>
hbase-site.xml
<configuration>
<property>
<name>hbase.rootdir</name>
<value>hdfs://mycluster/hbase</value>
</property>
<property>
<name>hbase.zookeeper.quorum</name>
<value>zk1,zk2,zk3</value>
</property>
<property>
<name>zookeeper.znode.parent</name>
<value>/znode</value>
</property>
<property>
<name>hbase.rpc.protection</name>
<value>privacy</value>
</property>
<!-- Secure HBase -->
<property>
<name>hbase.security.authentication</name>
<value>kerberos</value>
</property>
<property>
<name>hbase.security.authorization</name>
<value>true</value>
</property>
<property>
<name>hbase.regionserver.kerberos.principal</name>
<value>hbase/_HOST@HADOOP.HZ.NETEASE.COM</value>
</property>
<property>
<name>hbase.master.kerberos.principal</name>
<value>hbase/_HOST@HADOOP.HZ.NETEASE.COM</value>
</property>
<!-- -->
<property>
<name>phoenix.schema.isNamespaceMappingEnabled</name>
<value>true</value>
</property>
</configuration>
在环境变量配置完毕后,就可以使用客户端自带的命令行工具来连接HBase服务了。
2. Sqlline-交互式REPL
sqlline工具提供了Phoenix的交互式查询界面,该命令位于Phoenix的bin目录下。
./sqlline.py [<zookeeper quorum> [ :<port number> [ :<root node> ]]]
对于开启了Kerberos认证的HBase集群,需要先通过kinit命令进行认证。若本地配置文件已经配置好了相关参数,可以省略zk连接参数,程序将会自动从配置文件中获取Zookeeper集群的相关参数来连接。
Phoenix所支持的SQL语法可以查看其说明网页。http://phoenix.apache.org/language/index.html
3. Psql工具
psql提供了若干直接操作HBase的功能集。譬如批量执行DDL,执行数据导入。 该命令位于Phoenix的bin目录下
usage: psql [-t table-name] [-h comma-separated-column-names | in-line]
[-d field-delimiter-char quote-char escape-char]<zookeeper>
<path-to-sql-or-csv-file>...
-a,--array-separator <arg> 定义数组分隔符,默认是":"
-d,--delimiter <arg> CSV加载器的字段分隔符,默认是','。如果指定数字,那么代表控制字符,比如1代表CTRL+A('\001'),2代表CTRL+B('\002')依次类推
-e,--escape-character <arg> CSV加载器所用的转移字符,默认是'\'
-h,--header <arg> 覆盖默认映射的列名,并且是大小写敏感的。通常来说CSV文件的第一行决定字段名的映射关系
-l,--local-index-upgrade 升级本地索引,将其从单独的表迁移到同一个表的不同列族中
-m,--map-namespace <arg> 将表映射到与schema匹配的namespace,需要开启phoenix.schema.isNamespaceMappingEnabled
-q,--quote-character <arg> CSV加载器的引号,如果是数字,则认为是控制字符
-s,--strict 严格模式,当某条记录处理异常时中止导入
-t,--table <arg> 指定导入的Table名称,默认是csv文件名,大小写敏感
示例如下:
i) 使用默认的zk配置,直接执行DDL语句
psql my_ddl.sql
ii) 连接到远程的zk集群,执行my_ddl.sql中的语句
psql zkpath my_ddl.sql
iii) 连接到远程的zk集群,先执行my_ddl.sql中的语句,再导入my_table.csv中的数据到my_table表中
psql zkpath my_ddl.sql my_table.csv
iv) 连接到远程的zk集群上,将my_table2012-Q3.csv中的数据加载到MY_TABLE中,字段分隔符是","
psql -t MY_TABLE my_cluster:1825 my_table2012-Q3.csv
v) 将my_table2012-Q3.csv中的数据加载到MY_TABLE中,字段分隔符是":",只更新COL1,COL2,COL3三列
psql -t MY_TABLE -h COL1,COL2,COL3 -d : my_cluster:1825 my_table2012-Q3.csv
使用psql进行小数量的csv文件加载时,文件的后缀名必须为csv.
4. 批量导入
少量的数据导入(百兆字节以下)可以使用上述介绍PSQL工具来执行(要么通过csv导入,要么转换成DML语句),但是对于大批量数据的数据导入(百兆字节以上),这种方式性能会很差。所以Phoenix提供了相应的批量导入工具——使用MapReduce将数据文件(存储在HDFS上)转换成相应表的HFile存储格式,然后再通过HBase的BulkLoad方式将这些HFile批量加载到表的HStore中。关于HBase的BulkLoad导入表的说明可以参考之前的一篇文章《HBase数据迁移实战》http://ks.netease.com/blog?id=8050
在使用这种方式进行批量导入时,需要考虑到权限因素。因为程序在最后使用BulkLoad将生成的HFile导入到HBase所在的HDFS目录时,如果用户没有权限读写/hbase/data目录,是无法成功导入的。所以这种操作一般需要切换成hbase账号来进行(hbase账号为HBase的超级账号)。官方文档上给出的另一个做法是执行批量导入的MapReduce任务时将HDFS系统的umask设置为000(使用-Dfs.permissions.umask-mode=000),这样生成的数据文件对所有的用户都可读写(但实际上对于Secure HBase不生效)。另外使用这种方式进行批量导入时需要在HBase所在的集群上开启YARN服务,虽然通过其他集群的YARN服务可以跑MR任务生成临时的HFile文件,但后续需要将这些HFile批量加载到HBase时,会抛出路径合法性的异常。即必须是在本集群上YARN上才能完成这个操作。
4.1 CsvBulkLoadTool
本工具类用于将CSV格式的文件批量导入至HBase表,相关参数如下:
usage: help
-a,--array-delimiter <arg> 数组列分隔符 (可选)
-c,--import-columns <arg> 指定要导入的列名
-d,--delimiter <arg> 字段分隔符,默认是逗号
-e,--escape <arg> 指定转义符,默认是反引号
-g,--ignore-errors 忽略错误
-i,--input <arg> 输入路径(逗号分隔,必选)
-it,--index-table <arg> 需要同时加载的索引表名
-o,--output <arg> HFiles的临时输出路径 (可选)
-q,--quote <arg> 定义分词符, 默认是双引号
-s,--schema <arg> Phoenix库名(可选)
-t,--table <arg> Phoenix表名 (必选)
-z,--zookeeper <arg> zk连接字符串(可选)
4.2 JsonBulkLoadTool
本工具类用于将json格式的文件批量导入到HBase表,相关参数如下:
usage: help
-c,--import-columns <arg> 指定要导入的列名
-g,--ignore-errors 忽略错误
-i,--input <arg> 输入路径(逗号分隔,必选)
-it,--index-table <arg> 需要同时加载的索引表名
-o,--output <arg> HFiles的临时输出路径 (可选)
-s,--schema <arg> Phoenix库名(可选)
-t,--table <arg> Phoenix表名 (必选)
-z,--zookeeper <arg> zk连接字符串(可选)
使用JsonBulkLoadTool是有几点需要注意:
1. Json字符串中的各属性名是大小写敏感的,而HBase表的字段是大小写不敏感的,且字段映射采用小写名称。故Json字符串属性名最好都使用小写,以免出现无法对应上的问题。
2. Json字符串中数字只有整型和浮点型,在序列化成Java对象时会统一转成Integer或者Float对象。若在表字段定义成非对应类型时,可能会出现类型转换异常。
故使用这种方式导入时,无法解决复杂的类型转换问题。此种情况下需要自己定义JsonToKeyValueMapper类,将Object对象显示进行转换。
如用户有如下定义的表:
create table t1(
col1 BIGINT,
col2 BIGINT,
col3 UNSIGNED_INT
)
需要导入的Json字符串如下:
{"col1":1234, "col2":1, "col3": 134}
那么程序会直接抛出类型转换异常,因为java.lang.Integer对象无法直接转换成java.lang.Long对象,这时需要进行相应的改动。
下面是一个使用示例:
export HADOOP_CLASSPATH=/home/jinchuan/hbase-1.2.4/conf:$HADOOP_CLASSPATH
hadoop jar phoenix-4.8.0-HBase-1.2-client.jar org.apache.phoenix.mapreduce.JsonBulkLoadTool --table TEST.YARN_METRIC_RECORD --input hdfs://mycluster1/user/hbase/yarn_usage --output hdfs://mycluster1/user/hbase/jinchuan/test4 --zookeeper zk1,zk2,zk3:2181:/hbase
4.3 定制BulkLoad工具类
在实际的数据分析业务中,用户还会遇到很多其他格式的数据文件,譬如Nginx日志或者APP日志等。业务方可以针对这些不同的数据格式实现不同的BulkLoad类将其导入至HBase。
具体的实现可以参考CsvBulkLoadTool或JsonBulkLoadTool这2个工具类。
5. JDBC
5.1 使用JDBC连接字符串访问HBase
在Phoenix的帮助下,用户可以通过其封装的JDBC接口来访问HBase,从而避免调用HBase生硬和冗繁的底层API。其连接数据库的方式以及使用方法和其他数据库JDBC API一致,连接字符串参考如下格式:
jdbc:phoenix [:<zookeeper quorum>[:<port number>[:<root node>[:<principal>[:<keytab file>]]]]]
如果HBase使用Kerberos认证,则需要在连接字符串中添加keytab文件和principle信息,如下:
jdbc:phoenix:zk1,zk2,zk3:2181:/znode:test/dev:test.keytab
具体的代码示例
String phoenixURL="jdbc:phoenix:zk1,zk2,zk3:2181:/znode:test/dev:/home/jinchuan/test.keytab";
Connection conn = DriverManager.getConnection(phoenixURL).unwrap(PhoenixConnection.class);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select count(1) from TEST.MYTABLE");
while(rs.next()){
//TODO
}
rs.close();
stmt.close();
conn.close();
因为Phoenix连接HBase时需要加载到相应的配置文件,故需要在应用程序的CLASSPATH中添加配置文件(如core-site.xml,hbase-site.xml)所在路径;另外如果在创建表时使用了LZO/SNAPPY压缩等,还需要设置LD_LIBRARY_PATH环境变量加载Hadoop相关的本地编码库。下面是一个简单加载phoenix jar包的示例:
# conf目录下有相关的配置文件,如core-site.xml,hbase-site.xml等
# lib目录下面有应用程序用到的jar包,包括phoenix-client.jar等
export HBASE_CONF_DIR=./conf
export LD_LIBRARY_PATH=/lib/native
java -cp lib/*:. [JavaClass]
也可以参考官网的示例: https://phoenix.apache.org/faq.html
5.2 Phoenix连接池
对于常见的关系型数据库来说,一般都具备连接池功能。数据库系统启动时预先生成一批数据库连接对象放到连接池中,然后应用程序从连接池中获取可用连接,使用完毕后再次归还到连接池中,实现重用。因为创建数据库连接是一个昂贵的操作,对于需要频繁连接数据库的业务来说,连接池可以显著提高数据库访问效率。但是Phoenix的JDBC连接并不支持连接池功能:首要原因是作为父类对象的HBaseConnection没有相关实现,再则重用连接会导致HBaseConnection对象停留在上一次使用后的非正常状态,影响下一次使用,最后创建PhoenixConnection对象的操作相对于传统数据库来说代价很小,所以业务不需要额外为Phoenix来实现连接池功能。
但对于某些应用程序,使用了统一的数据库连接池访问框架(譬如Apache DBCP)来管理所有数据库(包括NoSQL数据库)访问——它们通过统一的DataSource对象来获取数据库连接。这时为了兼容整体的业务逻辑,就有必要实现Phoenix的Pool功能。实现Phoneix的连接池功能也比较简单,通过创建一个代理的数据库连接对象创建和关闭链接来模拟数据库连接池的获取和归还连接即可。社区已有人贡献了相关代码(PHOENIX-2388),但尚未被接受,有需要的同学可以参考下。
以上几点大概就是业务方在使用Phoenix时可能会遇到的相关场景,若各位在使用过程中遇到其他问题,希望可以一起学习探讨。
本文来自网易实践者社区,经作者金川授权发布。