HBase 学习二(最佳实践).
一、Rowkey 优化
Rowkey 是行的主键,它是以字典顺序排序的。所以 Rowkey 的设计是至关重要的, 关系到你应用层的查询效率。
整规化 Rowkey
有时作为 Rowkey 的字段长度不一样,比如 user_id, 而通过对 Rowkey 进行规整化,能够避免 Rowkey 长度不一致,导致每次请求返回的数据量不一,可将组合的 Rowkey 映射成等长 hash 值。
编码 Rowkey
如果 Rowkey 是以字符串形式保存,比如日期格式(yyyy-MM-dd HH:mm:ss),会造成大量存储空间的浪费, 因此可以对字符串进行数值编码,将编码后的数保存到 Rowkey 中。
高基维度
如果 Rowkey 由多个字段组成,需要把高基维度放到最前面,也就是 distinct 的字段数量在千万以上,比如 user_id 放到前面,这样字段能在过滤中起到很大作用、大幅缩小查询范围。
加盐
如果组合 Rowkey 的第一部分是时间戳,HBase又是按照 Rowkey 排序的,很可能邻近的数据存到一个 HRegionServer 里,考虑到最新的数据访问频率最高,将导致某个 HRegionServer 读请求负载过重,产生热点问题,一个可行的方案是 Rowkey 前缀加随机数,这可以保证数据均匀分布,但对数据读取造成了麻烦。和加盐类似的方案有对 Rowkey 进行哈希、翻转Rowkey (经常改变的部分放到前面)等。
二、数据热点问题
数据热点问题:热点发生在大量的 client 直接访问集群的一个或极少数个节点(访问可能是读, 写或者其他操作)。大量访问会使热点 HRegion 所在的单个机器超出自身承受能力,引起性能下降甚至 HRegion 不可用,这也会影响同一个 HRegionServer 上的其他HRegion,由于主机无法服务其他 HRegion 的请求,造成资源浪费。设计良好的数据访问模式以使集群被充分、均衡的利用。
可以通过下面方式解决 HRegion 热点问题:
-
Reverse反转:针对固定长度的 Rowkey 反转后存储,这样可以使 Rowkey 中经常改变的部分放在最前面,可以有效的随机 Rowkey。例如手机号比较固定开头(138、139)的热点问题。
-
预分区/Salt加盐:Salt 是将每一个 Rowkey 加一个前缀,前缀使用一些随机字符,使得数据分散在多个不同的 HRegion ,达到 HRegion 负载均衡的目标。比如在一个有 4 个 HRegion (注: 以 [ ,a)、 [a,b)、 [b,c)、 [c, )为 HRegion 起止) 的 HBase 表中, 加 Salt 前的 Rowkey:abc001、abc002、abc003 我们分别加 a、 b、 c 前缀,加 Salt 后 Rowkey 为:aabc001、b-abc002、c-abc003。可以看到,加盐前的 Rowkey 默认会在第2个 HRegion 中, 加盐后的 Rowkey 数据会分布在3个 HRegion 中,理论上处理后的吞吐量应是之前的3倍。
-
Hash 散列或者 Mod:Hash 散列来替代随机 Salt 前缀的好处是能让一个给定的行有相同的前缀,这在分散了 HRegion 负载的同时,使读操作也能够推断。确定性Hash(比如 md5 后取前4位做前缀)能让客户端重建完整的 RowKey,可以使用 get 操作直接 get 想要的行。如果 Rowkey 是数字类型的,也可以考虑 Mod 方法。
三、HBase三维有序
Hfile 是 HBase 中 KeyValue 数据的存储格式。从 HBase 物理数据模型中可以看出,HBase 是面向列表(簇)的存储。每个 Cell 由 {row key,column(=< family> + < label>),version}
唯一确定的单元,他们组合在一起就是一个 KeyValue。 根据上述描述,这个 KeyValue 中的 key 就是 {row key,column(=< family> + < label>),version}
,而 value 就是 cell 中的值。
HBase 的三维有序存储中的三维是指:rowkey( 行主键),column key(columnFamily+< label>), timestamp(时间戳或者版本号) 三部分组成的三维有序存储。
Rowkey:我们在根据 rowkey 范围查询的时候,我们一般是知道 startRowkey,如果我们通过 scan 只传 startRowKey:d开头的,那么查询的是所有比 d 大的都查了,而我们只需要 d 开头的数据,那就要通过 endRowKey 来限制。我们可以通过设定 endRowKey 为: d 开头,后面的根据你的 rowkey 组合来设定,一般是加比 startKey 大一位。
column key:column key 是第二维,数据按 rowkey 字典排序后,如果 rowkey 相同,则是根据 column key 来排序的,也是按字典排序。我们在设计 table 的时候要学会利用这一点。比如我们的收件箱,有时候需要按主题排序,那我们就可以把主题设置为我们的 column key,即设计为“columnFamily+主题”这样的设计。
timestamp:时间戳,是第三维,这是个按降序排序的,即最新的数据排在最前面。
四、写入优化
- put 是否可以批量提交:使用批量 put 接口可以减少客户端到 HRegionServer 之间的 RPC 连接数,提高写入性能。
- put 是否可以异步提交:业务如果可以接受异常情况下少量数据丢失的话,还可以使用异步批量提交的方式提交请求。提交分为两阶段执行:用户提交写请求之后,数据会写入客户端缓存,并返回用户写入成功;当客户端缓存达到阈值(默认2M)之后批量提交 HRegionServer。需要注意的是,在某些情况下客户端异常的情况下缓存数据有可能丢失。使用方式:setAutoFlush(false)。
- 写入请求是否不均衡:如果不均衡,一方面会导致系统并发度较低,另一方面也有可能造成部分节点负载很高,进而影响其他业务。分布式系统中特别害怕一个节点负载很高的情况,一个节点负载很高可能会拖慢整个集群,这是因为很多业务会使用批量提交读写请求,一旦其中一部分请求落到该节点无法得到及时响应,就会导致整个批量请求超时。
- 写入 KeyValue 数据是否太大:KeyValue 大小对写入性能的影响巨大,一旦遇到写入性能比较差的情况,可以考虑是否由于写入 KeyValue 数据太大导致。
五、查询优化
- Get 是否可以批量请求:可以减少客户端到 HRegionServer 之间的 RPC 连接数,提高查询性能。
- 大 scan 缓存是否设置合理:scan 一次需要从服务端返回大量的数据,客户端发起一次请求,服务端会分多批次返回客户端,这样的设计是避免一次性传输较多的数据给服务端及客户端产生较大的压力。目前数据会加载到本地的缓存中,默认100条数据大小。一些大 scan 需要获取大量的数据,传输数百次甚至数万的 rpc 请求。这种情况我们建议可以适当放开缓存的大小。
- 请求指定列簇或者列名:HBase 是列簇数据库,同一个列簇的数据存储在一块,不同列簇是分开的,为了减小 IO,建议指定列簇或者列名。
- 离线计算访问 HBase 建议禁止缓存:当离线访问 HBase 时,往往就是一次性的读取,此时读取的数据没有必要存放在 blockcache 中,建议在读取时禁止缓存。使用方式:setBlockCache(false)。