大数据系列(五)NoSQL数据库Hbase之Phoenix二级索引以及rowKey的设计

背景

众所周知,HBase的索引基于RowKey, 几千万几亿的数据,你只要where rowkey=‘xx’,简直是毫秒查询。但是问题来了,那么多列,我就想用别的列查,怎么办? 既然上了phoenix,那么二级索引 那必须得会用

使用phoenix的二级索引查询,可以避免扫描整个表,简直快又快

我们先来看一张图:
在这里插入图片描述
我们看到,220w的数据,count一下竟然要6秒多,我们通过explain select count(0) from demo命令发现,竟然扫描了全表,那么试一下RowKey条件以及非RowKey条件:
在这里插入图片描述
可以看到,rowKey条件并不是扫描全表,所以非常的快。 再来看看其他条件:

在这里插入图片描述
可以看到,不光慢,还扫描了全表。所以基于这样的现状,我们需要二级索引来撑场面(因为纯粹的HBase,RowKey设计起来太麻烦了,想要设计的好,就等于又设计出来一个phoenix,还不如直接使用)

Phoenix的二级索引

如果要开启各种索引,在hbase-site.xml中加入:

在每一个RegionServer的hbase-site.xml中加入如下的属性:

<property>
	<name>hbase.regionserver.wal.codec</name>
	<value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>
</property>
<!-- Phoeinx4.3以上为支持在数据region合并时本地索引region也能进行合并需要在每个region servers中添加以下属性,但是博主懒得配置,配置好像报错了 -->
<property>
	<name>hbase.coprocessor.regionserver.classes</name>
	<value>org.apache.hadoop.hbase.regionserver.LocalIndexMerger</value>
</property>

可以现在master上配置,然后使用
rsync -av /root/hbase/conf/hbase-site.xml node1:/root/hbase/conf
rsync -av /root/hbase/conf/hbase-site.xml node2:/root/hbase/conf
将文件copy到各个节点,然后在配置master自己的

在每一个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>

然后重启即可 stop-hbase.sh start-hbase.sh

Covered Indexes(覆盖索引)

覆盖索引:只需要通过索引就能返回所要查询的数据,所以索引的列必须包含所需查询的列(SELECT的列和WHRER的列)

CREATE INDEX idx_app_click ON app_click(app_name) INCLUDE(USER_ID,MOBILE_TYPE);
我们创建了一个索引,字段是app_name, 包含了另外两列:USER_ID与MOBILE_TYPE

先上一张图,上图之前,查询是5s左右:
在这里插入图片描述
的确快如闪电

Functional indexes(函数索引)

从Phoeinx4.3以上就支持函数索引,其索引不局限于列,可以合适任意的表达式来创建索引,当在查询时用到了这些表达式时就直接返回表达式结果

使用UPPER函数创建函数索引使查询出的USERID和URL里字母都是大写的

创建函数索引
CREATE INDEX func_index ON demo(UPPER(USERID || ’ ’ || URL))

这里博主就不实验了

Global indexes(全局索引)

全局索引适用于多读少写的场景,在写操作上会给性能带来极大的开销,因为所有的更新和写操作(DELETE,UPSERT VALUES和UPSERT SELECT)都会引起索引的更新,在读数据时,Phoenix将通过索引表来达到快速查询的目的。

CREATE INDEX idx_demo ON demo(text);
在查询text字段的时候会用到索引:select text from demo where text=‘demo’;

然后使用索引查询,果然没有扫描全表,:

explain select text from demo where text=‘demo’;

Local indexes(本地索引)

本地索引适用于写多读少,空间有限的场景,和全局索引一样,Phoneix在查询时会自动选择是否使用本地索引,使用本地索引,为避免进行写操作所带来的网络开销,索引数据和表数据都存放在相同的服务器中,当查询的字段不完全是索引字段时本地索引也会被使用,与全局索引不同的是,所有的本地索引都单独存储在同一张共享表中,由于无法预先确定region的位置,所以在读取数据时会检查每个region上的数据因而带来一定性能开销。

create local index app_name on app_click(app_name);

删除索引

drop index IDX_APP_CLICK on app_click;

索引的优化

以下属性都必须在各节点上的hbase-site.xml中设置为true才能起效

1.index.builder.threads.max:(默认值:10)
根据主表的更新来确定更新索引表的线程数

2.index.builder.threads.keepalivetime:(默认值:60)
builder线程池中线程的存活时间

3.index.write.threads.max:(默认值:10)
更新索引表时所能使用的线程数(即同时能更新多少张索引表),其数量最好与索引表的数量一致

4.index.write.threads.keepalivetime(默认值:60)
更新索引表的线程所能存活的时间

5.hbase.htable.threads.max(默认值:2147483647)
每张索引表所能使用的线程(即在一张索引表中同时可以有多少线程对其进行写入更新),增加此值可以提高更新索引的并发量

6.hbase.htable.threads.keepalivetime(默认值:60)
索引表上更新索引的线程的存活时间

