文章目录

  • 分布式NoSQL列存储数据库HBASE(二)
  • 知识点01:
  • 知识点02:
  • 知识点03:Java API:构建连接
  • 知识点04:Java API:DDL
  • 知识点05:Java API:DML:Table
  • 知识点06:Java API:DML:Put
  • 知识点07:Java API:DML:Get
  • 知识点08:Java API:DML:Delete
  • 知识点09:Java API:DML:Scan
  • 知识点10:Java API:DML:Filter
  • 知识点11:存储设计:存储架构
  • 知识点12:存储设计:Table、Region、RS的关系
  • 知识点13:存储设计:Region的划分规则
  • 知识点14:存储设计:Region的内部结构
  • 知识点15:存储设计:HDFS中的存储结构
  • 知识点16:热点问题:现象及原因
  • 知识点17:分布式设计:预分区
  • 知识点18:Hbase表设计:Rowkey设计
  • 附录一:Maven依赖
  • 附录一:Maven依赖


分布式NoSQL列存储数据库HBASE(二)

知识点01:

  1. Hbase的功能和应用场景是什么?
  • 功能:分布式提供大数据量的随机和实时数据存储【读写】
  • 应用:大数据量、高性能、高并发、按列存储、持久化大数据数据库【结构化或者半结构化】存储
  1. 为什么Hbase可以读写很快而且支持大数据量?
  • 读写很快:内存
  • 大数据量:磁盘
  • Hbase:分布式内存 + 分布式磁盘
  • 将刚写入的数据直接写入内存,等到内存达到一定阈值以后,将内存数据写入HDFS实现持久化存储
  • 写:内存
  • 读:内存 或者 磁盘
  • Hbase为了避免读磁盘很慢?
  • Rowkey索引
  • 二进制文件
  • 构建有序数据存储
  • 列族的设计
  1. Hbase与HDFS和Redis有什么区别?
  • Hbase:实时数据库、大数据量永久性存储
  • HDFS:离线文件系统、大数据量永久性存储
  • Redis:实时数据库、大数据量临时性存储或者小数量永久性存储
  1. 解释以下概念
  • Namespace:就是数据库的概念
  • Table:表的概念,Hbase中的表时分布式的表
  • Rowkey:行健,类似于主键,hbase表都必须自带这一列这一列的值由用户自己设计
  • 唯一标识一行
  • 作为唯一索引
  • 作为分区规则的判断条件:根据rowkey决定数据会写入哪个分区
  • ColumnFamily:列族,列的分组,任何一列都必须属于某个列族,cf:col
  • Qualifier:列标签,列的名称
  • VERSIONS:多版本,Hbase中的某一行的某一列可以存储多个版本的值,通过timestamp
  • Region:分区,实现表的分布式存储的概念
  1. Hbase的架构及角色功能是什么?
  • Hbase:由RegionServer的堆内存构建了分布式内存
  • 分布式主从架构
  • HMaster:管理节点:管理从节点,管理元数据
  • HRegionServer:存储节点:管理Region中所有数据的存储,接受客户端读写请求
  • HDFS:构建分布式磁盘存储
  • Zookeeper:辅助选举,存储管理元数据
  1. Hbase中的常用命令有哪些?
  • 场景一:运维管理,运行hbase脚本
  • hbase shell xxxxx.txt
  • 场景二:开发测试,命令行,DDL
  • namespace:create_namespace,list_namespace,drop_namespace
  • table:create[表名+列族],drop、disable、list、desc、exists
  • put tbname,rowkey,cf:col,value,ts
  • delete tbname,rowkey,cf:col
  • get tbname,rowkey,[cf:col]
  • scan tbname [Filter]
  • 场景三:生产开发,JavaAPI,DML
  1. 反馈问题
  • Hive中的表能不能算是分布式的?
  • 怎样删除一行的某一个列族了?
  • 如果用scan语法的时候有多个过滤器,过滤器的优先级是什么?可否人工进行干预优先级?
  • 优先级:只要将Rowkey的过滤器先加载
  • 逻辑关系:并列,或者

知识点02:

  1. Hbase Java API
  • 应用:Spark/MR 读写Hbase
  • DDL:创建表
  • DML:读写数据 + 过滤器
  1. Hbase存储设计
  • Table、Region、RegionServer三者之间的关系?
  • 分区的规则是什么?如何决定一个Rowkey的数据写入哪个分区?
  • 为什么要设计列族?底层Region内部的存储是什么样的?
  • Hbase存储在HDFS中的数据是什么样的结构?
  1. 热点问题
  • 类似于数据倾斜为
  • 现象、原因、解决方案
  • Rowkey的设计

