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

    xiaoxiao2025-07-19  11

    目录

    背景Phoenix的二级索引Covered Indexes(覆盖索引)Functional indexes(函数索引)Global indexes(全局索引)Local indexes(本地索引)删除索引 索引的优化Phoenix分区未分区的表分区的表 SCAN操作实时场景计算与分析CONSTRAINT PRIMARY KEYSCAN类型RANGE SCANFULL SCANSKIP SCANDEGENERATE SCAN 推荐

    背景

    众所周知,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

    最新回复(0)