文章目录
- 分布式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:
- Hbase的功能和应用场景是什么?
- 功能:分布式提供大数据量的随机和实时数据存储【读写】
- 应用:大数据量、高性能、高并发、按列存储、持久化大数据数据库【结构化或者半结构化】存储
- 为什么Hbase可以读写很快而且支持大数据量?
- 读写很快:内存
- 大数据量:磁盘
- Hbase:分布式内存 + 分布式磁盘
- 将刚写入的数据直接写入内存,等到内存达到一定阈值以后,将内存数据写入HDFS实现持久化存储
- 写:内存
- 读:内存 或者 磁盘
- Hbase为了避免读磁盘很慢?
- Rowkey索引
- 二进制文件
- 构建有序数据存储
- 列族的设计
- Hbase与HDFS和Redis有什么区别?
- Hbase:实时数据库、大数据量永久性存储
- HDFS:离线文件系统、大数据量永久性存储
- Redis:实时数据库、大数据量临时性存储或者小数量永久性存储
- 解释以下概念
- Namespace:就是数据库的概念
- Table:表的概念,Hbase中的表时分布式的表
- Rowkey:行健,类似于主键,hbase表都必须自带这一列这一列的值由用户自己设计
- 唯一标识一行
- 作为唯一索引
- 作为分区规则的判断条件:根据rowkey决定数据会写入哪个分区
- ColumnFamily:列族,列的分组,任何一列都必须属于某个列族,cf:col
- Qualifier:列标签,列的名称
- VERSIONS:多版本,Hbase中的某一行的某一列可以存储多个版本的值,通过timestamp
- Region:分区,实现表的分布式存储的概念
- Hbase的架构及角色功能是什么?
- Hbase:由RegionServer的堆内存构建了分布式内存
- 分布式主从架构
- HMaster:管理节点:管理从节点,管理元数据
- HRegionServer:存储节点:管理Region中所有数据的存储,接受客户端读写请求
- HDFS:构建分布式磁盘存储
- Zookeeper:辅助选举,存储管理元数据
- 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
- 反馈问题
- Hive中的表能不能算是分布式的?
- 怎样删除一行的某一个列族了?
- 如果用scan语法的时候有多个过滤器,过滤器的优先级是什么?可否人工进行干预优先级?
- 优先级:只要将Rowkey的过滤器先加载
- 逻辑关系:并列,或者
知识点02:
- Hbase Java API
- 应用:Spark/MR 读写Hbase
- DDL:创建表
- DML:读写数据 + 过滤器
- Hbase存储设计
- Table、Region、RegionServer三者之间的关系?
- 分区的规则是什么?如何决定一个Rowkey的数据写入哪个分区?
- 为什么要设计列族?底层Region内部的存储是什么样的?
- Hbase存储在HDFS中的数据是什么样的结构?
- 热点问题
- 类似于数据倾斜为
- 现象、原因、解决方案
- 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>