HBASE学习笔记

Hbase表结构

HBASE中的表由 列族,列,rowKey组成 ,建表时需要先给定列族信息,添加数据时,需要给定当前数据的Rowkey以及列族下的列信息。

由于HBASE是一个列式存储,可伸缩的数据库,那么由于列可伸缩,那么必须要一开始定义时指定列信息,但是对于列可以划分类型,比如上图中,列族分为 veh_info、pass_info ,其中和车辆基本信息相关的列数据保存到对于veh_info列中,和车辆通过信息相关的列可以保存到 pass_info列中

HBASE SHELL

Group name: namespace

对数据库的操作

Commands: alter_namespace, create_namespace, describe_namespace, drop_namespace, list_namespace, list_namespace_tables

创建
shell 复制代码
create_namespace
create_namespace 'bigdata28'
查看
shell 复制代码
list_namespace 

NAMESPACE                                     
bigdata28 -- 创建的
default --默认命名空间
hbase  --用于存储命名空间以及表的元数据信息
查看详细信息
shell 复制代码
describe_namespace

describe_namespace 'bigdata28'
删除命名空间
shell 复制代码
drop_namespace
drop_namespace 'bigdata28'
查看命名空间下的表
shell 复制代码
list_namespace_tables

list_namespace_tables 'default'

Group name: ddl

对表进行操作

Commands: alter, alter_async, alter_status, clone_table_schema, create, descr

ibe, disable, disable_all, drop, drop_all, enable, enable_all, exists, get_table, is_disabled, is_enabled, list, list_regions, locate_region, show_filters

创建表
shell 复制代码
create 
# t 表示表名称
# f 表示列族

create 't1', 'f1', 'f2', 'f3'

create 'bigdata28:veh_pass','veh_info','pass_info'
查看表
shell 复制代码
list_namespace_tables 'bigdata28'

list
查看表的描述信息
shell 复制代码
describe "bigdata28:veh_pass"