知识点03:Java API:构建连接

  • 目标:实现Hbase Java API的开发构建连接
  • 实施
//todo:1-构建连接
    Connection conn = null;
    @Before
    public void getConnect() throws IOException {
        //构建配置对象
        Configuration conf = HBaseConfiguration.create();
        //配置Hbase服务端地址:ZK
        conf.set("hbase.zookeeper.quorum","node1:2181,node2:2181,node3:2181");
        //构建连接实例
        conn = ConnectionFactory.createConnection(conf);
    }

    //todo:2-基于连接的方法实现操作

    //todo:3-释放连接
    @After
    public void closeConnect() throws IOException {
        conn.close();
    }
  • 小结
  • 用到了哪些类和方法?
  • Configuration:配置对象
  • Connection:连接对象
  • ConnectionFactory:连接工厂类
  • createConnection(conf)

知识点04:Java API:DDL

  • 目标:使用Hbase Java API实现DDL的管理
  • 实施
  • 构建管理员:Java API中所有的DDL操作都是由管理员对象构建的
//构建管理员对象
    public HBaseAdmin getHbaseAdmin() throws IOException {
        HBaseAdmin admin = (HBaseAdmin) conn.getAdmin();
        return admin;
    }
  • 实现DDL管理
  • 列举、创建、删除NS
  • 列举、创建、删除Table
package bigdata.itcast.cn.hbase.client;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.NamespaceDescriptor;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.client.*;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;import java.io.IOException;
 import java.util.List;/**
• @ClassName HbaseJavaClientDDLTest
• @Description TODO 基于Hbase Java API 实现DDL操作
- 列举、创建、删除NS- 列举、创建、删除TableJava API中所有的DDL操作都是由管理员对象构建的• @Date 2021/6/25 9:54
• @Create By Frank
 */
 public class HbaseJavaClientDDLTest {
 //todo:1-构建连接
 Connection conn = null;
 @Before
 public void getConnect() throws IOException {
 //构建配置对象
 Configuration conf = HBaseConfiguration.create();
 //配置Hbase服务端地址:ZK
 conf.set(“hbase.zookeeper.quorum”,“node1:2181,node2:2181,node3:2181”);
 //构建连接实例
 conn = ConnectionFactory.createConnection(conf);
 }
//todo:2-基于连接的方法实现操作
 //构建管理员对象
 public HBaseAdmin getHbaseAdmin() throws IOException {
 HBaseAdmin admin = (HBaseAdmin) conn.getAdmin();
 return admin;
 }
@Test
 public void listNS() throws IOException {
 HBaseAdmin admin = getHbaseAdmin();
 //实现列举NS
 NamespaceDescriptor[] namespaceDescriptors = admin.listNamespaceDescriptors();
 //打印名称
 for (NamespaceDescriptor namespaceDescriptor : namespaceDescriptors) {
 System.out.println(namespaceDescriptor.getName());
 }
 //关闭管理员
 admin.close();
 }
@Test
 public void delNs() throws IOException {
 HBaseAdmin admin = getHbaseAdmin();
 admin.deleteNamespace(“heima”);
 admin.close();
 }
@Test
 public void createNS() throws IOException {
 HBaseAdmin admin = getHbaseAdmin();
 //创建
 NamespaceDescriptor descriptor = NamespaceDescriptor
 .create(“heima”)//指定NS名称
 .build();
 admin.createNamespace(descriptor);
 admin.close();
 }
@Test
 public void listTables() throws IOException {
 HBaseAdmin admin = getHbaseAdmin();
 //列举所有表
 List tableDescriptors = admin.listTableDescriptors();
 //打印表名
 for (TableDescriptor tableDescriptor : tableDescriptors) {
 System.out.println(tableDescriptor.getTableName().getNameAsString());
 }
 admin.close();
 }
@Test
 public void createTb() throws IOException {
 HBaseAdmin admin = getHbaseAdmin();
 //构建表名对象
 TableName tbname = TableName.valueOf(“itcast:t1”);
 //判断表是否存在
 if(admin.tableExists(tbname)){
 //禁用表
 admin.disableTable(tbname);
 //删除表
 admin.deleteTable(tbname);
 }
 //构建列族对象
 ColumnFamilyDescriptor basic = ColumnFamilyDescriptorBuilder
 .newBuilder(Bytes.toBytes(“basic”))
 .build();
 ColumnFamilyDescriptor other = ColumnFamilyDescriptorBuilder
 .newBuilder(Bytes.toBytes(“other”))
 .setMaxVersions(3)
 .build();
 //创建表
 TableDescriptor desc = TableDescriptorBuilder
 .newBuilder(tbname)
 .setColumnFamily(basic)
 .setColumnFamily(other)
 .build();
 admin.createTable(desc);
 admin.close();
 }
//todo:3-释放连接
 @After
 public void closeConnect() throws IOException {
 conn.close();
 }
 }
  • 小结
  • 用到了哪些类和方法?
  • HbaseAdmin:管理员对象,实现DDL操作
  • listNameSpaceDescriptors
  • listTableDescriptor
  • createNameSpace
  • createTable
  • deleteNameSpace
  • deleteTable
  • dropTable
  • existsTable
  • NameSpaceDescriptor:Namespace的对象
  • TableDescriptor:表的对象
  • setColumnFamily:添加列族的方法
  • TableName:表名对象
  • ColumnFamilyDescriptor:列族的对象
  • setMaxVersions:设置最大版本

