1. 如何映射一个Phoenix的表到一个Hbase的表?
你可以通过Create table/create view DDL语句在一个已经存在的hbase表上创建一个Phoenix表或者视图。对于Createtable来说,我们将创建任何元数据(表,列族),这些之前都是不存在的。我们也将对于每行记录添加一个空的key值,以便查询时按照我们的方式(不需要在scan过程中对所有的列进行投影)。
另外需要注意的是,这些序列化的字节的方式必须匹配在Phoenix中序列化字节的方式。对于Varchar,Char和Unsigned_*类型,我们使用HBase字节的方法。Char字节仅仅为单个字节,Unsigned类型的值大于或等于0。
如果创建一个HBase的表,如下:
create 't1', {NAME => 'f1', VERSIONS=> 5}
你有一个HBase的表,表名为t1,列族为f1。请记住,在HBase里面,你不能模仿这KeyValues 或者行键的结构,你在Phoenix中指定的信息是建立在表和列族之上的。
因此在Phoenix中,你可以创建一个对应的视图,如下:
CREATE VIEW "t1" ( pk VARCHAR PRIMARY KEY, "f1".val VARCHAR );
pk列定义了你的行键的类型为VARCHAR类型,"f1".val列定义了你的HBase表包含KeyValues ,包含一个列族和列修饰符"f1".val,并且他们的值是Varchar类型的。
如果你创建HBase表时,都使用大写名称,如下:
create 'T1', {NAME => 'F1', VERSIONS=> 5}
你可以创建对应的Phoenix视图,可以不需要双引符号:
CREATE VIEW t1 ( pk VARCHAR PRIMARY KEY,f1.val VARCHAR )
或者你创建一个新的HBase表,仅仅让Phoenix为你如下的一切事情(根本不需要使用HBase shell):
CREATE TABLE t1 ( pk VARCHAR PRIMARY KEY,val VARCHAR )
这里补充一点Family Column:
如果在Phoenix SQL中创建table时为qualifier指定了family,如下:
CREATE TABLE "example" (my_pk bigint notnull, m1.col varchar(50), m2.col varchar(50), m3.col varchar(50) constraint pk primary key (my_pk)) ;
Phoenix primary key 与HBase rowkey 的关系:
在创建phoenix table时,必须指定一个primary key,但是这个主键我们不一定用到。如果我们想在用Phoenix创建table时,让Phoniex的主键自动地与HBase的rowkey对应起来,则可以用以下方式来创建:
create table"{空间名:表名}" ("pk"varchar primary key, "{列1}" varchar, "{列2}" varchar, "{列3}" varchar) salt_buckets=10;
这样,Phoniex的主键(名为pk
)就自动地与HBase的rowkey对应起来了。
注:
(1)自己定义的HBase中的 HTableName,ColumnFamily,以及Column,需要和Phoenix中保持一致。(最好都用大写)
(2)Phoenix操作HBase,我们有两种方式,创建表和创建视图。
这两种方式的区别如下:
创建表的话,可读可写,就可以对HBase进行插入,查询,删除操作。
创建视图的话,是只读的,一般就只可以进行查询操作
删除Phoenix中表时,会将HBase对应的表删掉。但是删除Phoenix视图操作,却不会影响HBase原始表的结构。
因为使用Phoenix,创建表后,会自动和HBase建立关联映射。当你使用Phoenix删除和HBase之间的关系时,就会将HBase中的表也删掉了。所以用视图,会对原始的HBase表影响小一些。
2. 优化Phoenix有哪些提示?
2.1 使用Salting方式提升读写性能
Salting能够通过预分区(pre-splitting)数据到多个region中来显著提升读写性能。
Salting 翻译成中文是加盐的意思,本质是在hbase中,rowkey的byte数组的第一个字节位置设定一个系统生成的byte值,这个byte值是由主键生成rowkey的byte数组做一个哈希算法,计算得来的。Salting之后可以把数据分布到不同的region上,这样有利于phoenix并发的读写操作。
CREATE TABLE TEST (HOST VARCHAR NOT NULL PRIMARY KEY, DESCRIPTION VARCHAR) SALT_BUCKETS=16;
注:
(1)理性情况下,对于拥有四核CPUs的16个RegionServer的HBase集群来说,选择salt buckets在32-64个以获取较好的性能。Salt bucket的个数必须在1-256之间。
(2)这里说明一下Salted Tables
为了避免读写HBase表数据时产生hot-spot问题,我们使用Phoenix来创建表时可以采用salted table。
salted table可以自动在每一个rowkey前面加上一个字节,这样对于一段连续的rowkeys,它们在表中实际存储时,就被自动地分布到不同的region中去了。当指定要读写该段区间内的数据时,也就避免了读写操作都集中在同一个region上。
简而言之,如果我们用Phoenix创建了一个saltedtable,那么向该表中写入数据时,原始的rowkey的前面会被自动地加上一个byte(不同的rowkey会被分配不同的byte),使得连续的rowkeys也能被均匀地分布到多个regions。
创建salted table:
CREATE TABLE "test:salted" (pk VARCHAR PRIMARY KEY, c1 VARCHAR, c2 VARCHAR, c3 VARCHAR) SALT_BUCKETS=5;
上述语句创建了一个名为"test:salted"的table(HBase中事先要创建名为test的namespace),SALT_BUCKETS=5
说明该salted table由5个bucket组成(必须在1~256之间)。
上面在创建table时,没有指定family,只指定了qualifier(c1, c2, c3),因此在HBase shell向表salted写入数据时,column name 要写成'0:[qualifier]'(如'0:c1'),否则HBase会报错。
另外,创建salted table需要注意两点:
A. 创建salted table后,应该使用Phoenix SQL来读写数据,而不要混合使用Phoenix SQL和HBase API
B. 如果通过Phoenix创建了一个salted table,那么只有通过Phoenix SQL插入数据才能使得被插入的原始rowkey前面被自动加上一个byte,通过HBase shell插入数据无法prefix原始的rowkey
2.2 Pre-split
Salting能够自动的设置表预分区,但是你得去控制表是如何分区的,所以在建phoenix表时,可以精确的指定要根据什么值来做预分区,比如:
CREATE TABLE TEST (HOST VARCHAR NOT NULL PRIMARY KEY, DESCRIPTION VARCHAR) SPLIT ON ('CS','EU','NA');
2.3 使用多列族
列族包含相关的数据都在独立的文件中,在Phoenix设置多个列族可以提高查询性能。例如:
CREATE TABLE TEST (MYKEY VARCHAR NOT NULL PRIMARY KEY, A.COL1 VARCHAR,A.COL2 VARCHAR, B.COL3 VARCHAR);
2.4 使用压缩
在数据量大的表上使用压缩算法来提高性能。
例如:
CREATE TABLE TEST (HOST VARCHAR NOT NULL PRIMARY KEY, DESCRIPTION VARCHAR) COMPRESSION='GZ';
2.5 创建二级索引
下面将会介绍。
2.6 优化HBase集群参数
请参考HBase官方详细介绍:http://hbase.apache.org/book.html#performance
2.7 优化Phoenix参数
请参考官网介绍:http://phoenix.apache.org/tuning.html
3. 如何在表上创建二级索引?
从Phoenix 2.1版本开始,Phoenix支持可变和不可变(数据插入后不再更新)数据建立二级索引。Phoenix 2.0版本仅支持在不可变数据建立二级索引。
示例:
3.1 创建表
不可变表:create table test (mykey varchar primary key, col1 varchar,col2 varchar) IMMUTABLE_ROWS=true;
可变表:create table test (mykey varchar primary key, col1 varchar,col2 varchar);
3.2 在col2创建索引
create index idx on test (col2);
3.3 在col1和一个覆盖列col2上创建索引
create index idx on test (col1) include (col2);
在test表中upsert数据,Phoenix查询优化器将选择使用合适的索引。你可以使用explain plan进行查看(http://phoenix.apache.org/language/index.html#explain)。当然你也可以使用hint(http://phoenix.apache.org/language/index.html#hint)方式指定一个需要使用的索引。
4. 为什么我的二级索引没有被使用?
除非所有查询使用的列被索引或者覆盖列,否则二级索引不会被使用。所有列组成数据表的主键将被包含在索引中。
例子:
创建表:
create table usertable (id varchar primary key,firstname varchar, lastname varchar);
create index idx_name on usertable (firstname);
这里创建二级索引时报错了如下:
Java.sql.SQLException: ERROR 1029 (42Y88):Mutable secondary indexes must have the hbase.regionserver.wal.codec propertyset to org.apache.Hadoop.hbase.regionserver.wal.IndexedWALEditCodec in thehbase-sites.xml of every region server. tableName=IDX
根据错误日志,需要在RegionServer每个节点的hbase-site.xml中配置支持可变的二级索引的配置项:
<property>
<name>hbase.regionserver.wal.codec</name>
<value> org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>
</property>
<property>
<name>hbase.region.server.rpc.scheduler.factory.class</name>
<value>org.apache.hadoop.hbase.ipc.PhoenixRpcSchedulerFactory</value>
<description>Factory to create the Phoenix RPC Scheduler that usesseparate queues for index and metadata updates</description>
</property>
<property>
<name>hbase.rpc.controllerfactory.class</name>
<value>org.apache.hadoop.hbase.ipc.controller.ServerRpcControllerFactory</value>
<description>Factory to create the Phoenix RPCScheduler that uses separate queues for index and metadataupdates</description>
</property>
<property>
<name>hbase.coprocessor.regionserver.classes</name>
<value>org.apache.hadoop.hbase.regionserver.LocalIndexMerger</value>
</property>
在每一个Master的hbase-site.xml中加入如下的属性:
<property>
<name>hbase.master.loadbalancer.class</name>
<value>org.apache.phoenix.hbase.index.balancer.IndexLoadBalancer</value>
</property>
<property>
<name>hbase.coprocessor.master.classes</name>
<value>org.apache.phoenix.hbase.index.master.IndexMasterObserver</value>
</property>
查询:
select id, firstname, lastname from usertablewhere firstname = 'foo';
请注意这个查询不会用到索引,因为lastname不是被索引或者覆盖列的部分。我们可以查询执行计划进行验证,比如:
0:jdbc:phoenix:SZB-L0023780:2181:/hbase114> explain select id, firstname,lastname from usertable where firstname = 'foo';
+------------------------------------------------------------------------------------------------------------------+
| PLAN |
+------------------------------------------------------------------------------------------------------------------+
| CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBINFULL SCAN OVER USERTABLE |
| SERVER FILTER BY FIRSTNAME = 'foo' |
+------------------------------------------------------------------------------------------------------------------+
为了修改这个创建索引的问题,要么对lastname创建索引,要么创建覆盖索引,如下:
drop index idx_name on usertable;
create index idx_name on usertable (firstname) include(lastname);
我们再次验证一下
0: jdbc:phoenix:SZB-L0023780:2181:/hbase114>explain select id, firstname, lastname from usertable where firstname = 'foo';
+-------------------------------------------------------------------------------------------------------------------------+
| PLAN |
+-------------------------------------------------------------------------------------------------------------------------+
| CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBINRANGE SCAN OVER IDX_NAME ['foo'] |
+------------------------------------------------------------------------------------------------------------------------+
根据执行计划的结果,可以看出使用了IDX_NAME索引。
5. Phoenix有多快,为什么如此之快?
全表扫描100M的数据量通过在20s完成(中等大小集群的窄表)。如果查询时增加一些索引列过滤的话,时间会减低到毫秒级别。
为什么Phoenix全表扫描还那么快?
(1)Phoenix使用region边界拆分查询,并且客户端使用配置的线程数进行并行查询。
(2)聚合操作将在服务端的协处理器中完成
6. 为什么我的查询不走Range scan?
首先看一下创建表的语句:
CREATE TABLE TEST (pk1 char(1) not null, pk2char(1) not null, pk3 char(1) not null, non_pk varchar CONSTRAINT PK PRIMARY KEY(pk1, pk2, pk3));
Range Scan意味着仅会扫描表的一部分子集的行记录。如果你使用主键约束中的首个列开头的话,就会使用Range Scan方式。
比如下面的查询将会使用Full Scan方式:
select * from test where pk2='x' and pk3='y';
而下面的查询将会使用Range Scan方式:
select * from test where pk1='x' and pk2='y';
DEGENERATE SCAN意味着一个查询不可能返回任何行记录。
FULL SCAN意味着将扫描表的所有行记录。
SKIP SCAN意味着要么表的部分子集,要么扫描表的所有记录,然而它将根据过滤条件跳过大量的记录。在一些情况下,当你的leading primary key columns的基数比较低时,它往往比FULL SCAN更有效率。
7. 我应该缓存Phoenix JDBC连接吗?
No,没有必要缓存Phoenix JDBC连接。
Phoenix的连接对象不同于绝大部分的JDBC连接,因为其是底层是HBase连接的。如果Phoenix连续被重用,有可能之前使用的用户留下的HBase连接状态并不是良好的,所以最好还是新创建一个Phoenix连接,可以避免潜在的问题。
8. 为什么执行upsert操作时,Phoenix添加一个empty/dummy的KeyValue?
我们先来看一下示例:
create table HADOOP (rk Integer primarykey,info.name varchar ,info.score Integer );
upsert into HADOOP values(1,'Hadoop',90);
查看HBase的表内容:
hbase(main):037:0> scan 'HADOOP'
ROW COLUMN+CELL
\x80\x00\x00\x01 column=INFO:NAME, timestamp=1472634053972,value=Hadoop
\x80\x00\x00\x01 column=INFO:SCORE, timestamp=1472634053972,value=\x80\x00\x00Z
\x80\x00\x00\x01 column=INFO:_0, timestamp=1472634053972,value=x
我们再通过Phoenix插入一行记录:
upsert into HADOOP values(2,'Spark',95);
再次查看HBase的表内容:
hbase(main):037:0> scan 'HADOOP'
ROW COLUMN+CELL
\x80\x00\x00\x01 column=INFO:NAME, timestamp=1472634053972, value=Hadoop
\x80\x00\x00\x01 column=INFO:SCORE, timestamp=1472634053972,value=\x80\x00\x00Z
\x80\x00\x00\x01 column=INFO:_0, timestamp=1472634053972,value=x
\x80\x00\x00\x02 column=INFO:NAME, timestamp=1472634140841,value=Spark
\x80\x00\x00\x02 column=INFO:SCORE, timestamp=1472634140841,value=\x80\x00\x00_
\x80\x00\x00\x02 column=INFO:_0, timestamp=1472634140841,value=x
可以发现针对每一行,都会增加一列_0。
empty/dummy的KeyValue(列修饰符_0)是需要的,为了确保给定的列能够被所有行访问,这是从提升查询性能方面考虑的。
据你所知,数据是以KeyValues形式存储在HBase中的,意味着全部的行键存储着每个列值。这也意味着除非至少有一个列存储,否则行键根本不存储数据。
这部分内容,后续我们有时间从源码层面给大家说明一下。
9. 如何将带有Schema的表移到namespace中?
前提条件:需要4.8以及以上版本去映射表到namespace。具体相关配置请查看官方文档:http://phoenix.apache.org/tuning.html
对于基于Kerberos的环境来说,运行的用户需要有admin权限创建namespace。
表将仅仅映射到名称为schema_name的namespace中,目前不支持迁移已经存在的表到不同的schema或者namespace。
我们来看一下测试过程:
[kylin@SZB-L0023780apache-phoenix-4.8.0-HBase-1.1-bin]$ bin/psql.py SZB-L0023780:2181:hbase114 -mmyworld.HADOOP
Starting upgrading table:myworld.HADOOP...please don't kill it in between!!
java.lang.IllegalArgumentException:phoenix.schema.isNamespaceMappingEnabled is not enabled!!
atorg.apache.phoenix.util.UpgradeUtil.upgradeTable(UpgradeUtil.java:1717)
atorg.apache.phoenix.util.PhoenixRuntime.main(PhoenixRuntime.java:221)
可以看到需要设置参数允许namespace mapping。
<property>
<name>phoenix.schema.isNamespaceMappingEnabled</name>
<value>true</value>
</property>
这里一定要注意:如果设置为true,创建的带有schema的表将映射到一个namespace,这个需要客户端和服务端同时设置。一旦设置为true,就不能回滚了。旧的客户端将无法再正常工作。所以建议大家都查看官方文档,确定后再进行设置。
示例:将表“table_name”移动到“schema_name”的namespace中。
bin/psql.py <zookeeper> -m<schema_name>.<table_name>