# 每个列族都有对应一个描述信息 
#  VERSIONS 表示当前列族中存储的数据对应的最多的版本数
{NAME => 'pass_info', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => 'false', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536'}                                                                                                                
{NAME => 'veh_info', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => 'false', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536'}
修改表信息
shell 复制代码
alter

alter 'bigdata28:veh_pass', {NAME => 'pass_info', IN_MEMORY => 'true'}

# 表示将当前pass_info列族中的数据版本设置为最高可保留3个版本的数据 
alter 'bigdata28:veh_pass', {NAME => 'pass_info', VERSIONS => 3}
删除表
shell 复制代码
drop

drop 't1'
# 对于表删除时,需要对其进行关闭之后再进行删除
ERROR: Table t1 is enabled. Disable it first.
关闭或开启表
shell 复制代码
disable 't1'
enable 't1'
查看表的Region信息
shell 复制代码
list_regions 

list_regions "bigdata28:veh_pass"

Hbase中的数据是存储在HDFS中,那么如何进行存储的?

Hbase的数据存储目录在 /hbase/data/ 路径中

命名空间、表都是以目录的形式存在的

在表目录下,会存在有一个Region目录,Region是按照RowKey进行划分的(当数据量较少时,仅有一个) 在Region下对应有列族的目录,列族目录下存储具体的数据 ,当数据添加到HBASE后先放在内存当中,之后再保存到对于HDFS路径中

Group name: dml

对数据进行操作

Commands: append, count, delete, deleteall, get, get_counter, get_splits, inc

r, put, scan, truncate, truncate_preserve

添加数据
shell 复制代码
put 
# ns1 表示命名空间
# t1 表示表名称
# r1 表示RowKey信息 
# c1 表示列族及列信息 
# value 表示对应具体列下的单个数据 
put 'ns1:t1', 'r1', 'c1', 'value'

put 'bigdata28:veh_pass','pass1','veh_info:hphm','ASJ666'
put 'bigdata28:veh_pass','pass1','veh_info:cllx','K33'
put 'bigdata28:veh_pass','pass1','veh_info:clys','red'


put 'bigdata28:veh_pass','pass12','veh_info:hphm','ASJ888'
put 'bigdata28:veh_pass','pass12','veh_info:cllx','K13'
put 'bigdata28:veh_pass','pass12','veh_info:clys','w'


# 添加数据时指定时间戳
put 't1', 'r1', 'c1', 'value', ts1

put 'bigdata28:veh_pass','pass9','veh_info:hphm','ASJ000_5', 1709279201100
# 展示数据时,按照时间戳从大到小依次展示

put命令可以按照 Rowkey 列族 列 添加单个数据

查看数据
shell 复制代码
get

get 'ns1:t1', 'r1'
get 'bigdata28:veh_pass','pass1'

# 指定列进行查看数据
get 'bigdata28:veh_pass','pass9', {COLUMN => ['veh_info:cllx', 'veh_info:hphm']}

# Column family cllx does not exist 表示给定的列族不存在,列需要使用列族对其进行修饰

get 可以根据rowKey信息获取到一行数据

查看所有数据
shell 复制代码
scan 
scan 'bigdata28:veh_pass'

# 扫描某个列下所有数据
scan 'bigdata28:veh_pass',{COLUMNS => 'veh_info:hphm'}
# 获取多列数据
scan 'bigdata28:veh_pass',{COLUMNS => ['veh_info:hphm','veh_info:cllx']}

# 限制展示行数(RowKey)
scan 'bigdata28:veh_pass',{LIMIT => 3}
scan 'bigdata28:veh_pass',{COLUMNS => 'veh_info:hphm',LIMIT => 3}


# 指定范围过滤数据
# STARTROW指定开始的RowKey
# STARTROW是包含的 
scan 'bigdata28:veh_pass',{STARTROW => 'pass12'}
# STOPROW可以指定结束Rowkey,并且不包含
scan 'bigdata28:veh_pass',{STARTROW => 'pass12',STOPROW => 'pass8'}

# 案例:
# 需求:获取pass1开头的所有数据 
scan 'bigdata28:veh_pass',{STARTROW => 'pass1',STOPROW => 'pass8'}
# 当添加了pass7,当前的扫描的数据不符合规范
scan 'bigdata28:veh_pass',{STARTROW => 'pass1',STOPROW => 'pass1~'}
# 在设定STARTROW及STOPROW范围时,可以参考ASC码表中的最大值和最小值 

# 扫描所有数据
scan 'bigdata28:veh_pass', {RAW => true, VERSIONS => 4}

scan 可以扫描当前表中的所有数据

删除
shell 复制代码
delete 'ns1:t1', 'r1', 'c1', ts1

delete 'bigdata28:veh_pass','pass9','veh_info:hphm', 1709279201176

# 删除某个RowKey下的 一列
delete 'bigdata28:veh_pass','pass9','veh_info:hphm'

# 当删除数据时,实际上是对数据进行标记 type=Delete
# 在之后的某一时刻会对标记数据进行批量整理 

deleteall
deleteall 'bigdata28:veh_pass','pass9'

# 删除列族信息 可以使用表的Alter语法
alter 'bigdata28:veh_pass',{ METHOD => 'delete', NAME => 'pass_info' }
count
shell 复制代码
# 统计表的行数
count 'bigdata28:veh_pass'
truncate
shell 复制代码
# 清空表
# 立即生效
truncate 'bigdata28:veh_pass'

HBASE名词

Rowkey

唯一标识一行数据

可以通过RowKey获取一行数据

按照字典顺序排序的:字典序排序规则是按位置比较,比较大小是按照ASC码值进行比较

Row key只能存储64k的字节数据 一般情况下RowKey在 10-100byte 之间最为合适

shell 复制代码
put 'bigdata28:veh_pass','pass1','veh_info:hphm','ASJ777'
put 'bigdata28:veh_pass','pass1','veh_info:cllx','K31'
put 'bigdata28:veh_pass','pass1','veh_info:clys','w'

get 'bigdata28:veh_pass','pass1'


put 'bigdata28:veh_pass','pass8','veh_info:hphm','ASJ999'
put 'bigdata28:veh_pass','pass8','veh_info:cllx','K32'
put 'bigdata28:veh_pass','pass8','veh_info:clys','r'

put 'bigdata28:veh_pass','pass10','veh_info:hphm','ASJ000'
put 'bigdata28:veh_pass','pass10','veh_info:cllx','K30'
put 'bigdata28:veh_pass','pass10','veh_info:clys','w'

Column Family(列族)和qualifier(列)

HBase表中的每个列都归属于某个列族(簇),列族必须作为表模式(schema)定义的一部分预先给出。如 create 'test', 'course'。

列名以列族作为前缀,每个"列族"都可以有多个列成员(column);如course:math, course:english, 新的列族成员(列)可以随后按需、动态加入。权限控制、存储以及调优都是在列族层面进行的;

HBase把同一列族里面的数据存储在同一目录下,由几个文件保存。(在HDFS的列族目录下,可以查看到)

Timestamp时间戳

在HBase每个cell存储单元对同一份数据有多个版本,根据唯一的时间戳来区分每个版本之间的差异,不同版本的数据按照时间倒序排序,最新的数据版本排在最前面。

shell 复制代码
alter 'bigdata28:veh_pass', {NAME => 'veh_info', VERSIONS => 3}
# 将veh_pass中的veh_info列族版本设置为3,其列族下的列,可以同时存储三个版本的时间戳数据

put 'bigdata28:veh_pass','pass9','veh_info:hphm','ASJ000_1'
put 'bigdata28:veh_pass','pass9','veh_info:cllx','K30_1'
put 'bigdata28:veh_pass','pass9','veh_info:clys','w_1'

put 'bigdata28:veh_pass','pass9','veh_info:hphm','ASJ000_2'
put 'bigdata28:veh_pass','pass9','veh_info:cllx','K30_2'
put 'bigdata28:veh_pass','pass9','veh_info:clys','w_2'

put 'bigdata28:veh_pass','pass9','veh_info:hphm','ASJ000_3'
put 'bigdata28:veh_pass','pass9','veh_info:cllx','K30_3'
put 'bigdata28:veh_pass','pass9','veh_info:clys','w_3'

get 'bigdata28:veh_pass','pass9' 
# get命令只能获取最新版本的数据

scan 'bigdata28:veh_pass', {RAW => true, VERSIONS => 3}
扫描全表时,可以获取多个版本的数据 


put 'bigdata28:veh_pass','pass9','veh_info:hphm','ASJ000_4'
put 'bigdata28:veh_pass','pass9','veh_info:cllx','K30_4'
put 'bigdata28:veh_pass','pass9','veh_info:clys','w_4'

scan 'bigdata28:veh_pass', {RAW => true, VERSIONS => 4}
# 对于HBASE中的数据操作,并不是一个实时的。对于不符合要求的数据,会通过定期检查,再将数据去除

Cell单元格

由行和列的坐标交叉决定。

单元格是有版本的。

单元格的内容是未解析的字节数组。

由 {row key, column( = +), version} 唯一确定的单元。

cell中的数据是没有类型的,全部是字节码形式存贮。(对于JavaAPI调用时,需要指定数据类型)

shell 复制代码
put 'bigdata28:veh_pass','pass9','veh_info:hpzl','大型汽车'
# 对于命令行,如果出现有中文,那么展示时,无法对其进行直接查看,英文可以由命令行转换


put 'bigdata28:veh_pass','pass9','pass_info:hpzl','大型汽车'
# 列是归属于列族的,不同列族之间的列名可以相同 
#  一般情况下相同类型的列会放到一个列族下,所以列名一般不重复

HBase架构分析

HMaster

1.为Region server分配region (当表中添加数据时,产生的Region具体是由HMaster将其分配给从节点HRegionServer)

2.负责Region server的负载均衡 (当表在使用过程中,一开始仅有一个Region,当数据量不断增大时,会对Region进行分裂成多个,分裂的Region是由HMaster进行指定到具体的从节点)

3.发现失效的Region server并重新分配其上的region(如果当Region server 宕机,此时需要将该RegionServer上的数据分配给其他节点)

4.管理用户对table的增删改操作(对表中元数据的操作,需要请求Master,但是数据的读写,并不是由HMaster管理)

RegionServer

1.Region server维护region,处理对这些region的IO请求(负责对HMaster分配的Region进行数据的检查更新操作,同时客户端的数据访问,会直接请求HRegionServer)

2.Region server负责切分在运行过程中变得过大的region(当表中的数据不断添加,数据是存储在Region中,当Region数据量过大时,会降低查询效率,于是需要将Region中的数据划分成多个Region进行分开存储)

Region

1.HBase自动把表水平划分成多个区域(region),每个region会保存一个表里面某段连续的数据(对于表中的RowKey是按照字典序排序,同时按照RowKey进行划分Region);每个表一开始只有一个region(但是建表时,也可以指定Region划分的规则生成多个Region),随着数据不断插入表,region不断增大,当增大到一个阀值的时候,region就会等分会两个新的region(裂变)

2.当table中的行不断增多,就会有越来越多的region。这样一张完整的表被保存在多个Regionserver 上。(通过该方式可以达到降低用户的单节点请求压力,当大量的用户请求被分配到少了的RegionServer上此时称为热点问题)

Memstore

一个region由多个store组成,一个store对应一个CF(列族)store包括位于内存中的memstore和位于磁盘的storefile , 写操作先写入memstore,当memstore中的数据达到某个阈值,hregionserver会启动flashcache进程写入storefile,每次写入形成单独的一个storefile

(在读取数据时,会对相同列族中的数据进行获取,同时为了提高查询速度,将数据按照列族划分,数据保存在不同的文件中,同时由不同的线程进行管理,为了提高查询速度,同时将数据放入内存缓存,但是内存容量有限,于是会将数据按照一定的阈值,将内存中的数据缓存到HDFS磁盘中形成storefile)

当storefile文件的数量增长到一定阈值后,系统会进行合并(minor、major compaction),在合并过程中会进行版本合并和删除工作(majar),形成更大的storefile

(当内存缓存刷写到磁盘时,每个文件大小不一样,同时文件中存储的数据有的会被标记成 delete等,需要对该数据进行删除操作,于是需要对数据进行合并整理,那么合并时,如果一次性将所有的数据进行合并,那么数据量较大,对资源的要求比较高,于是可以分为小合并和大合并)

当一个region所有storefile的大小和数量超过一定阈值后,会把当前的region分割为两个,并由hmaster分配到相应的regionserver服务器,实现负载均衡

客户端检索数据,先在memstore找,找不到再找storefile(由于最新的数据一定是存储在memstore中的,当查询数据时,可以先去memstore中查询,如果能查询到,那么可以直接返回,如果没有查询到,那么可以从HDFS中进行遍历查询)

Hbase读写流程

当数据被客户端提交到Hbase集群时,Hbase如何发现数据要存储的位置 => 数据存储位置是当前Hbase的元数据信息 =>Hbase元数据信息存储在什么位置?=> 通过查看namespace 发现有hbase命名空间,在该命名空间中对应有meta(表的元数据表,包括当前表的Region中保存的RowKey信息以及位置信息)namespace表示当前命名空间的元数据 => 当提交写入数据请求时,需要通过meta表知道表的Region存储位置 => 那么meta表客户端如何知道存储在什么位置?meta表也是一张Hbase表 => Zookeeper中/hbase路径下存在有meta-region-server, namespace节点,该节点中保存的数据为具体哪个节点中的RegionServer中 => 于是可以先去Meta表中的RegionServer中发现表的元数据 => 再去目标表中的RegionServer中添加数据

写操作

1)Client 先访问 zookeeper,获取 hbase:meta 表位于哪个 Region Server。 2)访问对应的 Region Server,获取 hbase:meta 表,根据写请求的 namespace:table/rowkey,查询出目标数据所在的 Region Server的Region信息,并将该 table 的 region 信息以及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问。