知识点05:Java API:DML:Table

  • 目标:使用Hbase Java API实现Table的实例开发
  • 实施
  • DML操作都必须构建Hbase表的对象来进行操作
//构建Hbase表的对象
    public Table getHbaseTable() throws IOException {
        TableName tbname = TableName.valueOf("itcast:t1");
        Table table = conn.getTable(tbname);
        return table;
    }
  • 小结
  • 使用Hbase Java API实现Table的实例开发

知识点06:Java API:DML:Put

  • 目标:使用Hbase Java API实现Put插入或者更新数据
  • 实施
@Test
    public void testPut() throws IOException {
        Table table = getHbaseTable();
        //构建Put对象,一个Put对象表示写入一个Rowkey的数据
        Put put = new Put(Bytes.toBytes("20210101_001"));
        //添加列的信息
        put.addColumn(Bytes.toBytes("basic"),Bytes.toBytes("name"),Bytes.toBytes("laoda"));
        put.addColumn(Bytes.toBytes("basic"),Bytes.toBytes("age"),Bytes.toBytes("18"));
        put.addColumn(Bytes.toBytes("other"),Bytes.toBytes("phone"),Bytes.toBytes("110"));
        //执行Put
        table.put(put);
        table.close();
    }
  • 小结
  • 有哪些类和方法?
  • Table:表的对象
  • put(Put | List)
  • Put:Put对象实例
  • addColumn(列族,列,TS,值)

知识点07:Java API:DML:Get

  • 目标:使用Hbase Java API实现Get读取数据
  • 实施
  • 插入数据
put 'itcast:t1','20210201_000','basic:name','laoda'
put 'itcast:t1','20210201_000','basic:age',18

put 'itcast:t1','20210101_001','basic:name','laoer'
put 'itcast:t1','20210101_001','basic:age',20
put 'itcast:t1','20210101_001','basic:sex','male'

put 'itcast:t1','20210228_002','basic:name','laosan'
put 'itcast:t1','20210228_002','basic:age',22
put 'itcast:t1','20210228_002','other:phone','110'

put 'itcast:t1','20210301_003','basic:name','laosi'
put 'itcast:t1','20210301_003','basic:age',20
put 'itcast:t1','20210301_003','other:phone','120'
put 'itcast:t1','20210301_003','other:addr','shanghai'
  • 实现Get
@Test
    public void testGet() throws IOException {
        Table table = getHbaseTable();
        //构建Get:get tbname,rowkey,[cf:col]
        Get get = new Get(Bytes.toBytes("20210301_003"));
        //配置Get
        get.addFamily(Bytes.toBytes("basic"));//指定列族读取
//        get.addColumn()//指定列读取
        //执行:一个Result代表一个Rowkey的数据
        Result result = table.get(get);
        //打印这个rowkey每一列的结果:一个Cell对象就是一列的对象:20210301_003 column=other:phone, timestamp=1624590747738, value=120
        for(Cell cell : result.rawCells()){
            System.out.println(
                    Bytes.toString(CellUtil.cloneRow(cell)) + "\t" +
                    Bytes.toString(CellUtil.cloneFamily(cell)) + "\t" +
                    Bytes.toString(CellUtil.cloneQualifier(cell)) + "\t" +
                    Bytes.toString(CellUtil.cloneValue(cell)) + "\t" +
                    cell.getTimestamp()
            );
        }
        table.close();
    }
  • 小结
  • 有哪些类和方法?
  • Table:表的对象
  • .get(Get | List)
  • Get:Get对象
  • .addFamily
  • .addColumn
  • Result:一个Result代表一个Rowkey的数据
  • .rawCells:返回所有列的数组
  • Cell:一个Cell代表一列的数据
  • CellUtils:从Cell中取值的工具类
  • cloneValue
  • cloneRow
  • ……

