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重构