1.安装habse 1.25
2.下载并且解压 Phonenix 4.9 (所有节点)
我用的是:apache-phoenix-4.9.0-HBase-1.2-bin.tar.gz
3. 将Phonenix 的 lib 中的所有jar包 拷贝到hbase的里面 (所有节点)
(二级索引测试)
(补充:2017-8-15日):
hbase-site.xml 中添加:(每个节点,phonenix4.8 以下版本 配置不同 ,网上自己找)
<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 uses separate 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 RPC Scheduler that uses separate queues for index and metadata updates</description>
</property>
4.把hbase 中 conf 里面的 hbase-site.xml 文件拷贝到 Phonenix 中的 bin 目录中 (所有节点)
5.重新启动hbase
6.开启Phonenix : # Phonenix/bin/sqlline.py zkserver01:2181 (任意hbase集群节点,后面跟的是zk的一个节点,我的是slaver3:2800)
效果:
Setting property: [incremental, false]
Setting property: [isolation, TRANSACTION_READ_COMMITTED]
issuing: !connect jdbc:phoenix:slaver3:2800 none none org.apache.phoenix.jdbc.PhoenixDriver
Connecting to jdbc:phoenix:slaver3:2800
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/home/phoenix4.9.0/phoenix-4.9.0-HBase-1.2-client.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/home/hadoop2.73/share/hadoop/common/lib/slf4j-log4j12-1.7.10.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
17/08/11 09:17:51 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
Connected to: Phoenix (version 4.9)
Driver: PhoenixEmbeddedDriver (version 4.9)
Autocommit status: true
Transaction isolation: TRANSACTION_READ_COMMITTED
Building list of tables and columns for tab-completion (set fastconnect to true to skip)...
87/87 (100%) Done
Done
sqlline version 1.2.0
7.#!tables
能看到很多表
0: jdbc:phoenix:slaver3:2800> !tables
+------------+--------------+-------------+---------------+----------+------------+----------------------------+-----------------+--------------+-----------------+------------+
| TABLE_CAT | TABLE_SCHEM | TABLE_NAME | TABLE_TYPE | REMARKS | TYPE_NAME | SELF_REFERENCING_COL_NAME | REF_GENERATION | INDEX_STATE | IMMUTABLE_ROWS | SALT_BUCKE |
+------------+--------------+-------------+---------------+----------+------------+----------------------------+-----------------+--------------+-----------------+------------+
| | SYSTEM | CATALOG | SYSTEM TABLE | | | | | | false | null |
| | SYSTEM | FUNCTION | SYSTEM TABLE | | | | | | false | null |
| | SYSTEM | SEQUENCE | SYSTEM TABLE | | | | | | false | null |
| | SYSTEM | STATS | SYSTEM TABLE | | | | | | false | null |
+------------+--------------+-------------+---------------+----------+------------+----------------------------+-----------------+--------------+-----------------+-----
8.操作练习
0: jdbc:phoenix:slaver3:2800> CREATE TABLE MY_TABLE (id BIGINT not null primary key,dt CHAR(15));
No rows affected (1.35 seconds)
0: jdbc:phoenix:slaver3:2800> UPSERT INTO my_table values(12,'abc');
1 row affected (0.109 seconds)
0: jdbc:phoenix:slaver3:2800> UPSERT INTO my_table values(13,'bcd');
1 row affected (0.023 seconds)
0: jdbc:phoenix:slaver3:2800> select * from my_table;
+-----+------+
| ID | DT |
+-----+------+
| 12 | abc |
| 13 | bcd |
+-----+------+
2 rows selected (0.098 seconds)
0: jdbc:phoenix:slaver3:2800> !quit
Closing: org.apache.phoenix.jdbc.PhoenixConnection
这里是在hbase shell中查看
[root@slaver3 bin]# cd /home/hbase1.25/bin/
[root@slaver3 bin]# ./hbase shell
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/home/hbase1.25/lib/phoenix-4.9.0-HBase-1.2-client.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/home/hbase1.25/lib/phoenix-4.9.0-HBase-1.2-hive.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/home/hbase1.25/lib/phoenix-4.9.0-HBase-1.2-pig.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/home/hbase1.25/lib/phoenix-4.9.0-HBase-1.2-thin-client.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/home/hbase1.25/lib/slf4j-log4j12-1.7.5.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/home/hadoop2.73/share/hadoop/common/lib/slf4j-log4j12-1.7.10.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
HBase Shell; enter 'help<RETURN>' for list of supported commands.
Type "exit<RETURN>" to leave the HBase Shell
Version 1.2.5, rd7b05f79dee10e0ada614765bb354b93d615a157, Wed Mar 1 00:34:48 CST 2017
hbase(main):001:0> list
TABLE
MY_TABLE
SYSTEM.CATALOG
SYSTEM.FUNCTION
SYSTEM.SEQUENCE
SYSTEM.STATS
ScalaTest
TestScores
hbase_test
student
9 row(s) in 0.2800 seconds
=> ["MY_TABLE", "SYSTEM.CATALOG", "SYSTEM.FUNCTION", "SYSTEM.SEQUENCE", "SYSTEM.STATS", "ScalaTest", "TestScores", "hbase_test", "student"]
hbase(main):006:0> scan 'MY_TABLE'
ROW COLUMN+CELL
\x80\x00\x00\x00\x00\x00\x00\x0C column=0:DT, timestamp=1502414575468, value=abc
\x80\x00\x00\x00\x00\x00\x00\x0C column=0:_0, timestamp=1502414575468, value=x
\x80\x00\x00\x00\x00\x00\x00\x0D column=0:DT, timestamp=1502414588554, value=bcd
\x80\x00\x00\x00\x00\x00\x00\x0D column=0:_0, timestamp=1502414588554, value=x
2 row(s) in 0.0510 seconds
二级索引测试:
案例:(4.7 及其一下版本,4.8在后面写到)
1
|
> create table company(
id
varchar primary key, name varchar, address varchar);
|
![](https://i-blog.csdnimg.cn/blog_migrate/dc30037a545abcf477cc700461cb7589.jpeg)
1
|
> !indexes company
|
![](https://i-blog.csdnimg.cn/blog_migrate/518b702de653c0bfde36a7cf31ece870.jpeg)
2.3 创建索引
对company表的name字段创建索引,索引名为my_index。
1
|
> create
local
index my_index on company(name);
|
![](https://i-blog.csdnimg.cn/blog_migrate/5bad7c304c5bd04cd267f8986591dc37.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/9ef7daf75b59914e18c709e54c5d5144.jpeg)
从HBase的CLI界面查看当前所有表。
《重要提示:》
phonenix4.8开始 会将“本地索引表”一同合并到原表中;(后面有实例,下面这个是4.7以下的版本,能在hbase中看到本地索引表)
1
|
> list
|
![](https://i-blog.csdnimg.cn/blog_migrate/8ce60b95935affc406c27115c79aec09.jpeg)
2.4 插入数据
在company表中添加测试数据。
1
|
> upsert into company(
id
, name, address) values(
'001'
,
'dimensoft'
,
'nanjing'
);
|
2.5 查询数据
查看company表数据以及索引表my_index数据。
1
2
|
>
select
* from company;
>
select
* from my_index;
|
![](https://i-blog.csdnimg.cn/blog_migrate/d42cacfa17e77758f307afe064f431be.jpeg)
1
|
> scan
'_LOCAL_IDX_COMPANY'
|
![](https://i-blog.csdnimg.cn/blog_migrate/48c782dd42621db39cbc76eaf5118f28.jpeg)
3个索引字段_INDEX_ID、NAME和ID的值被合并为索引表的rowKey,其中_INDEX_ID并没有值(\x000是十六进制表示,转换为字符串是空格)。
phonenix 4.8 版本以上 local index变更:
phoenix有全局索引以及本地索引(可变与不可变等其它的且不谈),全局索引理解应该比较简单,如果让我自己去实现Hbase的索引应该想到的也是全局索引这种方式。本地索引适用于写比较频繁,储存空间受限的情况。
Local indexing targets write heavy, space constrained use cases.
phoenix-4.8.x的本地索引与之前版本的实现方式完全不同,在官网中也没有比较详细的说明(感觉phoenix的文档跟不上版本更新)。以4.8版本为分水岭,分别说一下新旧版本的两种不同实现方式。
2. 本地索引前置条件
实现本地索引需要考虑以下3个方面的因素:
- 索引数据与原表数据需要位于同一regionserver(colocation),这是为了性能而考虑,可以避免不必要的网络传输。同时,它也是条件2的前置条件:意味着数据region和索引region都以原子性的方式被同一个regionserver打开(要么一起提供服务,要么一起offline)。
- 一致性:指的是索引表与原表可以被原子更新数据,有两种方式:1)使用事务 ;2)索引表与原表共享相同的wal同时使用MVCC机制。
- 需要考虑客户端如何使用索引数据:如果本地索引是保存在一个单独的表中,那么客端直接进行scan等一些操作,但如果索引数据隐藏在原数据region当中,那么就需要另外的机制去保障如何从原数据region中访问到索引数据。
3. 旧版本实现方式
4.8以前版本的实现方式与全局索引有点相似(索引数据与原表数据分离),不同的是本地索引有自己的split逻辑,具体的细节可以参考华为hbase二级索引的实现原理,这种实现方式的特点有:
- 由一个单独的表去保存本地索引数据
- 原表以及索引表各自拥有自己的WAL,这意味着一致性得不到保障,有可能原表数据多于索引数据,也有可能相反。
- 使用balancer来尽量保证colocation,但只能提供一个很弱的保证,最坏的情况下它将会失去原表数据与索引数据co-locate特性。
- 使用本地索引就像对一个常规的phoenix进行操作,例如查询的时候根据查询条件可以快速定位到索引数据处于哪个region上(这点比4.8的实现方式好)。
4. 4.8版本实现方式
4.1 特点
引用官网的描述:
From 4.8.0 onwards we are storing all local index data in the separate shadow column families in the same data table
这里说明了索引的数据是与原表数据储存在同一个表的,不过索引数据是一个独立的”阴影列族”,在网上是搜索不到这个概念的,个人理解是因为这个一列族客观是存在的,但使用时对用户不可见,这种方式的特点有:
- 这种存储特性使得colocation得到强保证,因为原表数据与索引数据的RowKey都在同一个范围内,使得它们会位于同一个region中。
- 因为原数据与索引数据保存在同一个region,所以它们共享同一个WAL与MVCC标志,使得原子性也得到保证:当插入一行记录时,会同时解析出其中的索引数据并附加到本次的插入,所以,实际插入的行=业务数据+索引。
- 索引数据的生成以及读取都依赖于原表以及索引表(索引表已经是一个逻辑表),原因在下面介绍本地索引RowKey的组成会说明。
- 这种实现方式不需要对hbase作任何改变,最主要的是不用再去维护region的split以及merge(以前的实现方式需要phoenix自己实现逻辑以保证split/merge后索引数据与原数据依然在同一个region中)
4.2 索引构成
1.创建一个测试表
- 1
- 1
2.为测试表创建一个本地索引
- 1
- 1
3.插入一条记录
- 1
- 1
4.进入hbase shell并scan
观察scan结果,只upsert了一条记录,却出现了2 row(s),那是因为多了一行索引数据,可以看到第一行记录即为索引数据:
\x00\x00name1\x00\x80\x00\x00\x00\x00\x00\x00\x01
分析一下索引的组成部分,上面以3种颜色表示不同的部分:
\x00\x00:这是代表索引的编号,如果再创建多一个索引,那么会变成\x00\x01
name1\x00:索引列的值,后面的\x00其实是空格,不知是不是为了起标记作用
\x80\x00\x00\x00\x00\x00\x00\x01:这串就是RowKey即ID值了,可以参考scan正面的记录值。
注意,上面的分析是基于一个region的情况,如果原表是加盐表,RowKey=加盐前缀+ID,而索引数据也会一样在索引编号前面加上同样的加盐前缀,这样可以保证了索引与原表数据保存在同一个桶(region)中,保证了colocate特性。这里的加盐前缀其实相当于该region的starRowKey。其中还涉及了很多实现细节,例如当region进行split时会怎么样,可以去参考一下奇虎360Hbase二级索引的实现原理。
4.3 不足之处
- 当确定查询条件进行查询,此时是无法确定我们要检索的数据位于哪一个region上面,根据上面所分析的索引构成,索引还有一部分是加盐前缀(region的startRowKey),这个部分就是查询时无法确定的,所以当一个查询请求到达时,所有region都会响应,这会带来额外的开销。相比于全局索引或者旧版本的本地索引实现方式,由于是一个单独的表来保存索引数据,所以根据查询条件可以快速定位到索引数据所在的region.
- 使用本地索引一般都是先根据查询条件获取到数据rowkey,然后再根据rowkey到原表查询其它列的详细信息,所以,每一次查询都包含了scan以及seek操作,这也是它不适用于读频繁的原因之一。
4. 参考
https://phoenix.apache.org/secondary_indexing.html
https://issues.apache.org/jira/browse/PHOENIX-1734