知识点08:Java API:DML:Delete

  • 目标:使用Hbase Java API实现Delete删除数据
  • 实施
@Test
    public void testDel() throws IOException {
        Table table = getHbaseTable();
        //构建Delete
        Delete del = new Delete(Bytes.toBytes("20210301_003"));
        //删除列族
//        del.addFamily()
        //删除列
        del.addColumn(Bytes.toBytes("other"),Bytes.toBytes("phone"));
//        del.addColumns(Bytes.toBytes("other"),Bytes.toBytes("phone"));//删除所有版本
        //执行删除
        table.delete(del);
        table.close();
    }
  • 小结
  • 有哪些类和方法?
  • Delete:删除对象
  • .addFamily:基于列族删除
  • .addColumn:基于列删除
  • Table:表的对象
  • .delete

知识点09:Java API:DML:Scan

  • 目标:使用Hbase Java API实现Scan读取数据
  • 实施
@Test
    public void testScan () throws IOException {
        Table table = getHbaseTable();
        //构建Scan对象
        Scan scan = new Scan();
        //执行scan:ResultScanner用于存储多个Rowkey的数据,是Result的集合
        ResultScanner scanner = table.getScanner(scan);
        //取出每个Rowkey的数据
        for (Result result : scanner) {
            //先打印当前这个rowkey的内容
            System.out.println(Bytes.toString(result.getRow()));
            for(Cell cell : result.rawCells()){
                System.out.println(
                        Bytes.toString(CellUtil.cloneRow(cell)) + "\t" +
                                Bytes.toString(CellUtil.cloneFamily(cell)) + "\t" +
                                Bytes.toString(CellUtil.cloneQualifier(cell)) + "\t" +
                                Bytes.toString(CellUtil.cloneValue(cell)) + "\t" +
                                cell.getTimestamp()
                );
            }
            System.out.println("------------------------------------------------------------");
        }
        table.close();
    }
  • 小结
  • 有哪些类和方法?
  • Scan:查询整表数据的对象
  • Table:表的对象
  • .getScanner(Scan)
  • ResultScanner:多个Rowkey的数据
  • Result:单个Rowkey的数据
  • Cell:每个列的数据

知识点10:Java API:DML:Filter

  • 目标:使用Hbase Java API实现Scan + Filter过滤
  • 实施
  • 需求:JavaAPI实现从Hbase表中根据条件读取部分
  • 需求1:查询2021年1月和2月的数据
  • 需求2:查询2021年的所有数据
  • 需求3:查询所有age = 20的数据
  • 需求4:查询所有数据的name和age这两列
  • 需求5:查询所有年age = 20的人的name和age
  • 实现
@Test
    public void testScan () throws IOException {
        Table table = getHbaseTable();
        //构建Scan对象
        Scan scan = new Scan();
        /**
         * todo:配置Scan实现过滤操作
         * - 需求1:查询2021年1月和2月的数据
         *          select * from tb where substr(time,0,7) >= 202101 and time <= 202102
         * - 需求2:查询2021年的所有数据
         *          select * from tb where substr(time,0,4) = 2021
         * - 需求3:查询所有age = 20的数据
         *          select * from tb where age = 20
         * - 需求4:查询所有数据的name和age这两列
         *          select name,age from tb;
         * - 需求5:查询所有年age = 20的人的name和age
         *          select name,age from tb where age = 20
        */
        //startrow and stoprow 实现Rowkey范围过滤,如果不是Rowkey的范围过滤:RowFilter、FamilyFilter、QualifierFilter、ValueFilter
//        scan.withStartRow(Bytes.toBytes("202101"));
//        scan.withStopRow(Bytes.toBytes("202103"));
        //Rowkey前缀过滤器
//        Filter prefixFilter = new PrefixFilter(Bytes.toBytes("202102"));
        //单列列值过滤器
        Filter singleColumnValueFilter = new SingleColumnValueFilter(
                Bytes.toBytes("basic"),
                Bytes.toBytes("age"),
                CompareOperator.EQUAL,
                Bytes.toBytes("20")
        );
        //多列前缀过滤器
        byte[][] prefixes = {
                Bytes.toBytes("name"),
                Bytes.toBytes("age")
        };
        Filter multiColumnPrefixFilter = new MultipleColumnPrefixFilter(prefixes);

        //构建过滤器集合
        FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ONE);
        filterList.addFilter(singleColumnValueFilter);
        filterList.addFilter(multiColumnPrefixFilter);
        //Scan加载过滤器
        scan.setFilter(filterList);

        //执行scan:ResultScanner用于存储多个Rowkey的数据,是Result的集合
        ResultScanner scanner = table.getScanner(scan);
        //取出每个Rowkey的数据
        for (Result result : scanner) {
            //先打印当前这个rowkey的内容
            System.out.println(Bytes.toString(result.getRow()));
            for(Cell cell : result.rawCells()){
                System.out.println(
                        Bytes.toString(CellUtil.cloneRow(cell)) + "\t" +
                                Bytes.toString(CellUtil.cloneFamily(cell)) + "\t" +
                                Bytes.toString(CellUtil.cloneQualifier(cell)) + "\t" +
                                Bytes.toString(CellUtil.cloneValue(cell)) + "\t" +
                                cell.getTimestamp()
                );
            }
            System.out.println("------------------------------------------------------------");
        }
        table.close();
    }
  • 小结
  • 有哪些常用的Filter?
  • Rowkey范围过滤:startrow,stoprow
  • Rowkey前缀过滤:PrefixFilter
  • 列值过滤:SingleColumnValueFilter
  • 列的过滤:MultipleColumnPrefixFilter
  • 组合过滤:FilterList
  • 方法:scan.setFilter