3)向Region Server发送写操作

4)将数据顺序写入(追加)到 WAL

5)将数据写入对应的 MemStore,数据会在 MemStore 进行排序

6)向客户端发送 ack响应信息

7)等达到 MemStore 的刷写时机后,将数据刷写到 HFile。

读操作

1)Client 先访问 zookeeper,获取 hbase:meta 表位于哪个 Region Server。2)访问对应的 Region Server,获取 hbase:meta 表,根据读请求的 namespace:table/rowkey,查询出目标数据位于哪个 Region Server 中的哪个 Region 中。并将该 table 的 region 信息以及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问。

3)与目标 Region Server 进行通讯

4) 当在MemStore中如果直接找到对应的数据,那么可以直接返回,如果找不到,再去BlockCache(读缓存,存放了HFile中的元数据信息)中进行查询,如果能查询到对应HFile,那么可以直接返回,如果查询到多个HFile那么对应会进行合并操作,将合并后的结果数据进行返回

5)将从文件中查询到的数据块元数据信息(Block,HFile 数据存储单元,默认大小为 64KB)缓存到Block Cache。

6)将合并后的最终结果返回给客户端。

原理及优化

布隆过滤器

对于Hbase可以由Rowkey进行划分Region,再由Region中的列族划分store,Store中有MemStore对数据进行缓存,对刷写的数据可以保存在HDFS中的HFile文件中,每次MemStore刷写都会生成一个HFile文件,如果要过滤数据 找到对应数据存储在对应哪个HFile中即可。于是对于布隆过滤器的使用,可以将HFile看成是一个集合,当数据添加到HFile中,可以由布隆过滤器维护一个二进制的向量,通过Hash函数对数据进行映射。当有数据要验证时,直接对RowKey数据进行hash计算,找到对应位置是否为1即可。过滤掉大部分的HFile,减少需要扫描的Block。误判对于查询结果影响并不大。

