1、现象及原因
- 某一时间内,大量读写请求都集中在某个region或者某台regionserver中,导致这台regionserver的负载非常高,其他regionserver非常的空闲
- 本质:
Hbase的请求负载不均衡
- 原因1:
一张表只有一个region,没有构成分布式:
- 导致对于这张表所有的请求都写入一个region,请求了一台regionserver
- 解决
- 原因2:
rowkey设计的不合理
- 解决
- 根据rowkey的值来划分每个分区的范围
让数据均匀的分布在多个分区中
- 我们使用序列来写入
- 这种方式也不行,在写入每个1万条时,还是热点
- 一定要构建随机或者轮询的方式来写入不同的分区

2、预分区
- 什么是预分区?
- 为什么要做预分区?
- 如何做预分区呢?
create 'ns1:t1', 'f1', SPLITS => ['10', '20', '30', '40']
create 'tbsplit1','info',SPLITS => ['10', '20', '30', '40','50','60','70','80','90']

create 't1', 'f1', SPLITS_FILE => '/export/datas/splits.txt'
create 'tbsplit2', 'info', SPLITS_FILE => '/export/datas/splits.txt'
JavaAPI
admin.createTable(HTableDescripor desc ,byte[][] splitKeys);
- 注意:预分区必须根据rowkey的值来设计,先设计rowkey,然后再预分区
- 如果实现预分区,但是分区的范围与rowkey对应的值的分布不匹配
- 依旧会产生热点
- 一定要根据rowkey的前缀来设计预分区
- 如何在Linux的命令行中执行Hbase的命令?
- 如何封装Hbase的自动化脚本?
hbase shell file_path
hbase shell /export/datas/command.hbase
- /export/datas/command.hbase :把每一行的 命令写进去,最后要以exit结尾
- 主要用于运维Hbase
3、Rowkey的设计规则
重要性
- 所有数据的存储都是根据rowkey来实现读写对应的分区
- rowkey要实现
唯一性、进行排序
- rowkey是整个Hbase中的
唯一索引
- 数据查询时,如果不走索引就走全表扫描
- 在工作中要尽量让查询走索引
- get:肯定走索引,必须指定rowkey
- scan:rowkey查询符合前缀匹配
- rowkey的前缀是什么,就决定了可以按照什么来走索引查询
- rowkey:20200101_001
- 按照日期走索引查询
- 查询2020年所有的数据
- scan ‘tbname’,{STARTROW=>‘2020’ STOPROW=>‘2021’}
- 按照日期和用户id走索引查询
- 查询2020年1月1号用户001的数据
- get ‘tbname’,‘20200101_001’
- 什么情况下不走索引?
设计规则
业务原则:必须严格按照业务需求来设计rowkey
- 有别于传统数据库的设计
主键:只要有一列能区分每一行的唯一性,就可以作为主键
- 自增int类型
- 学生id
- 学生身份证号码
- 准考证号码
Hbase的rowkey不仅仅只有唯一性,还要考虑业务
- 用什么作为rowkey的前缀,就可以走索引查询
将最常用的查询条件作为前缀
- 例如:经常按照日期查询这张表,就用日期作为rowkey前缀
唯一原则:每个rowkey,唯一标识一条数据
组合原则:根据业务需求,将经常被查询的列放在rowkey中,共同构成rowkey
- 在查询时,
将最常用的一些查询条件的列,放在rowkey中,让常用查询可以走索引
- 本身数据中还是有这些列的
- userid/time/orderid/productid
- 商品表
- rowkey:type_productid_name
- 水果 _ 001_ 荔枝
- type:水果
- productid:001
- name:荔枝
- 颜色
- 价格
- 水果 _ 002_ 西瓜
- 数码 _ 003 _ 手机
- 查询所有的水果
scan.set(new PrefiexFilter("水果"))
scan.set(new PrefiexFilter("水果_001"))
- 订单表
- 后台:rowkey:orderId_userId_timestamp
- ddfkjdlkfjdj_001_1593570797
- userid
- serverTime
- orderid
- price
- productid
- paytype
- ……
- dffjklfksdss_002_1593570797
- fklfjklfsklds_003_1593570797
- dfdifjdkfjdd_004_1593570798
- dffdsdseerd_001_1593570799
- 满足索引的
- 查询某个订单的信息
- 查询某个订单对应用户在某个时间的信息
- 如果我只知道时间,能不能查询?
- 可以查询,但是不走索引,走全表扫描,对时间这一列进行过滤
- scan tbname {valuefilter {serverTime= 20200101}}
- 因为时间在rowkey中,但不是前缀,不能做前缀匹配的索引查询
- 用户:rowkey:userId_timestamp_orderId
- 001_1593570797_ddfkjdlkfjdj
- userid
- serverTime
- orderid
- price
- productid
- paytype
- ……
- 001_1593570799_dffdsdseerd
- 002_1593570797_dffjklfksdss
- 003_1593570797_fklfjklfsklds
- 004_
- 满足索引查询
- 用户登录,能查询所有的订单
- 用户登录,根据用户名查询这个用户在任何一个时间的订单
散列原则:必须构建rowkey的随机散列,不允许rowkey是连续的
- rowkey:时间 _ 订单Id _ 用户id
- 1593570797_dffjklfksdss_002
- 1593570797_fklfjklfsklds_003
- 1593570798_dfdifjdkfjdd_003
- 1593570799_dffdsdseerd_001
- 1593570800
- 1593570801
- 1593570802
- ……
- 1600000000:
- |
- 2000000000
- 按照时间作为前缀,写入表中
因为region是有序的,如果rowkey也是有序的,必然会产生热点
- 必须将rowkey构建散列,常用的方式
- 推荐使用
不连续的字段作为前缀
编码
:将连续的rowkey编码以后作为rowkey
- 1593570797:12qwert4
- 1593570798:asdfg789
- 读取数据时需要解码
- MD5、CRC32
- 8位、16位、32位
反转
:读取时需要再反转回来
- 0080753951
- 1080793951
- 2080753951
- 3080753951
- 4080753951
- 5080753951
- ……
- 9080753951
- 0180753951
- 1180753951
加随机数
- 不推荐,给定一个固定的随机范围0-9
- 0_1593570797_dffjklfksdss_002
- 9_1593570797_dffjklfksdss_003
- 牺牲很大的读取的代价
长度规则:建议rowkey的长度不超过100字节
- 如果rowkey越长,在底层进行查询比较时候就比较慢
- 在满足业务需求的情况下,越短越好
rowkey在底层每一列的存储中都有,是冗余的
- 如果长度真的无法缩短,
可以使用编码变成16位或者32位的rowkey
列族以及列标签的设计
- 列族
- 个数原则:
一般不建议超过三个
- 列族的个数越多,
每个region中的store就越多,读写数据时比较的次数也就越多,就越慢
- 一般建议给两个
经常读写的列放在一个列族
其他的列放在另外一个列族
- 长度原则:在满足业务的情况下,越短越好
- 列标签