知识点11:存储设计:存储架构

  • 目标:掌握Hbase的存储架构
  • 实施
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ju16nRpy-1624621141663)(Day27_分布式NoSQL列存储数据库HBASE(二).assets/image-20210524170928749.png)]
  • 问题:Hbase整体如何实现数据的存储?
  • 分析
  • Client:负责连接服务端
  • 提交用户操作给服务端执行
  • 将服务端执行的结果返回给用户
  • Zookeeper:存储Hbase管理元数据
  • Hbase中的有哪些Master、RegionServer
  • Hbase:分布式内存
  • RegionServer的Java堆内存
  • HDFS:分布式磁盘
  • 当RegionServer的内存达到一定阈值,会将内存中的数据写入HDFS
  • 小结
  • 掌握Hbase的存储架构

知识点12:存储设计:Table、Region、RS的关系

  • 目标:掌握Hbase中Table与Region、RS三者之间的关系
  • 实施
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UReIocxr-1624621141665)(Day27_分布式NoSQL列存储数据库HBASE(二).assets/image-20210524171650278.png)]
  • 问题:客户端操作的是表,数据最终存在RegionServer中,表和RegionServer的关系是什么?
  • client:put ns:tbname rowkey cf col value
  • 分析
  • Table:是一个逻辑对象,物理上不存在,供用户实现逻辑操作,存储在元数据的一个概念
  • 类似于HDFS中文件
  • 数据写入表以后的物理存储:分区
  • 一张表会有多个分区Region,每个分区存储在不同的机器上
  • 默认每张表只有1个Region分区
  • Region:Hbase中数据存储的最小单元
  • 类似于HDFS中Block,用于实现Hbase中分布式
  • 就是分区的概念,每张表都可以划分为多个Region,实现分布式存储
  • 默认一张表只有一个分区
  • 每个Region由一台RegionServer所管理,Region存储在RegionServer
  • 一台RegionServer可以管理多个Region
  • RegionServer:是一个物理对象,Hbase中的一个进程,管理一台机器的存储
  • 类似于HDFS中DataNode
  • 一个Regionserver可以管理多个Region
  • 一个Region只能被一个RegionServer所管理
  • 观察监控
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FrlU1tL9-1624621141666)(Day27_分布式NoSQL列存储数据库HBASE(二).assets/image-20210525114439044.png)]
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mKtmpRs3-1624621141666)(Day27_分布式NoSQL列存储数据库HBASE(二).assets/image-20210525114753868.png)]
itcast:t1,,1624588882208.2aa671a470e2f179a86c2fe662d1a910.
表名,这个Region开始范围,时间戳.Region的唯一编号
  • 小结
  • Hbase中Table与Region、RS三者之间的关系是什么?
  • Table:分布式表,逻辑概念
  • 一张表可以有多个Region,默认只有1个
  • RegionServer:存储节点,物理概念,进程
  • 每台RegionServer管理多个Region