RowKey设计-三原则

Rowkey长度原则

rowkey是一个二进制码流,可以是任意字符串,最大长度 64kb ,实际应用中一般为10-100bytes原因如下:一、数据的持久化文件HFile中是按照KeyValue存储的,如果rowkey过长,比如超过100字节,1000w行数据,光rowkey就要占用100*1000w=10亿个字节,将近1G数据,这样会极大影响HFile的存储效率;二、MemStore将缓存部分数据到内存,如果rowkey字段过长,内存的有效利用率就会降低,系统不能缓存更多的数据,这样会降低检索效率。

Rowkey散列原则

Rowkey散列原则

如果rowkey按照时间戳的方式递增,不要将时间放在二进制码的前面,建议将rowkey的高位作为散列字段,由程序随机生成,低位放时间字段,这样将提高数据均衡分布在每个RegionServer,以提高负载均衡的几率。如果没有散列字段,首字段直接是时间信息,所有的数据都会集中在一个RegionServer上,这样在数据检索的时候负载会集中在个别的RegionServer上,造成热点问题,会降低查询效率。

如果Rowkey现定义以时间戳作为 Rowkey+随机值,那么Rowkey是有序的,对应会将连续的Rowkey发送到同一个RegionServer中,在存储时,如果数据量较大,那么大部分的写入请求会连续的写入到一个RegionServer中,没有形成分布式的效果。于是形成写热点问题。同时读取数据时,时间戳最近的数据,其访问频率相对较高,所以所有的读请求也会发送到对应少了的RegionServer中,也会形成读热点问题

