一、概览
HBase是一个分布式的,面向列的开源数据库。
它更像是分布式存储而不是分布式数据库,它缺少很多RDBMS系统的特性,比如列类型,辅助索引,触发器,和高级查询语言等。
那Hbase有什么特性呢?如下:
- 强读写一致,但是不是“最终一致性”的数据存储,这使得它非常适合高速的计算聚合。
- .自动分片,通过Region分散在集群中,当行数增长的时候,Region也会自动的切分和再分配自动的故障转移。
- Hadoop/HDFS集成,和HDFS开箱即用,不用太麻烦的衔接
- 丰富的“简洁,高效”API,Thrift/REST API,Java API
- 块缓存,布隆过滤器,可以高效的列查询优化
- 操作管理,Hbase提供了内置的web界面来操作,还可以监控JMX指标
什么时候用Hbase?
- 成熟的数据分析主题,查询模式已经确立并且不轻易改变。
- 传统型关系型数据已经无法承受负荷,写入量巨大,高速插入,大量读取。
- 适合海量数据的单条记录或小范围扫描(大范围扫描性能低下),,不支持JOIN等复杂关联。
Hbase与关系型数据库对比
属性 | HBase | RDBMS |
---|---|---|
数据类型 | 只有字符串 | 丰富的数据类型 |
数据操作 | 增删改查,不支持join | 各种各样的函数与表连接 |
存储模式 | 基于列式存储 | 基于表结构和行式存储 |
数据保护 | 更新后仍然保留旧版本 | 替换 |
可伸缩性 | 轻易增加节点 | 需要中间层,牺牲性能 |
事务 | 不支持 | 支持 |
二、Hbase数据模型
该技术来源于的Google论文"Bigtable"
Big Table的想法
学生表的例子S(S#,sn,sd,sa)(学号,名称,系别,年龄)
在Big Table中,可以写成三个列的表,列分别为行键,属性字段(名字sn),value
比如
学号1, 名称,小明
学号1,系别,计算机系
学号1,年龄,20
这样来看,任何一个具有key的表,均可以写成三个列的表。
Hbase中概念:
- 表(Table):是一种稀疏表(不存储数据为NULL的数据),表的索引是行关键字、列关键字、时间戳
- 行关键字(Row Key):行的主键,唯一标识一行数据,也称为行键。(并不是唯一一条记录,因为有时间戳标示多条)
表中的行根据行键进行字典排序,所有对表的访问都要通过表的行键(单个行键访问\行键范围访问(支持正则)\全表扫描(性能低下))。在创建表时,行键不用也不能预先定义,而对表数据进行操作时必须指定行键,行键在添加数据时首次被确定。 - 列族(Column Family):行中的列被分为列族。同一个列族的所有成员具有相同的列族前缀。例如[course:math]和[course:art]都是列族[course]的成员。一个表的列族必须在创建表时预先定义。
- 列关键字(Column Key):也称列键。 格式为:[Family:qualifier]。 family是列族名,用于表示列族前缀。qualifier是列修饰符,表示列族中的一个成员,列族成员可以在随后按需拓展。理解为列的唯一标识。但是列标识是可以改变的,因此每一行可能有不同的列标识。
- 存储单元(Cell): 在HBase中,值是作为一个单元保存在系统中的。要定位一个单元,需要用[行键+列键+时间戳] 3个要素。
- 时间戳(Timestamp):插入单元格时间,默认为单元格的版本号。
HBase 本质上只有插入操作,更新和删除都是使用插入方式完成的,这是由其底层 HDFS 的流式访问特性(一次写入、多次读取)决定的。
HBase在更新时总会插入一个带时间戳的新行,而删除时则插入一个带有删除标记的新行。标示这条记录已被删除。
Hbase会在生命周期内,会在一定的时间内的将一些小的文件进行合并成大的文件,然后将一些打上删除标记的记录抛弃掉。每次插入都有一个时间戳的标记,每次都是一个新的版本,Hbase会保留一定数据的版本(自行设置)。如果查询时提供时间戳则返回距离该时间最近的版本,否则返回离现在最近的版本。
时间戳
对应每次数据操作的时间,可由系统自动生成,也可以由用户显式的赋值。
Hbase支持两种数据版本回收方式:
1.每个数据单元,值存储指定个数的最新版本。
2.保存指定时间长度的版本(7天)
在传统数据库中,只能通过表的主键和唯一字段定位到某一条数据。
主键为name,主要字段有name、grade、math、art。
由于主键唯一标识了一行记录,所以我们很容易按姓名查询某位同学的成绩。
那么思考以下几个问题:
如果新增一门课程,在不修改表结构的情况下,能保存成绩么?
如果有同学参加补考,怎么保存两次成绩。
如果同学只参加一门考试,其他课程都没有成绩。我们能保存只有成绩的课程来节省存储空间么?
HBase的数据模型就能完美解决以上问题:
可以通过[行键+时间戳+列族]来访问具体的值(单元)。
对表操作必须指定行键和列键,每次操作都会增加一条数据并会自动生成时间戳。从上到下倒序排列,不必由用户管理。
每次只针对一个列键操作。且列修饰符可以为空。
eg. 在t4时刻,客户端添加[jason] 的[grade]为[2]。
类似操作为:先找到行键[jason],然后指定列键并赋值[grade:=2]。这里的列族修饰符可以为空。
前面提到过列族修饰符可以由任意字符组成。
现在基于HBase回答前面的问题:
- 如果jason新增英语科目成绩,那么指定行键[jason],列键[source:english],以及值(英语成绩)即可。
- 如果jason 参加数学补考,那么直接指定行键[jason],列键[source:math],以及值(数学补考成绩)即可。
- 前面说过HBase是稀疏的存储设计。其实概览图中空白部分是不会实际被存储的。
设计要点:
- 行健设计Hbase在存储数据的时候,有两个SortedMap,首先按照rowkey进行字典排序,然后再对Column进行字典排序。行健的设计非常重要。这种设计方式可以让有关系的行非常的近,通常行健的设计是网站的域名反转, 比如(org.apache.www,org.apache.mail,org.apache.jira),这样的话所有的Apache的域名就很接近。避免单调的递增行健,因为Hbase的行健是有序排列的,这样可能导致一段时间内大部分写入集中在某一个Region上进行操作,负载都在一台节点上。可以设计成:[metric_type][event_timestamp],不同的metric_type可以将压力分散到不同的region上行健短到可读即可,因为查询短键不比长键性能好多少,所以设计时要权衡长度。行健不能改变,唯一可以改变的方式是先删除后插入。
其他的避免热点堆积的解决方案:Rowkey加一个前缀,使用一些随机字符;使用Hash散列(比如md5后取前四位),好处时能让一个给定的行有相同前缀,使得读操作能够推断 - 列簇设计
列簇是一些列的集合,一个列簇的成员有相同的前缀,以冒号(:)作为分隔符。现在Hbase不能很好处理2~3个以上的列簇,所以尽可能让列簇少一些,如果表有多个列簇,列簇A有100万行数据,列簇B有10亿行,那么列簇A会分散到很多的Region导致扫描列簇A的时候效率底下。列簇名的长度要尽量小,一个为了节省空间,另外加快效率,比如d表示data,v表示value - 列簇属性配置 HFile数据块,默认是64KB,数据库的大小影响数据块索引的大小。数据块大的话一次加载进内存的数据越多,
扫描查询效果越好。但是数据块小的话,随机查询性能更好create ‘mytable’,{NAME => ‘cf1’, BLOCKSIZE => ‘65536’} 数据块缓存,数据块缓存默认是打开的,如果一些比较少访问的数据可以选择关闭缓存
create ‘mytable’,{NAME => ‘cf1’, BLOCKCACHE => ‘FALSE’} 数据压缩,压缩会提高磁盘利用率,但是会增加CPU的负载,看情况进行控制
create ‘mytable’,{NAME => ‘cf1’, COMPRESSION => ‘SNAPPY’} Hbase表设计是和需求相关的,但是遵守表设计的一些硬性指标对性能的提升还是很有帮助的,这里整理了一些设计时用到的要点。
应用场景一:浏览历史
利用关系型数据库,特别简单 使用order by 取出前五条。
关系型数据库的困难
1.简单的事情只要上了量就会变成无比复杂的事情。
2.order by 耗费很多性能
3.大量发生,但又无法分布式处理
4.顾客需要实现看到自己的足迹,因此不能使用缓存技巧。
Hbase迎接挑战
1.天生就是面向时间戳查询
2.基于行键的查询异常快速,特别是最近的数据被放在内存的memstore里,完全没有开销。
3.分布式化解复核。
模式设计
行键: userid
列族和列 : book:bookid
为了充分利用分布式,可以用reverse key,hash等技巧改造行键。
应用场景二、推荐系统(浏览本商品的顾客还看过什么书)
使用Hbase:表设计与查询实现
两个表,一个是u-t,另一个是t-u
U-t:行键位userid,列族和列为thread:threadid
T-u: 行键位threadid, 列族和列为user:userid
查询:先根据threadid,找出userid,然后将这些userid根据u-t表中将所有的threadid找出,在程序中实现去重和统计功能。
应用场景三、辅助索引
例子:学生表(学号,身份证号,姓名,系,年龄),有时在学号上查询,有时在身份证上查询
主表:行键位学号,列族为学生,下面的列式身份证,姓名,性别,系,年龄。
辅助(索引)表: 行键位身份证号,列族和列为学号。
此索引表大大不同于oracle的索引,需要在手工维护此表。
应用场景四、复合行键设计
指查询需要多个列的时候,如何设计?
简单就是将多个列连接起来,作为一个行键。
复合行键:
便于分布
便于多条件伸缩查询
三、Hbase架构
HBase采用Master/slaves 的主从服务器结构,由一个HMaste服务器和多个HRegion Server服务器组成,
所有的服务器都通过Zoo Keeper协调并处理运行期间遇到的错误。
HMaster负责管理所有的HRegion Server,各HRegion Server负责存储许多HRegion,
每一个HRegion是对HBase逻辑表的分块。
一台物理节点只能跑一个HRegionServer
- HRegion
HBase使用表存储数据,表由行和列组成。但是当表超过设定值大小时,Hbase会自动将表划分不同的区域,每个区域被称为HRegion。
HRegion是集群上分布式存储和负责均衡的最小单位,类似于HDFS中文件与文件块的概念。
一个HRegion保存一个表中连续的数据,通过表名及主键来区分每一个HRegion。最开始一个表只有一个HRegion,
随着HRegion增大,超出设定阈值,会分裂成两个大小基本相同的HRegion,称为HRegion分裂。 - HRegion Server HRegion
Server负责响应客户端IO请求,向HDFS中读写数据,一台机器上只运行一个HRegion Server。 HRegion
Server包含两个部分:HLog 和 HRegion。 HLog用于存储数据日志,实质是HDFS的Sequence
File。到达HRegion的写操作首先追加到日志中, 才被加入到内存中的Mem Store。 HLog
主要用于故障恢复,如果HRegion所在主机发生故障,那么所维护的HRegion会被重新分配至新的主机上, 新的HRegion
Server 在加载HRegion时,可通过Hlog恢复数据。 - HMaster
HMaster主要任务是告诉每个HRegion Server需要维护哪些HRegion。在Hbase中可以启动多个HMaster,通过ZooKeeper的Master选举机制来保证系统中总会有一个HMaster在运行。
HMaster包括以下功能:
1.管理用户对表的增改查操作
2.管理HRegion的负责均衡,调整HRegion分布
3.在HRegion分裂后,负责HRegion分配
4.在HRegion Server停机后,负责失效HRegion Server上的HRegion迁移 - zookeeper Zookeeper 是存储HBase
-ROOT-表和.META.表的位置,这是HBase中两张特殊的表。称为根数据表(-ROOT-)和元数据表(.META.)。 元数据表记录普通用户表的HRegion标识符信息,每个HRgion的标识符为:表名+开始主键+唯一ID。
随着用户表的HRegion的分裂,.META.表的信息也会增加,并且还可能被分割为几个HRegion,
此时可以用一个-ROOT-表来保存META的HRegion信息,而-ROOT-表是不能被分割的,也就是-ROOT-表只有一个HRegion。
那么客户端(Client)在访问用户数据前需要先访问Zoo Keeper,然后访问-ROOT-表,接着访问.META.表,
最后才能找到用户数据所在位置进行访问。
Hbase读数据
数据读流程:
- Client通过zookeeper定位的Hbase元数据表(hbase:meta)。(老版本还有 --ROOT–表)
- 从hbase:meta定位数据分布在哪些RegionServer上
- 从RegionServer,将内存(MemStore)和磁盘(StoreFile)中的数据合并后,返回Client
数据写流程:
- Client向Region Server发起写请求(Put、Delete)
- Region Server首先将数据写入WAL(HLog)中。顺序写,没有寻道的时间(600M/s)
- Region Server将数据写入内存(MemStore)
- Client写成功
- 内存中的数据定期写入磁盘(StoreFile),这个过程就是Flush
什么时候触发Flush:
- 内存(MemStore)中的数据达到阈值(默认64M),
将内存中的数据写入StoreFile(HDFS) - 删除内存和HLog中历史数据
- 在HLog中打标记点
数据Compact(合并)、Split:
- 当一个Region的Store中StoreFile文件数量达到一定阈值(默认4个),会触发Compact(合并)操作,
将多个StoreFile合并成一个大的StoreFile。 Compact会对Cell的历史版本进行删除和数据合并。 - 当一个Region的数据达到256M,会进行split操作,即将一个region拆分成两个region,拆分完成后,老的region下线。
新的两个region根据负载情况分配到对应的regionServer。
从一张表的存储理解Hbase架构
基于一张表:
1.一个表的Region对应HDFS的一个目录
2.Store和列族一一对应。 在HDFS上,一个列族对应一个HDFS目录,在Region目录下面。
3.一个Store对应多个StoreFile和一个MemStore
4.数据写先写日志,然后存入MemStore,返回Client写成功。
5.MemStore中数据大小达到阈值,数据将Flush到StoreFile。
6.当Store中的文件数量达到阈值,会执行Compact(合并)操作
7.当一个Region的StoreFile的大小达到阈值,这个表的Region会进行split。
示例:user1表存在两个data/info两个列族
/hbase/data/default/user1/7eb4ce87a61ef099528c0ee17738091e/data/f6b45be8822243038c3fd55e07a89e10
/hbase/data/default/user1/7eb4ce87a61ef099528c0ee17738091e/info/d31cc9a4f55646b3a706288403fa8405
store是保存在表的region目录下(一级目录)、列族下(二级目录)。一个Store包含多个StoreFile。
当对StoreFile进行合并和拆分时,数据时无法读写的。同时这两个操作费IO。
可以关掉合并和拆分,通过定时shell脚本进行合并和拆分。
Hbase列族数量不能太多
1.Flush会产生更多IO Flush的最小单元是region,也就是说一个region中的某个列族做Flush操作,其他的列族也会Flush。
对每个列族而言,每次Flush都会产生一个文件,频繁Flush必然会产生更多的StoreFile,StoreFile数量增多又会产生更多的Compact操作。
Flush和Compact都是很重的IO操作。
- Split操作可能会导致数据分布不均匀 Split的最小单元是region,如果这个region有两个列族A、B。
列族A有100亿条记录,列族B有100条记录。 如果最终Split成20个region,那么列族B的100条记录会分布到20个region上,
扫描列族B的性能低下。
4.HBase数据特点
- 数据类型: 只有简单的字符串类型。(传统SQL有多种数据类型)
- 数据操作:只有简单的插入、查询、删除、清空等操作,表和表之间是分离的,没有复杂的表和表之间的关系。(传统SQL有多种连接操作)
- 存储模式:基于列的存储。每个列族由几个文件保存,不同列族的文件是分离的。(传统SQL是基于表格结构和行模式存储) 数据维护:
只是简单的插入了新数据,它的旧版本会保留(传统SQL是替换修改操作) - 可伸缩: 分布式数据库。能够轻松的增加和减少硬件数量,并且对错误兼容性较高。(传统SQL需要中间层才能实现类似功能)
四、实例
–查询数据库状态
status
–查询数据库版本
version
–创建表
create ‘member’,‘member_id’,‘address’,‘info’
member是表名
member_id,address,info是列族
–查看表信息
list
–查看表结构
describe ‘member’
–删除列族
alter ‘member’,{NAME=> ‘member_id’,method=>‘delete’}
–离线表,然后再修改表
disable ‘member’
–使表在线
enable ‘member’
– 查询表是否存在
exists ‘member’
–判断表是否离线或者在线
is_enabled ‘member’
–插入记录
put ‘member’,‘scuteshuxue’,‘info:age’,‘24’
member是表名
scuteshuxue是行键
info是列族
–获取行键所有数据
get ‘member’,‘scuteshuxue’
–获取一个行键,一个列族的所有数据
get ‘member’,‘scutshuxue’,‘info’
–获取一个行键,一个列族中一个列的所有数据
get ‘member’,‘scutshuxue’,‘info:age’
–更新一条记录,跟插入一样
put ‘member’,‘scutshuxue’,‘info:age’,‘99’
–全表扫描
scan ‘member’
–删除指定行键的字段
delete ‘member’,‘temp’,‘info:age’
–删除整个行
deleteall ‘member’,‘xiaofeng’
– 查询表多少行
count ‘member’
–清空表
truncate ‘member’
–示例获取多版本数据
create ‘t1’,‘f1’
alter ‘t1’,{NAME=>‘f1’,VERSIONS=>3}
–插入数据
put ‘t1’,‘rowkey1’,‘f1:name’,‘chhliu’
put ‘t1’,‘rowkey1’,‘f1:name’,‘xyh123’
put ‘t1’,‘rowkey1’,‘f1:name’,‘chhliuxyh’
–获取数据,默认只展示最新一条
get ‘t1’,‘rowkey1’,‘f1:name’
–获取多版本数据
get ‘t1’,‘rowkey1’,{COLUMN=>‘f1:name’,VERSIONS=>3}
–过滤数据
–查看列的值为22:
scan ‘testorder’,FILTER=>“ValueFilter(=,‘binary:22’)”
–查看列的值包含xiao: scan ‘testorder’,FILTER=>“ValueFilter(=,‘substring:xiao’)”
–列名以age开头,且age为22或者25的记录:
scan ‘testorder’, FILTER=>“ColumnPrefixFilter(‘ag’) AND (ValueFilter(=,‘binary:22’) OR ValueFilter(=,‘binary:25’) )”
整理来源:
https://www.jianshu.com/p/354ad1d29f3a
https://www.jianshu.com/p/b23800d9b227