知识点13:存储设计:Region的划分规则

  • 目标掌握Hbase中表的Region的划分规则
  • 实施
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2ZXHaJdA-1624621141667)(Day27_分布式NoSQL列存储数据库HBASE(二).assets/image-20210524171757479.png)]
  • 问题:一张表划分为多个Region,划分的规则是什么?写一条数据到表中,这条数据会写入哪个Region,分配规则是什么?
  • 分析
  • 回顾:HDFS划分规则
  • 划分分区的规则:按照大小划分,文件按照每128M划分一个Block
  • Hbase分区划分规则范围划分【根据Rowkey范围】
  • 任何一个Region都会对应一个范围
  • 如果只有一个Region,范围:-oo ~ +oo
  • 范围划分:从整个-oo ~ +oo区间上进行范围划分
  • 每个分区都会有一个范围:根据Rowkey属于哪个范围就写入哪个分区
[startKey,stopKey)
  • 前闭后开区间
  • 默认:一张表创建时,只有一个Region
  • 范围:-oo ~ +oo
  • 自定义:创建表时,指定有多少个分区,每个分区的范围
  • 举个栗子:创建一张表,有2个分区Region
  • region0:-oo ~ 50
  • region1:50 ~ +oo
  • 数据分配的规则:根据Rowkey属于哪个范围就写入哪个分区
  • 举个栗子:创建一张表,有4个分区Region,20,40,60
  • region0:-oo ~ 20
  • region1:20 ~ 40
  • region2:40 ~ 60
  • region3:60 ~ +oo
  • 写入数据的rowkey:比较是按照ASC码比较的,不是数值比较
  • A1234:region3
  • c6789:region3
  • 00000001:region0
  • 2:region1
  • 99999999:region3
  • 9:region3
  • 问题
Hbase中对数值构建的序列:按照ASC码表实现的
1
11
2
23
4
如果我想基于数值构建有序:Rowkey补0
01
02
04
11
23
  • 观察监控
  • 默认只有1个分区
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JHSJoUjI-1624621141669)(Day27_分布式NoSQL列存储数据库HBASE(二).assets/image-20210525120031461.png)]
  • 注意:随着数据越来越多,达到阈值,这个分区会自动分裂为两个分裂
  • 手动创建多个分区
create 'itcast:t3','cf',SPLITS => ['20', '40', '60', '80']

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uBiRVVqI-1624621141670)(Day27_分布式NoSQL列存储数据库HBASE(二).assets/image-20210525120214465.png)]

  • 写入数据
put 'itcast:t3','0300000','cf:name','laoda'
put 'itcast:t3','7890000','cf:name','laoer'

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jy12q16K-1624621141670)(Day27_分布式NoSQL列存储数据库HBASE(二).assets/image-20210525120338729.png)]

  • 小结
  • 掌握Hbase中表的Region的划分规则是什么?
  • 如何划分每个Region的范围:根据Rowkey的前缀
  • 如果决定数据写入哪个Region:根据Rowkey的前缀,属于哪个region的范围就写入哪个region中

知识点14:存储设计:Region的内部结构

  • 目标掌握Region的内部存储结构
  • 实施
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ENVvhHhB-1624621141670)(Day27_分布式NoSQL列存储数据库HBASE(二).assets/image-20210524171934125.png)]
  • 问题:数据在Region的内部是如何存储的?
put tbname,rowkey,cf:col,value
  • tbname:决定了这张表的数据最终要读写哪些分区
  • rowkey:决定了具体读写哪个分区
  • cf:决定具体写入哪个Store
  • 分析
  • Table/RegionServer:数据指定写入哪张表,提交给对应的某台regionserver
  • Region:对整张表的数据划分,按照范围划分,实现分布式存储
  • Store:对分区的数据进行划分,按照列族划分,一个列族对应一个Store
- 不同列族的数据写入不同的Store中,实现了按照列族将列进行分组
- 根据用户查询时指定的列族,可以快速的读取对应的store
  • MemStore:每个Store都有一个,内存存储区域
  • StoreFile:每个Store中可能有0个或者多个StoreFile文件
  • 逻辑:属于Store的
  • 物理:StoreFile是存储在HDFS中的HFILE【二进制】文件
  • 小结
  • Region的内部存储结构是什么样的?
  • RegionServer
  • Region:表的数据的划分
  • Store:分区数据的划分,按照列族划分
  • memstore:一个memstore
  • storefile:memstore满了,将memstore的数据写入HDFS变成storefile文件,0个或者多个

知识点15:存储设计:HDFS中的存储结构

  • 目标掌握Hbase在HDFS中的存储结构
  • 实施
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EYNpQLSt-1624621141671)(Day27_分布式NoSQL列存储数据库HBASE(二).assets/image-20210524172229613.png)]
  • 问题:Hbase的数据在HDFS中是如何存储的?
  • 分析
  • 整个Hbase在HDFS中的存储目录

