2018-06-14~2018-06-19
Apache Phoenix是构建在HBase之上的关系型数据库层,作为内嵌的客户端JDBC驱动用以对HBase中的数据进行低延迟访问。Apache Phoenix会将用户编写的sql查询编译为一系列的scan操作,最终产生通用的JDBC结果集返回给客户端。数据表的元数据存储在HBase的表中被会标记版本号,所以进行查询的时候会自动选择正确的schema。直接使用HBase的API,结合协处理器(coprocessor)和自定义的过滤器的话,小范围的查询在毫秒级响应,千万数据的话响应速度为秒级。
Phoenix Downloads
下载链接:
4.14.0-cdh5.14.2
解压到每个节点并执行:
# cp apache-phoenix-4.14.0-cdh5.14.2-bin/phoenix-4.14.0-cdh5.14.2-server.jar /opt/cloudera/parcels/CDH/jars/
# cd /opt/cloudera/parcels/CDH/lib/hbase/lib
# ln -s ../../../jars/phoenix-4.14.0-cdh5.14.2-server.jar phoenix-4.14.0-cdh5.14.2-server.jar
Phoenix支持两种索引:可变索引跟不可变索引。在可变表上建的索引是可变索引,在不可变表上建的索引是不可变索引。可变索引是指插入或删除数据的时候会同时更新索引;不可变索引适用于只写入一次不再更改的表,索引只建立一次,再插入数据不会更新索引。需要在 hbase-site.xml 中进行相关配置使其支持可变索引(不可变索引无需另外配置,默认支持)。
java.sql.SQLException: 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.
集群所有 RegionServer 的 hbase-site.xml 配置文件里面增加如下配置:
hbase.regionserver.wal.codec
org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec
支持可变索引
hbase.regionserver.executor.openregion.threads
100
Phoenix导致HBase集群region offline故障
hbase.rpc.timeout
6000000
hbase.client.operation.timeout
6000000
hbase.client.scanner.timeout.period
6000000
hbase.client.ipc.pool.type
RoundRobinPool
hbase.client.ipc.pool.size
10
hbase.regionserver.lease.period
6000000
phoenix.query.timeoutMs
6000000
phoenix.query.keepAliveMs
6000000
同时 apache-phoenix-4.14.0-cdh5.14.2-bin/bin/hbase-site.xml 加上以上内容。
使用 galaxy_loggers 准备测试文件如下(/tmp/stash/):
weblogger_2018061510.csv
weblogger-1_2018061510.csv
weblogger-1_2018061510.csv.1
weblogger-1_2018061510.csv.2
weblogger-1_2018061510.csv.3
weblogger-2_2018061510.csv
weblogger-2_2018061510.csv.1
weblogger-2_2018061510.csv.2
weblogger-2_2018061510.csv.3
...
文件是模拟生成的网站访问日志,每行 1 KB,文件 1GB=1000万行,1TB=100亿行:
rowid,timestr,timeint,dest.id,sour.ip,sour.port,dest.ip,dest.port,dest.url,proxy.ip,proxy.port,proxy.type,keywdid
上传单个文件 weblogger_2018061510.csv 到 hdfs 中(/phoenix/stash/ 目录下):
# sudo -u hdfs hdfs dfs -put /tmp/stash/weblogger_2018061510.csv /phoenix/stash/
或上传整个文件夹(将本地文件夹 /tmp/stash/ 下的全部文件复制到 hdfs://phoenix/stash/):
# sudo -u hdfs hdfs dfs -put /tmp/stash/ /phoenix/
进入 phoenix 命令行 ( 提示符 > ):
# bin/sqlline.py ha06.ztgame.com,ha07.ztgame.com,ha08.ztgame.com:2181
创建表 weblogger.testcsv:
> CREATE TABLE IF NOT EXISTS weblogger.testcsv (
rowid BIGINT NOT NULL,
timestr DATE,
timeint BIGINT,
dest.id BIGINT,
sour.ip VARCHAR(30),
sour.port VARCHAR(10),
dest.ip VARCHAR(30),
dest.port VARCHAR(10),
dest.url VARCHAR(255),
proxy.ip VARCHAR(30),
proxy.port VARCHAR(10),
proxy.type VARCHAR(10),
keywdid BIGINT,
CONSTRAINT PK PRIMARY KEY (rowid)
);
删除索引:
> drop index idx_testcsv_time on weblogger.testcsv;
创建第二索引(secondary index):
> create index idx_testcsv_time on weblogger.testcsv(timestr);
> create index idx_testcsv_ip on weblogger.testcsv(sour.ip, dest.ip);
> create index idx_testcsv_url on weblogger.testcsv(dest.url, keywdid);
因为是空表,所以立即创建索引。使用下面的命令查看,可以看到表和索引(也是表)已经创建好了:
> !tables
TABLE_SCHEM TABLE_NAME INDEX_STATE
-------------------------------------------------
WEBLOGGER IDX_TESTCSV_IP ACTIVE
WEBLOGGER IDX_TESTCSV_TIME ACTIVE
WEBLOGGER IDX_TESTCSV_URL ACTIVE
WEBLOGGER TESTCSV
注:phoenix 的所有表名字都必须大写!
先看看有哪些 hdfs 文件:
# sudo -u hdfs hdfs dfs -ls /phoenix/stash/
导入其中一个 hdfs:
# sudo -u hbase HADOOP_CLASSPATH=/opt/cloudera/parcels/CDH/jars/hbase-protocol-1.2.0-cdh5.14.2.jar \
hadoop jar ./phoenix-4.14.0-cdh5.14.2-client.jar \
org.apache.phoenix.mapreduce.CsvBulkLoadTool \
--zookeeper=ha06.ztgame.com,ha07.ztgame.com,ha08.ztgame.com:2181 \
--table=weblogger.testcsv \
--input=/phoenix/stash/weblogger_2018061510.csv
参考:Phoenix系列:二级索引
如果数据表中已经导入了大量数据,此时创建第二索引会很慢,因此提供了异步索引(ASYNC)。
> create index idx_testcsv_keyid on weblogger.testcsv(keywdid, dest.id) async;
执行了上句会立即返回,但其实并没有做任何事情,还需要退出 phoenix 命令回到系统提示符,调用 IndexTool 命令:
# hbase org.apache.phoenix.mapreduce.index.IndexTool --schema=WEBLOGGER --data-table=TESTCSV --index-table=IDX_TESTCSV_KEYID --output-path=/phoenix/ASYNC_INDEX_RES
这个任务不会因为客户端给关闭而结束,是在后台运行。你可以在指定的 hdfs 文件目录下(–output-path=ASYNC_INDEX_RES) 中找到最终的结果。执行结束显示:
... ...
18/06/15 18:25:08 INFO index.IndexTool: Loading HFiles from /phoenix/ASYNC_INDEX_RES/WEBLOGGER.IDX_TESTCSV_KEYID
18/06/15 18:25:08 WARN mapreduce.LoadIncrementalHFiles: Skipping non-directory hdfs://ha01.ztgame.com:8020/phoenix/ASYNC_INDEX_RES/WEBLOGGER.IDX_TESTCSV_KEYID/_SUCCESS
18/06/15 18:25:08 INFO hfile.CacheConfig: CacheConfig:disabled
18/06/15 18:25:08 INFO mapreduce.LoadIncrementalHFiles: Trying to load hfile=hdfs://ha01.ztgame.com:8020/phoenix/ASYNC_INDEX_RES/WEBLOGGER.IDX_TESTCSV_KEYID/0/19e68383bd2f444a81ad85ae459582e0 first=\xC1\x02\x00\xC3\x0B\x01&\x00\x80\x00\x00\xE8\xE3\xD2\xDF\x98 last=\xC2\x0A`\x00\xC3\x1205\x00\x80\x00\x00\xE8\xE3\xD6\x8Dg
18/06/15 18:25:08 INFO index.IndexToolUtil: Updated the status of the index IDX_TESTCSV_KEYID to ACTIVE
如果执行上面的任务发生异常(NoSuchMethodError),复制 phoenix-4.14.0-cdh5.14.2-client.jar 到:
/opt/cloudera/parcels/CDH/jars/
然后进入:
# cd /opt/cloudera/parcels/CDH/lib/hbase/lib/
创建链接:
# ln -s ../../../jars/phoenix-4.14.0-cdh5.14.2-client.jar phoenix-4.14.0-cdh5.14.2-client.jar
再执行 IndexTool 命令。
将 hdfs://phoenix/stash/ 下的所有文件一并导入到 hbase 表中。总 hdfs 文件 300GB。
新建一个 weblogger.stashcsv 表:
# CREATE TABLE IF NOT EXISTS weblogger.stashcsv (
rowid BIGINT NOT NULL,
timestr DATE,
timeint BIGINT,
dest.id BIGINT,
sour.ip VARCHAR(30),
sour.port VARCHAR(10),
dest.ip VARCHAR(30),
dest.port VARCHAR(10),
dest.url VARCHAR(255),
proxy.ip VARCHAR(30),
proxy.port VARCHAR(10),
proxy.type VARCHAR(10),
keywdid BIGINT,
CONSTRAINT PK PRIMARY KEY (rowid)
);
然后将 hdfs://phoenix/stash/ 下的所有文件一并导入到表中 Loading via MapReduce:
# sudo -u hbase HADOOP_CLASSPATH=$(hbase mapredcp):/path/to/hbase/conf \
hadoop jar ./phoenix-4.14.0-cdh5.14.2-client.jar \
org.apache.phoenix.mapreduce.CsvBulkLoadTool \
--zookeeper=ha06.ztgame.com,ha07.ztgame.com,ha08.ztgame.com:2181 \
--table=weblogger.stashcsv --input=/phoenix/stash/*
或者
# sudo -u hbase HADOOP_CLASSPATH=/path/to/hbase-protocol.jar:/path/to/hbase/conf \
hadoop jar ./phoenix-4.14.0-cdh5.14.2-client.jar \
org.apache.phoenix.mapreduce.CsvBulkLoadTool \
--zookeeper=ha06.ztgame.com,ha07.ztgame.com,ha08.ztgame.com:2181 \
--table=weblogger.stashcsv --input=/phoenix/stash/*
实际操作命令:
# sudo -u hbase HADOOP_CLASSPATH=$(hbase mapredcp) hadoop jar ./phoenix-4.14.0-cdh5.14.2-client.jar org.apache.phoenix.mapreduce.CsvBulkLoadTool --zookeeper=ha06.ztgame.com,ha07.ztgame.com,ha08.ztgame.com:2181 --table=weblogger.stashcsv --input=/phoenix/stash/*
本例 300 GB 文件运行 2.5 个小时显示失败异常:
Exception in thread “main” java.io.IOException: Trying to load more than 32 hfiles to one family of one region
更改每个节点的配置文件 hbase-site.xml (cdh manager 可以直接修改配置项) 如下:
hbase.hstore.blockingStoreFiles
200
当某一个region的storefile个数达到该值则block写入,等待compact. 7 default
hbase.hregion.max.filesize
107374182400
单个ColumnFamily的region大小,若按照ConstantSizeRegionSplitPolicy策略,超过设置的该值则自动split. 10 GB default
再次运行:
# sudo -u hbase HADOOP_CLASSPATH=$(hbase mapredcp) hadoop jar ./phoenix-4.14.0-cdh5.14.2-client.jar org.apache.phoenix.mapreduce.CsvBulkLoadTool -Dhbase.mapreduce.bulkload.max.hfiles.perRegion.perFamily=500 --zookeeper=ha06.ztgame.com,ha07.ztgame.com,ha08.ztgame.com:2181 --table=weblogger.stashcsv --input=/phoenix/stash/*
其中 hbase.mapreduce.bulkload.max.hfiles.perRegion.perFamily 表示:
在bulkload过程中,每个region列族的HFile数的上限. 默认是 32.
导入成功。300GB hdfs 文件。耗时约 6 hours。
# bin/sqlline.py ha06.ztgame.com,ha07.ztgame.com,ha08.ztgame.com:2181
Done
sqlline version 1.2.0
0: jdbc:phoenix:ha06.ztgame.com,ha07.ztgame.c>
> drop index IDX_STASHCSV_ALL on WEBLOGGER.STASHCSV;
> create index idx_stashcsv_all on weblogger.stashcsv(timestr,sour.ip,dest.ip,dest.url) async;
执行了上句会立即返回,但其实并没有做任何事情,还需要退出 phoenix 命令回到系统提示符,调用 IndexTool 命令:
# hbase org.apache.phoenix.mapreduce.index.IndexTool \
--schema=WEBLOGGER \
--data-table=STASHCSV \
--index-table=IDX_STASHCSV_ALL \
--output-path=/phoenix/ASYNC_INDEX_RES
索引创建成功,耗时很长,可能和 hbase 配置有关(待查)。
> UPDATE STATISTICS weblogger.stashcsv;
No rows affected (3.705 seconds)
> select count(1) from weblogger.stashcsv;
+-------------+
| COUNT(1) |
+-------------+
| 2315915000 |
+-------------+
1 row selected (148.85 seconds)
> explain select rowid,timestr,sour.ip from weblogger.stashcsv where timestr=to_date('2012-01-01 00:00:24') and sour.ip='1.108.167.37';
ROUND ROBIN RANGE SCAN OVER WEBLOGGER.IDX_STASHCSV_ALL
RANGE SCAN 说明使用了索引。
> select rowid,timestr,sour.ip from weblogger.stashcsv where timestr >= to_date('2012-01-01 00:00:00') and timestr < to_date('2013-01-01 00:00:00') and sour.ip='1.108.167.37';
大量的查询返回慢是因为结果集庞大。查询之前首先 explain select … 以获得是否使用索引的信息,否则会全部扫描。
是否每个字段单独建索引而不是联合索引?需要测试。