7.index.tablefactoy.cache.size(默认值:10)
允许缓存的索引表的数量
增加此值,可以在更新索引表时不用每次都去重复的创建htable,由于是缓存在内存中,所以其值越大,其需要的内存越多

Phoenix分区

良好的分区,可以极大提升HBase的处理效率,指定多个分区后,等于读写操作都会由多个RegionServer来操作,这样对于效率的提升是成倍的。

未分区的表

我们在之前的博客中,创建过表,我们在创建一个表,并且插入一定量数据之后,观察一下不分区的结果:

创建demo
create table demo(id integer not null primary key, name varchar(50),sex integer);

然后插入几百万数据之后(rowkey是日期),我们看一下结果:
在这里插入图片描述
上图可以看到,整个存储来看,大部分数据全部存在了node1节点中,并且全局只有3个region,读写请求几乎都是node1来做的,

分区的表

那么我们来创建一个指定分区的表:

create table all_log(id integer not null primary key, name varchar(50),sex integer) SALT_BUCKETS=6;

依然搞点数据进去,又是查询又是插入,然后看结果:
在这里插入图片描述
上图可以看得出来,我勒个去,读写请求以及存储,分布在各个节点之上,分区的值应为region server总CPU核数的0.5~1倍之间(这里指集群cpu核数总和,这个说法来自于万能的网友,而不是官网)

这里提醒一点,既然table可以指定分区,那么index肯定也是可以的,毕竟index也是属于table的

SCAN操作

有人问了,反正都有二级索引了,我设计个毛rowkey,直接上二级索引不是high的吗?

二级索引是创建了索引表进行多数据存储,会极大消耗存储空间,并且影响插入效率

所以说,不可以全部依赖与二级索引

实时场景计算与分析

博主这里的场景是实时计算分析,然后直接入库,对于upsert操作非常频繁(实时计算没有使用spark,博主自己团队利用logback写了实时流计算),这样的场景下,如果我们做了3张索引表,相当于一次upsert就是4次,虽然你读取爽了,想想你插入怎么办?
这里博主团队使用rowkey的scan策略+内存计算,实现实时查询(group by等操作,类似于spark,不过目前业务量单节点即可,后期考虑分布式的内存计算spark),实时计算插入(大量的count、sum等操作,包含部分etl,直接利用内存以及phoenix支持的on duplicate key update)

利用日志机制,将所有数据由之前mysql分库分表改为行为、业务等日志传输,实时接收,实时计算与清洗,然后插入hbase。建立相关的rowkey、组合主键、二级索引,查询的时候如果表没有二级索引,则依靠rowkey检索一批数据,内存中进行group by等操作。

CONSTRAINT PRIMARY KEY

我们创建一个组合的主键索引,然后利用组合的索引各种查询,看下结果

CREATE TABLE IF NOT EXISTS Test.AppLog ( 
userId INTEGER NOT NULL,
itemId INTEGER NOT NULL,
date VARCHAR NULL,
text VARCHAR,
test VARCHAR,
CONSTRAINT pk_TestAppLog PRIMARY KEY (userId, itemId, date)
) default_column_family='apl', SALT_BUCKETS=8;

查询语句执行效果如下:
在这里插入图片描述
上图可以看到,除了select * from table扫描了全表之外,其余没有扫描全表。

SCAN类型

Phoenix SCAN分为 RANGE SCAN, FULL SCAN, SKIP SCAN 及 DEGENERATE SCAN

RANGE SCAN

RANGE SCAN是指,仅扫描表中的一部分行。如果您使用主键约束中的一个或多个前导列,则会发生这种情况。

FULL SCAN

FULL SCAN意味着将扫描表的所有行(但如果sql中包含WHERE子句,则可能会应用过滤器)

SKIP SCAN

Phoenix使用SKIP_SCAN应对行内scan。当根据给定的一组键检索行时,与Range Scan相比能显着提高性能。
他的原理是利用了HBase Filter的SEEK_NEXT_USING_HINT。 它存储了每个列中正在被搜索的key set/range的信息。 然后它接收一个key(在过滤器评估期间传递给它),并确定该key是否在其中一个set或range内。 如果没有,它会计算出要跳到的下一个目标最大key值。

DEGENERATE SCAN

DEGENERATE SCAN意味着查询不可能返回任何行。 如果我们可以在编译时确定,那么我们甚至可以不运行该``scan。
楼上的图中,利用组合索引三列进行查询,没有任何数据值,这是肯定的,因为利用主键查询,博主没有添加任何数据,当然不会有返回值,这并不是什么报错。插入数据后,再次查看sql结果,会显示 ROUND ROBIN POINT LOOKUP ON 1 KEY OVER TEST.APPLOG

推荐

老规矩,推荐一些文章:
https://blog.csdn.net/Cky079/article/details/84442894
https://blog.csdn.net/carolzhang8406/article/details/79455684
https://blog.csdn.net/haoshuai2015/article/details/79963716
https://blog.csdn.net/baichoufei90/article/details/85732043

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值