hbase.rootdir=hdfs://node1:8020/hbase

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CvozeaEo-1624621141671)(Day27_分布式NoSQL列存储数据库HBASE(二).assets/image-20210625154845286.png)]



- NameSpace:目录结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CA4bXxrj-1624621141672)(Day27_分布式NoSQL列存储数据库HBASE(二).assets/image-20210625154914849.png)]



- Table:目录结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WCsVewJh-1624621141672)(Day27_分布式NoSQL列存储数据库HBASE(二).assets/image-20210625154949303.png)]



- Region:目录结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9id2B91f-1624621141673)(Day27_分布式NoSQL列存储数据库HBASE(二).assets/image-20210625155037410.png)]



- Store/ColumnFamily:目录结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lUvwSJnz-1624621141674)(Day27_分布式NoSQL列存储数据库HBASE(二).assets/image-20210625155222749.png)]



- StoreFile

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AY1Kyykk-1624621141674)(Day27_分布式NoSQL列存储数据库HBASE(二).assets/image-20210625155242858.png)]

- 如果HDFS上没有storefile文件,可以通过flush,手动将表中的数据从内存刷写到HDFS中

  ```
  flush 'itcast:t3'
  ```
  • 小结
  • 掌握Hbase在HDFS中的存储结构

知识点16:热点问题:现象及原因

  • 目标掌握热点问题的现象及原因
  • 实施
  • 现象
在某个时间段内,大量的读写请求全部集中在某个Region中,导致这台RegionServer的负载比较高,其他的Region和RegionServer比较空闲
  • 问题:这台RegionServer故障的概率就会增加,整体性能降低,效率比较差
  • 原因:本质上的原因,数据分配不均衡
  • 情况一:如果这张表只有一个分区
  • 所有数据都存储在一个分区中,这个分区要响应所有读写请求,出现了热点
  • 情况二:如果这张表有多个分区,而且你的Rowkey写入时是连续的
  • 一张表有5个分区
region0:-oo  20
region1:20   40
region2:40   60
region3:60    80
region4:80    +oo
  • 000001:region0
  • 000002:region0
  • ……
  • 199999:region0
  • 都写入了同一个region0分区
  • 200000:region1
  • 200001:region1
  • ……
  • 399999:region1
  • 解决:避免热点的产生
  • 构建多个分区
  • 构建不连续的rowkey
  • 小结
  • 掌握热点问题的现象及原因

知识点17:分布式设计:预分区

  • 目标:实现建表时指定多个分区
  • 实施
  • 需求:在创建表的时候,指定一张表拥有多个Region分区
  • 规则
  • 划分的目标:划分多个分区,实现分布式并行读写,将无穷区间划分为几段,将数据存储在不同分区中,实现分区的负载均衡
  • 划分的规则Rowkey或者Rowkey的前缀来划分
  • 如果不按照这个规则划分,预分区就可能没有作用
  • Rowkey:00 ~ 99
  • region0: -oo ~ 30
  • ……
  • regionN : 90 ~ +oo
  • 如果分区的设计不按照rowkey来
  • region0:-oo ~ b
  • region1: b ~ g
  • ……
  • regionN:z ~ +oo
  • 实现
  • 方式一:指定分隔段,实现预分区
  • 前提:先设计rowkey
create 'ns1:t1', 'f1', SPLITS => ['10', '20', '30', '40']
#将每个分割的段写在文件中,一行一个

create ‘t1’, ‘f1’, SPLITS_FILE => ‘splits.txt’

- 方式二:指定Region个数,自动进行Hash划分:字母和数字的组合