rowkey唯一原则

必须在设计上保证其唯一性

经常读取的数据存储到一块,将最近可能会被访问的数据放到一块 => 当查询数据时,是按照Rowkey的区间对数据进行获取,相连续的数据会在一个HFile中存储,将遍历的数据一次性返回即可,而不需要再从多个Region中加载多个HFile文件进行返回。

RowKey热点问题

①加盐:在rowkey的前面增加随机数,具体就是给rowkey分配一个随机前缀以使得它和之前的rowkey的开头不同。分配的前缀种类数量应该和你想使用数据分散到不同的region的数量一致。

优点:对数据进行均匀的分散到多个Region中,有效避免热点问题

缺点:要按照RowKey查询对应的Value数据 rand_id 时,会造成数据不确定存放在哪个位置,不能直接根据一个RowKey获取对应Value值
②哈希:哈希也可以使负载分散到整个集群,但是读却是可以预测的。使用确定的哈希可以让客户端重构完整的rowkey,可以使用get操作准确获取某一个行数据

id:1500001 =hash> 5a91734fe2f68e2cb93cd4c3d46776a7

通过生成的密文作为Rowkey可以有效的将数据进行分散开

优点:使负载分散到整个集群,但是读却是可以预测的

缺点:使用Scan方式扫描数据时,不能以明文的方式查看到对应ID的内容,同时也不能使用连续的方式扫描ID数据
③反转:使得rowkey中经常改变的部分(最没有意义的部分)放在前面。这样可以有效的随机rowkey,但是牺牲了rowkey的有序性

