Hbase简介
参考:Hbase技术详细学习笔记
如何合理的设计HBase RowKey?
Hbase是分布式、面向列的开源数据库(其实准确的说是面向列族)。
HDFS为Hbase提供可靠的底层数据存储服务;
MapReduce为Hbase提供高性能的计算能力;
Zookeeper为Hbase提供稳定服务和Failover机制。
因此我们说Hbase是一个通过大量廉价的机器解决海量数据的高速存储和读取的分布式数据库解决方案。
Hbase特点
- 海量存储
Hbase适合存储PB级别的数据,采用廉价PC存储的情况下,能在几十到百毫秒内返回查询结果。 - 列式存储
这里的列式存储其实说的是列族存储,Hbase是根据列族来存储数据的。
列族下面可以有非常多的列,列族在创建表的时候就必须指定。
对比关系型数据库的表与Hbase数据库的表
关系型数据库的表(RDBMS)
key | column1 | column2 |
---|---|---|
record1 | v11 | v21 |
record2 | v12 | v22 |
Hbase 的表
Rowkey | cf1 | cf2 |
---|---|---|
record1 | c1...cn | c1,c2,c3 |
record2 | c1,c2 |
compare
Type | Hbase | RDBMS |
---|---|---|
数据类型 | 字符串 | 丰富的数据类型 |
数据操作 | 简单的增删改查 | SQL 支持 |
存储模式 | 列存储 | 行存储 |
数据保护 | 保留 | 替换 |
可伸缩性 | 好 | 差 |
- 易扩展性
扩展性分为两个层面:RegionServer 扩展与 Hdfs 扩展
RegionServer 扩展:
基于上层处理能力的扩展,通过横向增加 RegionServer 机器,提升 Hbase 上层处理能力,使得 Hbase 可以服务更多的 Region。
Hdfs 扩展:
通过hdfs扩容,提升 Hbase 的数据存储能力和 读写能力。 - 高并发
在并发的情况下,Hbase的单个IO延迟下降并不多。能获得高并发、低延迟的服务。(目前的Hbase架构中,大多采用廉价的PC设备,单个的IO延迟并不低) - 稀疏
一个列族可以包含多个列,在列数据为空的情况下,并不会占用存储空间。
概念介绍
- 列族(Column Family)
Hbase通过列族划分数据的存储,hbase表中的每个列,都归属与某个列族。
列族是表的chema的一部分(而列不是),必须在使用表之前定义。列名都以列族作为前缀。
列族必须在Hbase表创建的时候就指定。
Hbase的列族也不是越多越好,根据官方的说法列族最好小于或者等于3。 - Rowkey
Rowkey 类似于关系型数据库的主键,Hbase用Rowkey来唯一去分某一行数据。
目前支持三种查询:
基于Rwokey的单行查询
基于Rwokey的范围查询
范围查询
Rowkey对Hbase的性能影响非常大,所以设计好一个好的Rowkey非常重要,要兼顾单行、范围的查询。 - Region
Region的概念类似关系型数据库的分区或者分片差不多。
Hbase会将一个大表的数据基于Rowkey的不同范围分配到不同的Region中,每个Region负责一定范围的数据访问和存储。
这样即使是一张巨大的表,由于被切割到不同的Region,访问的速度也很快。 - TimeStamp
TimeStamp实现了Hbase对同相同Rowkey记录的多版本控制;
在写入数据时,如果没有指定 TimeStamp,Hbase会自动添加一个和服务器时间保持一致的TimeStamp;
相同的Rowkey数据按照TimeStamp 倒序排序,默认访问的是最新版本,也可以通过指定TimeStamp访问指定历史版本数据。
Hbase的架构
Client
Client包含了访问 Hbase 的接口;
另外Client还维护了对应的cache来加速Hbase的访问,比如cache的.META.元数据的信息。Zookeeper
- 通过Zoopkeeper来保证集群中只有1个master在运行,如果master异常,会通过竞争机制产生新的master;
- 提供服务通过 Zoopkeeper 来监控 RegionServer 的状态,当 RegionSevrer 有异常的时候,通过回调的形式通知 Master RegionServer 上下限的信息;
- 通过Zoopkeeper存储元数据的统一入口地址
- Hmaster
Hmaster的主要职责如下:
- 为RegionServer分配Region
- 维护整个集群的负载均衡
- 维护集群的元数据信息
- 发现失效的Region,并将失效的Region分配到正常的RegionServer上
- 当RegionSever失效的时候,协调对应Hlog的拆分
- HregionServer
HregionServer直接对接用户的读写请求,是真正的“干活”的节点。它的功能概括如下:
- 管理 Hmaster 为其分配的 Region
- 处理来自客户端的读写请求
- 负责和底层 HDFS 的交互,存储数据到 HDFS
- 负责 Region 变大以后的拆分
- 负责 Storefile 的合并工作
- HDFS
HDFS 为 Hbase 提供最终的底层数据存储服务,同时为Hbase提供高可用(Hlog存储在HDFS)的支持。
具体功能概括如下:
- 提供元数据和表数据的底层分布式存储服务
- 数据多副本,保证的高可靠和高可用性
使用场景
Hbase是一个通过廉价PC机器集群来存储海量数据的分布式数据库解决方案。
它比较适合的场景概括如下:
- 是巨量大(百T、PB级别)
- 查询简单(基于rowkey或者rowkey范围查询),不涉及到复杂的关联操作
- 要是实时返回查询结果
如:海量订单流水数据(长久保存)、交易记录、数据库历史数据。
Region 介绍
Region 类似于数据库的分片和分区的概念,每个 Region 负责一小部分 Rowkey 范围的数据的读写和维护,Region 包含了对应的起始行到结束行的所有信息。
Hmaster 将对应的region分配给不同的 RergionServer ,由 RegionSever 来提供 Region 的读写服务和相关的管理工作。
Region实例
模拟Hbase的表是如何拆分成region:
- 上图中的八条数据根据 RowKey 的范围被且分成3个Region,分别为UserInfo_Table Region1、UserInfo_Table Region2、UserInfo_Table Region3
- UserInfo_Table Region1、UserInfo_Table Region2 被分配给了HRegionServer 1,UserInfo_Table Region3被分配给了HRegionServer 2.
此处只做拆分模拟,真实情况下,几条记录并不会拆分,只有达到一定数据量才会拆分。
Region 寻址
Hbase 的读写都发生在 RegionSever 上,而每个 RegionSever 负责管理一定数量的 Region 服务。
那么当 Client 要对某一行数据做读写操作室,如何找到要访问的 RegionSever呢?
以读 UserInfo 表RowKey为007为例。
- Region寻址方式: 0.96 前
0.96 前Hbase有两个特殊的表:-ROOT-:、.META.
-ROOT-:存储在ZooKeeper中(对应之前zookeeper的功能),本身存储了, 且存储了.META. Table的RegionInfo信息,并且-ROOT-不会分裂,只有一个region
.META.:.META.表可以被切分成多个region。
读取流程如下
- Client 请求 ZK 获得 -ROOT- 所在的 RegionSever 地址
- Client 请求 -ROOT- 所在的RS地址,获取 .META. 表地址, Client 会将 -ROOT- 相关信息 cache 下来,以便下一次访问。
- Client 请求 .META. 表的RS地址,获取访问数据所在的 RegionSever 的地址。 Client 会将 .META. 相关信息 cache 下来,以便下一次访问。
- Client 请求访问数据所在 RegionSever 的地址,获取对应得数据
从上述流程可知,需要3此请求才能得到 UserInfo 标的真正位置,这对程序的性能带来一定程度上的下降。
之所以采用3层设计的原因是考虑到元数据可能需要很大的空间,但是在真正的集群运行,元数据的大小很轻易计算出来。
按照BigTable论文的描述,每行METADATA数据存储大小为1KB左右,如果按照一个Region为128M的计算,3层设计可以支持的Region个数为2^34 个,采用2层设计可以支持2^17(131072)。那么2层设计的情况下一个集群可以存储4P的数据。这仅仅是一个Region只有128M的情况下。如果是10G呢?因此,通过计算,其实2层设计就可以满足集群的需求。因此在0.96版本以后就去掉了-ROOT-表了。(这一块没搞懂怎么算的....)
- Region寻址方式: 0.96 后
如上面的计算,2层结构其实完全能满足业务的需求,因此0.96版本以后将-ROOT-表去掉了。
如下图所示:
- Clent 请求ZK 获取.META. 所在的RegionServer的地址。
- Client 请求 .META. 表的RS地址,获取访问数据所在的 RegionSever 的地址。 Client 会将 .META. 相关信息 cache 下来,以便下一次访问。
- Client 请求访问数据所在 RegionSever
关于去掉 -ROOT- 的原因:
提高性能(只需要两次请求,对比原来三次请求)
2层结构已经满足集群需求
关于Client会缓存.META.的问题
Q: 缓存什么时候更新?
A:Client的元数据缓存不更新。
Q:如果.META.更新了,比如Region1不在 RS2 上了,被转移到了 RS3 上。Client的缓存没有更新会有什么情况?
A:当.META.的数据发生更新,由于Region1的位置发生了变化,Client再次根据缓存去访问的时候,会出现错误,
当出现异常达到重试次数后就会去.META.所在的RegionServer获取最新的数据,
如果.META.所在的RegionServer也变了,Client就会去ZK上获取.META.所在的RegionServer的最新地址。
Hbase的写逻辑
Hbase的写入流程
如上图所示,主要流程如下:
- Client 获取写入数据的 Region 所在的 RegionServer
- 请求写 Hlog
- 请求写 MemStore
只有当 Hlog 、MemStore 都成功了才算些请求的完成,MemStore 后续会将数据逐渐刷到HDFS中
注:Hlog存储在HDFS,当RegionServer出现异常,需要使用Hlog恢复数据。
MemStore 刷盘
为了提高Hbase的性能,当写请求写入 MemStore 后,不会立刻刷盘,而是打到一定个条件才会刷盘。
- 全局内存控制
hbase.regionserver.global.memstore.upperLimit
全局的参数,控制内存整体的使用情况;
当所有memstore占整个heap的最大比例的时候,会触发刷盘的操作,默认值为整个heap内存的40%;
但是达到这一条件并不会触发所有的 MemStore 都进行刷盘,通过参数hbase.regionserver.global.memstore.lowerLimit
(默认是整个heap内存的35%)控制flush到所有memstore占整个heap内存的比率为35%的时候停止刷盘。(这么做主要是为了减少刷盘对业务带来的影响,实现平滑系统负载的目的) - MemStore 达到上限
MemStore 大小达到hbase.hregion.memstore.flush.size
配置的值时会出发刷盘,默认128M - RegionServer的 Hlog数量达到上限
Hbase 利用 Hlog 实现 Hbase 的数据一致性、高可用,如果Hlog太多会导致故障恢复的时间太长,因此 Hbase 通过参数hase.regionserver.max.logs
限制 Hlog 的数量,默认32,当Hlog 数量达到该值就会出发 MemStore 的强制刷盘 - 手动触发
通过 HBase Shell 或 Java API 手动触发 flush 操作 - 正常关闭RegionServer
正常关闭 RegionServer 会触发 MemStore 刷盘的操作,全部数据刷盘后就不需要再使用 Hlog 恢复数据。 - Region 使用 Hlog 恢复完数据后
当RegionServer出现故障后,其上的Rgion会迁移到其他正常的 RegionServer中,当恢复万Region的数据后,会触发刷盘,完成后才会提供给业务访问。
Hlog
- Hlog 结构
从上图我们可以得到如下信息:
- 多个 Region 共享一个Hlog文件,单个 Region 在Hlog中按照时间顺序排序,多个 Region 可能并不会完全按照时间顺序。
- Hlog最小单元由 Hlogkey 和 WALEdit 组成
- Hlogky由sequenceid、timestamp、cluster ids、regionname以及tablename等组成
- WALEdit是由一系列的 KeyValue 组成,对一行上所有列(即所有 KeyValue )的更新操作,都包含在同一个WALEdit对象中,这主要是为了实现写入一行多个列时的原子性。
sequenceid是一个store级别的自增序列号,是一个非常重要的属性,region的数据恢复和Hlog过期清除都要依赖这个属。
对于某一个store,开始的时候oldestUnflushedSequenceId为NULL;
此时,如果触发flush的操作,假设初始刷盘到sequenceid为10,那么hbase会在10的基础上append一个空的Entry到HLog,最新的sequenceid为11;
然后将 sequenceid 为11的号赋给 oldestUnflushedSequenceId ,并将 oldestUnflushedSequenceId 的值刷到HFile文件中进行持久化。
Hlog文件对应所有 Region 的 store 中最大的 sequenceid 如果已经刷盘,就认为 Hlog 文件已经过期,就会移动到 .oldlogs ,等待被移除。
当RegionServer出现故障的时候,需要对Hlog进行回放来恢复数据。
回放的时候会读取HFile的 oldestUnflushedSequenceId 和 Hlog 中的 sequenceid 进行比较,
sequenceid小于oldestUnflushedSequenceId的就直接忽略,大于或者等于的就进行重做。回放完成后,就完成了数据的恢复工作。
- Hlog的生命周期
- 产生
所有涉及到数据的变更都会先写Hlog,除非是你关闭了Hlog - 滚动
hbase.regionserver.logroll.period
默认1个小时,达到设置值后会创建一个新的Hlog文件,配合hase.regionserver.max.logs
是为了控制单个Hlog过大的情况,方便后续的过期和删除。 - 过期
Hlog的sequenceid 会和 HFile 中的 oldestUnflushedSequenceId(刷新到的最新位置) 比较;
如果小于 oldestUnflushedSequenceId 说明 Hlog就过期了,过期了以后,对应Hlog会被移动到.oldlogs目录。
Q: 为什么要将过期的Hlog移动到.oldlogs目录,而不是直接删除呢?
A:Hbase还有一个主从同步的功能,这个依赖Hlog来同步Hbase的变更,有一种情况不能删除Hlog,那就是Hlog虽然过期,但是对应的Hlog并没有同步完成,因此比较好的做好是移动到别的目录。再增加对应的检查和保留时间。
- 删除
如果Hbase开启了replication,当replication执行完一个Hlog的时候,会删除Zoopkeeper上的对应Hlog节点。
在Hlog被移动到.oldlogs目录后,Hbase每隔hbase.master.cleaner.interval
(默认60秒)时间会去检查.oldlogs目录下的所有Hlog,确认对应的Zookeeper的Hlog节点是否被删除,如果Zookeeper 上不存在对应的Hlog节点,那么就直接删除对应的Hlog。
hbase.master.logcleaner.ttl
(默认10分钟)这个参数设置Hlog在.oldlogs目录保留的最长时间。
RegionServer的故障恢复
RegionServer(RS) 的相关信息保存在ZK中(前面说的ZK功能),在 RS 启动的时候,会在 ZK 中创建对应的临时节点。
RS 与 ZK 之间会维持一个心跳,当心跳断开时候,ZK 认为该 RS 出现了故障,会将该 RS 对应的临时节点删除并通知 HMaster ,HMaster收到RS 挂掉的信息后启动数据恢复流程
HMaster收到RS启动数据恢复流程:
- RS宕机
- ZK检测到RS异常,删除临时节点,通知HMaster
- HMaster收到宕机信息,启动数据恢复
- Hlog切分
- Region重新分配
- Hlog回放
- 完成恢复并提供服务
故障恢复三种模式:LogSplitting、、
LogSplitting
流程:
- 将待切分的日志文件夹进行重命名,防止RS未真的宕机而持续写入Hlog
- HMaster启动读取线程读取Hlog的数据,并将不同Region的日志写入到不同的内存buffer中。
- 针对每个buffer,HMaster 会启动对应的写线程将不同 Region 的 buffer 数据写入到HDFS中,对应的路径为
/hbase/table_name/region/recoverd.edits/.tmp
(未验证)。 - HMaster 重新将宕机的 RS 中的Rgion分配到正常的 RS 中,对应的 RS 读取 Region 的数据,会发现该 Region 目录下的recoverd.edits目录以及相关的日志,然后 RS 重放对应的Hlog日志,从而实现对应 Region 数据的恢复。
LogSplitting 模式中 Hlog的切分一直都是HMaster在干活,如果集群中有多台RegionServer在同一时间宕机,串行修复,肯定异常慢,因为只有master一个人在干Hlog切分的活。
Distributed Log Splitting
利用多个 RS 来并行切分 Hlog,提高切分的效率。
流程:
- HMaster 将要切分的日志发布到ZK节点上(
/hbase/splitWAL
),每个Hlog日志一个任务,任务的初始状态为TASK_UNASSIGNED - 在HMaster发布Hlog任务后,RS会采用竞争方式认领对应的任务(先查看任务的状态,如果是TASK_UNASSIGNED,就将该任务状态修改为TASK_OWNED)
- RS 取得任务后会让对应的 HLogSplitter 线程处理Hlog的切分,切分的时候读取出Hlog的WALEdit 内容,然后写入不同的Region buffer的内存中。
- RS 启动对应写线程,将Region buffer 的数据写入到HDFS中路径为
/hbase/table/region/seqenceid.temp
,seqenceid是一个日志中该Region对应的最大sequenceid,
如果日志切分成功,而 RS 会将对应的ZK节点的任务修改为TASK_DONE;
如果切分失败,则会将任务修改为TASK_ERR。 - 如果任务是TASK_ERR状态,则HMaster会重新发布该任务,继续由RegionServer竞争任务,并做切分处理。
- HMaster重新将宕机的RS中的Rgion分配到正常的RS中,对应的RS读取Region的数据,将该region目录下的一系列的seqenceid.temp进行从小到大进行重放,从而实现对应Region数据的恢复。
Distributed Log Splitting 采用分布式的方式,使用多台RS做Hlog的切分工作,确实能提高效率。
但是这种方式有个弊端是会产生很多小文件,比如一个RegionServer有20个Region,有50个Hlog,那么产生的小文件数量为20*50=1000个
Distributed Log Replay
Distributed Log Replay和Distributed Log Splitting的不同是先将宕机RS上的Region分配给正常的RS,并将该Region标记为recovering。
再使用Distributed Log Splitting 类似的方式进行Hlog切分,不同的是,RS将Hlog切分到对应Region buffer后,并不写HDFS,而是直接进行重放。这样可以减少将大量的文件写入HDFS中,大大减少了HDFS的IO消耗。
Region的拆分
3种默认策略:ConstantSizeRegionSplitPolicy、、
ConstantSizeRegionSplitPolicy
0.94版本之前的默认拆分策略,这个策略的拆分规则是:当region大小达到hbase.hregion.max.filesize
(默认10G)后拆分。
这种拆分策略对于小表不太友好,按照默认的设置,如果1个表的HFile小于10G就一直不会拆分。注意10G是压缩后的大小,如果使用了压缩的话。
如果1个表一直不拆分,访问量小也不会有问题,但是如果这个表访问量比较大的话,就比较容易出现性能问题。这个时候只能手工进行拆分。还是很不方便。
IncreasingToUpperBoundRegionSplitPolicy
IncreasingToUpperBoundRegionSplitPolicy 策略是Hbase的0.94~2.0版本默认的拆分策略,这个策略相较于ConstantSizeRegionSplitPolicy策略做了一些优化,该策略的算法为:min(r^2*flushSize,maxFileSize ),最大为maxFileSize 。
从这个算是我们可以得出flushsize为128M、maxFileSize为10G的情况下,可以计算出Region的分裂情况如下:
第一次拆分大小为:min(10G,11128M)=128M
第二次拆分大小为:min(10G,33128M)=1152M
第三次拆分大小为:min(10G,55128M)=3200M
第四次拆分大小为:min(10G,77128M)=6272M
第五次拆分大小为:min(10G,99128M)=10G
第五次拆分大小为:min(10G,1111128M)=10G
从上面的计算我们可以看到这种策略能够自适应大表和小表,但是这种策略会导致小表产生比较多的小region,对于小表还是不是很完美。
SteppingSplitPolicy
SteppingSplitPolicy是在Hbase 2.0版本后的默认策略,,拆分规则为:If region=1 then: flush size * 2 else: MaxRegionFileSize。
还是以flushsize为128M、maxFileSize为10场景为列,计算出Region的分裂情况如下:
第一次拆分大小为:2*128M=256M
第二次拆分大小为:10G
从上面的计算我们可以看出,这种策略兼顾了 ConstantSizeRegionSplitPolicy 策略和 IncreasingToUpperBoundRegionSplitPolicy 策略,对于小表也肯呢个比较好的适配。
Hbase Region拆分的详细流程
分割区域是在RS上做出的本地决策,但是分割过程本身必须与许多参与者进行协调。
RS在分割之前和之后通知HMaster,更新 .META. 表,以便客户机能够发现新daughter regions,并重新排列HDFS中的目录结构和数据文件。
分割是一个多任务的过程。为了在出现错误时启用回滚,RS在内存中保存关于执行状态的日志。下图显示了由RS执行分割所采取的步骤。每一步都有它的标记
(关于拆分这一段理解的不是很透彻,下面的流程也是翻译,有些地方翻译的不好)
- RegionServer决定在本地分割该区域,并准备分割。作为第一步,它在zookeeper中创建一个znode,该znode位于
/hbase/region-in-transition/region-name
下,处于SPLITTING状态。 - HMaster通过watch节点检测到Region状态的变化,并修改内存中Region状态的变化
- RS在HDFS的父区域目录下创建一个名为
.splits
的子目录。 - RS关闭 parent region,强制刷新缓存,并在其本地数据结构中将该区域标记为offline。此时,到parent region 的client requests请求将抛出NotServingRegionException。客户端将重试一些 some backoff。
- RS在.split目录下为daughter regionsA和daughter regionsB创建目录,并创建必要的数据结构。
然后,分割存储文件,即在parent region中为每个存储文件创建两个引用文件。这些引用文件将指向parent region文件。 - RS在HDFS中创建实际的区域目录,并为每个子目录移动引用文件。
- RS 向 .META. 表发送一个 Put 请求将 parent 设置为下线,并且添加关于daughter regions 的信息。
此时.META.中不会有daughters 的单独条目,Clients 如果扫描 .META.表将会看到parent region 处于split,在daughters出现在.META. 之前我无法知道daughters的信息。
同样的,如果Put请求到.META.成功,那么parent将会被高效分割。 如果在此RPC成功之前的RS失败,则HMaster和下一个打开该区域的RS将清除关于region分割的脏状态。不过,更新.META.之后, region split 将由HMaster前滚。 - RS并行打开子节点以接受写操作。
- RS 将daughters A 和 B及它承载regions信息添加到 .META.。在此之后,客户端可以发现 new region,并向 new region发出请求,Clients本地缓存.META.,但当它们向区域服务器或.META.发出请求时,它们的缓存将失效,它们将从.META. 重新获取new region信息
- RS将zookeeper中的znode
/hbase/region-in-transition/region-name
更新为SPLIT状态,以HMaster可以发现解它。如有必要,平衡器可以自由地将子区域重新分配给其他区域服务器。 - 分割之后,meta和HDFS仍然包含对父区域的引用。当子区域中的压缩重写数据文件时,这些引用将被删除。主程序中的垃圾收集任务定期检查daughter regions是否仍然引用parents files。如果没有,将删除parent region。
Region的合并
Region的合并分为小合并和大合并,下面就分别来做介绍:
小合并(MinorCompaction)
由前面的刷盘部分的介绍,我们知道当MemStore达到hbase.hregion.memstore.flush.size
大小的时候会将数据刷到磁盘,生产StoreFile,因此势必产生很多的小问题,对于Hbase的读取,如果要扫描大量的小文件,会导致性能很;
因此需要将这些小文件合并成大一点的文件。所谓的小合并,就是把多个小的StoreFile组合在一起,形成一个较大的StoreFile,通常是累积到3个Store File后执行 。
通过参数hbase.hstore.compactionThreshold
配置(默认值3)。
小合并的大致步骤为:
- 分别读取出待合并的StoreFile文件的KeyValues,并顺序地写入到位于./tmp目录下的临时文件中
- 将临时文件移动到对应的Region目录中
- 将合并的输入文件路径和输出路径封装成KeyValues写入WAL日志,并打上compaction标记,最后强制自行sync
- 将对应region数据目录下的合并的输入文件全部删除,合并完成
这种小合并一般速度很快,对业务的影响也比较小。
本质上,小合并就是使用短时间的IO消耗以及带宽消耗换取后续查询的低延迟。
大合并(MajorCompaction)
所谓的大合并,就是将一个Region下的所有StoreFile合并成一个StoreFile文件。
在大合并的过程中,之前删除的行和过期的版本都会被删除,拆分的母Region的数据也会迁移到拆分后的子Region上。
大合并一般一周做一次,控制参数为hbase.hregion.majorcompaction。
大合并的影响一般比较大,尽量避免统一时间多个Region进行合并,因此Hbase通过一些参数来进行控制,用于防止多个Region同时进行大合并。该参数为:hbase.hregion.majorcompaction.jitter
RowKey的设计
RowKey = 先导列 + 附加列 + 附加列.....
- 先导列
- 被选作先导列的列,一定是经常被用到的列;
- 应选择设置了EQUALS查询条件的列作为先导列;
- 先导列应该具备较好的离散度;
- 尽量不要重复选择其它索引的先导列作为本索引的先导列。
- 附加列
- 提供了EQUALS查询条件的列,应该放在前面部分;
- 由于列的组合顺序将会影响到数据的排序,我们也应该考虑业务场景关于排序的诉求;
- 几何各个列的离散度进一步分析,将这些组合之后,能否将数据限定在一个合理的范围之内?如果不能,需要结合查询场景设置更合理的组合列。
热点问题解决方案
- Reversing
如果经初步设计出的RowKey在数据分布上不均匀,但RowKey尾部的数据却呈现出了良好的随机性,
此时,可以考虑将RowKey的信息翻转,或者直接将尾部的bytes提前到RowKey的前部。 - Salting
Salting的原理是在原RowKey的前面添加固定长度的随机bytes,随机bytes能保障数据在所有Regions间的负载均衡。
缺点:既然是随机bytes,基于原RowKey查询时无法获知随机bytes信息是什么,也就需要去各个可能的Regions中去查看。可见Salting对于读取是利空的。 - Hashing
基于RowKey的完整或部分数据进行Hash,而后将Hashing后的值完整替换原RowKey或部分替换RowKey的前缀部分。
缺点是与Reversing类似,Hashing也不利于Scan,因为打乱了原RowKey的自然顺序。