Hbase--热点问题

1、现象及原因

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

2、预分区

  • 什么是预分区?
    • 创建表的时候指定这张表有多个分区region
  • 为什么要做预分区?
    • 默认每张表创建只有一个分区,会导致热点问题
  • 如何做预分区呢?
    • 方式一
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’
    • 什么情况下不走索引?
      • 想查询001的数据
        • 全表扫描
      • 想查询1月1号的数据
        • 全表扫描

设计规则

业务原则:必须严格按照业务需求来设计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("水果"))
  • 查询水果中001这个商品的信息
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构建散列,常用的方式
    • 推荐使用不连续的字段作为前缀
      • userid
    • 编码:将连续的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就越多,读写数据时比较的次数也就越多,就越慢
      • 一般建议给两个
      • 经常读写的列放在一个列族
      • 其他的列放在另外一个列族
    • 长度原则:在满足业务的情况下,越短越好
      • 名称只要能区分有标识性就可以了
  • 列标签
    • 就是普通的列的名称
    • 一定要有标示性

猜你喜欢

转载自blog.csdn.net/qq_46893497/article/details/114195149