20240304154101 直接使用时间戳会导致热点问题,于是使用反转的方式将变化快的数据放在前面,不变的放在后面

=> 10145104034202 当秒数发生变化时,对应连续时间数据数据会分散到多个Region中进行存储

优点:有效解决热点问题,同时数据内容可以有效展示,同时对于get方法获取数据时,也可以直接获取到其Value值

缺点:丢失了数据的连续性
④预分区:可以通过指定SPLITS_FILE的值指定分区文件,如果分区信息比较少,也可以直接用SPLITS分区。create 'split_table_test', 'cf', {SPLITS_FILE => 'region_split_info.txt'}

思考:

当数据一开始保存到表中时,表中只会存在一个Region,如果插入的数据量较大,所有的写请求都会存入一个RegionServer中的1个Region中,其写入压力非常大,会影响其他表在同RegionServer中的Region的访问

如果一开始创建表时,就给定初始的Region,那么对应数据按照Region中的Rowkey范围进行划分, 那么就不存在有写入压力了

create 'api:student_split', 'info', {SPLITS_FILE => '/root/region_split_info.txt'}

region_split_info.txt:该文件中保存了每个Region的RowKey存储范围

1500100100!

1500100200!

1500100300!

1500100400!

1500100500!

1500100600!

1500100700!

1500100800!

1500100900!

在Hbase中如何判断是否存在有热点问题?

1.打开Hbase页面16010端口

2.找到对应表的描述页面

3.在页面中对每个Region的请求数量进行分析,当某个Region的请求过多时,说明存在有热点问题。需要对当前表进行RowKey重构

相关推荐
QQ同步助手10 分钟前
如何正确使用人工智能:开启智慧学习与创新之旅
人工智能·学习·百度
流浪的小新18 分钟前
【AI】人工智能、LLM学习资源汇总
人工智能·学习
A懿轩A1 小时前
C/C++ 数据结构与算法【数组】 数组详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·数组
云边有个稻草人1 小时前
【优选算法】—复写零(双指针算法)
笔记·算法·双指针算法
南宫生9 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
sanguine__9 小时前
Web APIs学习 (操作DOM BOM)
学习
冷眼看人间恩怨9 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
数据的世界0111 小时前
.NET开发人员学习书籍推荐
学习·.net
四口鲸鱼爱吃盐12 小时前
CVPR2024 | 通过集成渐近正态分布学习实现强可迁移对抗攻击
学习
OopspoO14 小时前
qcow2镜像大小压缩
学习·性能优化