```shell
#你的rowkey的前缀是数字和字母的组合 
create 'itcast:t4', 'f1', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EFWvEiaP-1624621141674)(Day27_分布式NoSQL列存储数据库HBASE(二).assets/image-20210525152034069.png)]

  • 方式三:Java API
HBASEAdmin admin = conn.getAdmin
admin.create(表的描述器对象,byte[][] splitsKey)
  • 小结
  • 实现建表时指定多个分区

知识点18:Hbase表设计:Rowkey设计

  • 目标掌握Hbase表的Rowkey的设计规则
  • 实施
  • 功能
  • 唯一标记一条数据
  • 唯一索引:rowkey的前缀是什么,决定了可以按照什么条件走索引查询
  • Region的划分,数据的分区划分
  • 需求:根据不同业务需求,来合理的设计rowkey,实现高性能的数据存储
  • 分析:不同的业务需求的表,Rowkey设计都不一样
  • 设计规则
  • 业务原则:Rowkey的设计必须贴合业务的需求,一般选择最常用的查询条件作为rowkey的前缀
  • 举个栗子:一般最常用的查询条件肯定是时间
  • timestamp_userid_orderid:订单表
  • 所有时间的查询都是走索引的
  • 唯一原则:Rowkey必须具有唯一性,不能重复,一个Rowkey唯一标识一条数据
  • 组合原则:将更多的经常作为的查询条件的列放入Rowkey中,可以满足更多的条件查询可以走索引查询
  • 举个栗子:一般最常用的查询条件肯定是时间
  • timestamp_userid_orderid:订单表
  • 查询条件:订单id、用户id、时间、价格、商品id
  • 走索引查询
  • timestamp
  • timestamp_userid
  • timestamp_userid_orderid
  • 不走索引:userid、orderid
  • 散列原则:为了避免出现热点问题,需要将数据的rowkey生成规则,构建散列的rowkey
  • 举个栗子:一般最常用的查询条件肯定是时间
  • timestamp_userid_orderid:订单表
1624609420000_u001_o001
1624609420001_u002_o002
1624609420002_u003_o003
1624609421000_u001_o004
……
  • 预分区:数值
  • region0:-oo ~ 1624
  • region1:1624 ~ 1924
  • region2:1924 ~ 2100
  • region3:2100 -2400
  • region4:2400 ~ +oo
  • 问题:出现热点
  • 解决:构建散列
  • 方案一:更换不是连续的字段作为前缀,例如用户id
  • 优点:构建散列,数据存储相对均衡
  • 缺点:必须以前缀的字段作为查询条件
  • 方案二:反转
  • 一般用于时间作为前缀,查询时候必须将数据反转再查询
0000249064261_u001_o001
1000249064261_u002_o002
2000249064261_u003_o003
0010249064261_u001_o004
……
- region0:-oo  ~ 2
- region1:2  ~ 4
- region2:4 ~ 6
- region3:6  ~ 8
- region4:8 ~ +oo
  • 方案三:加盐(Slat),本质对数据进行编码
1624609420000_u001_o001
1624609420001_u002_o002
1624609420002_u003_o003
1624609421000_u001_o004
|
df34343jed_u001_o001
09u9jdjkfd_u002_o002
  • 缺点:查询时候,也必须对查询条件加盐以后再进行查询
  • 长度原则:在满足业务需求情况下,rowkey越短越好,一般建议Rowkey的长度小于100字节
  • 原因:rowkey越长,比较性能越差,rowkey在底层的存储是冗余的
  • 问题:为了满足组合原则,rowkey超过了100字节怎么办?
  • 解决:实现编码,将一个长的rowkey,编码为8位,16位,32位
  • 小结
  • Rowkey的设计要符合哪些设计原则?
  • 业务原则:使用最常用的查询条件作为rowkey的前缀
  • 唯一原则:一个rowkey唯一标识一条数据
  • 组合原则:尽量将多个常用查询条件放在rowkey中
  • 散列原则:构建不连续的rowkey
  • 选择不连续的字段作为前缀、反转、加盐
  • 长度原则:保证业务的情况下,越短越好,100字节以内

附录一:Maven依赖

<repositories>
        <repository>
            <id>aliyun</id>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
        </repository>
    </repositories>
    <properties>
        <hbase.version>2.1.2</hbase.version>
    </properties>
    <dependencies>
        <!-- Hbase Client依赖 -->
        <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId>hbase-client</artifactId>
            <version>${hbase.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId>hbase-server</artifactId>
            <version>${hbase.version}</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

:为了满足组合原则,rowkey超过了100字节怎么办?
- 解决:实现编码,将一个长的rowkey,编码为8位,16位,32位

  • 小结
  • Rowkey的设计要符合哪些设计原则?
  • 业务原则:使用最常用的查询条件作为rowkey的前缀
  • 唯一原则:一个rowkey唯一标识一条数据
  • 组合原则:尽量将多个常用查询条件放在rowkey中
  • 散列原则:构建不连续的rowkey
  • 选择不连续的字段作为前缀、反转、加盐
  • 长度原则:保证业务的情况下,越短越好,100字节以内

附录一:Maven依赖

<repositories>
        <repository>
            <id>aliyun</id>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
        </repository>
    </repositories>
    <properties>
        <hbase.version>2.1.2</hbase.version>
    </properties>
    <dependencies>
        <!-- Hbase Client依赖 -->
        <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId>hbase-client</artifactId>
            <version>${hbase.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId>hbase-server</artifactId>
            <version>${hbase.version}</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>