#HABSE
##一、关系型数据库和非关系型数据库的特点
关系型数据库:Mysql,Oracle
非关系型数据库:Hbase,Mongo,Cassandra,redis
数据库类型|特性|优点|缺点
--|--|--|--
关系型数据库|1、采用了关系模型来组织数据 2、事务的一致性;3、二维表格模型|1、容易理解 2、使用方便 3、易于维护 4、支持SQL,可用于复杂的查询。|1、为了维护一致性所付出的巨大代价就是其读写性能比较差;2、固定的表结构;3、高并发读写需求;4、海量数据的高效率读写;
非关系型数据库|1、使用键值对存储数据;2、分布式;3、一般不支持ACID特性;4、是一种数据结构化存储方法的集合。|1、无需经过sql层的解析,读写性能很高;2、基于键值对,数据没有耦合性,容易扩展;3、存储数据的格式:nosql的存储格式是key,value形式、文档形式、图片形式等等,而关系型数据库则只支持基础类型。|1、不提供sql支持,学习和使用成本较高;2、无事务处理,附加功能bi和报表等支持也不好;
##二、Hbase、Mongo、cassandra的对比
Mongo:源于开发人员,为开发人员服务,MongoDB以文档的形式存储数据,不支持事务和表连接。因此查询的编写、理解和优化都容易得多。
Cassandra:优秀的可拓展性,加上出色的写入和可观的查询性能
Hbase:HBase提供了一个基于记录的存储层,能够快速随机读取和写入数据,正好弥补了Hadoop的缺陷,Hadoop侧重系统吞吐量,而牺牲I / O读取效率为代价
##三、Hbase的角色及作用
1)Client
Client包含了访问Hbase的接口,另外Client还维护了对应的cache来加速Hbase的访问,比如cache的.META.元数据的信息。
2)Zookeeper
HBase通过Zookeeper来做master的高可用、RegionServer的监控、元数据的入口以及集群配置的维护等工作。具体工作如下:
1.通过Zoopkeeper来保证集群中只有1个master在运行,如果master异常,会通过竞争机制产生新的master提供服务
2.通过Zoopkeeper来监控RegionServer的状态,当RegionSevrer有异常的时候,通过回调的形式通知Master RegionServer上下线的信息
3.通过Zoopkeeper存储元数据的统一入口地址
3)Hmaster(类似于NameNode)
master节点是整个Hbase的管理者,其主要职责如下:
1.监控RegionServer
2.处理RegionServer故障转移
3.处理元数据的变更,处理表级别的增删改查(ddl)
4.处理region的分配或转移
5.在空闲时间进行数据的负载均衡
6.通过Zookeeper发布自己的位置给客户端
4)HregionServer(类似于DataNode)
HregionServer直接对接用户的读写请求,是真正的“干活”的节点。它的功能概括如下:
1.负责存储HBase的实际数据,负责表内数据的增删改查(dml)
2.处理分配给它的Region
3.刷新缓存到HDFS
4.维护Hlog
5.执行压缩
6.负责处理Region分片
5)HLOG(WAL)
预写日志,记录操作的日志.由于数据要经 MemStore 排序后才能刷写到 HFile,但把数据保存在内存中会有很高的概率导致数据丢失,为了解决这个问题,数据会先写在一个叫做Write-Ahead logfile 的文件中,然后再写入MemStore 中。所以在系统出现故障的时候,数据可以通过这个日志文件重建。
6)HRegion
Hbase表的分片,HBase表会根据RowKey值被切分成不同的region存储在RegionServer中,在一个RegionServer中可以有多个不同的region。
7)Store
HFile存储在Store中,一个Store对应HBase表中的一个列族
8)Mem Store
写缓存,由于HFile 中的数据要求是有序的,所以数据是先存储在MemStore 中,排好序后,等到达刷写时机才会刷写到HFile,每次刷写都会形成一个新的HFile。
9)StoreFile
缓存中的数据足够多以后会flush至硬盘形成StoreFile.是保存实际数据的物理文件,StoreFile 以HFile 的形式存储在HDFS 上。每个Store 会有一个或多个StoreFile(HFile),数据在每个StoreFile 中都是有序的。
10)HFile
是在磁盘上保存原始数据的实际的物理文件,是实际的存储文件。StoreFile是以Hfile的形式存储在HDFS的,其内在是键值对形式。
11)HDFS
HDFS为Hbase提供最终的底层数据存储服务,同时为HBase提供高可用(Hlog存储在HDFS)的支持,具体功能概括如下:
1 提供元数据和表数据的底层分布式存储服务
2 数据多副本,保证的高可靠和高可用性
Client使用Hbase的RPC机制与HMaster、HRegionServer进行通信。Client与HMaster进行管理类通信,与HRegionServer进行数据操作类通信
HRegionServer内部管理了一系列HRegion对象,每个HRegion对应Table中的一个Region,HRegion有多个Store组成,每个Store对一个Table中的一个Column Family的存储,即一个Store管理一个Region上的一个列族,每个Store包含一个MemStore和0个到多个StoreFlie,Store是Hbase的存储核心,有memstore和StoreFile组成
数据在写入时,首先写入预写日志(WAL),每个RegionServer服务所有Region的写操作日志都存在同一个日志文件中,数据并非直接写入HDFS二十等缓存到一定数量的时候再批量写入,写入完成后再日志中做标记
MemStore是一个有序的内存缓冲区,用户写入的数据首先放到MemStore,当Memstore满了Flush成一个StoreFile(存储时对应的HFile),当StoreFile数量增加到一定的阈值,触发Compact合并,将多个StoreFiles合并成一个StoreFile
StoreFile合并后逐步形成越来越大的StoreFile,当Region内的所有StoreFiles总大小超过阈值即触发Split
把当前的Region Split成2个Region,父Region下线,新Split的两个Region被Hmaster分配到合适的RegionServer上,使得原先1个Region的压力得以分流到两个Region上
##四、Hbase特点
1、海量存储:单表可以存储百亿级别的量级,不用担心读取的性能下降。
2、面向列:数据在表中是按某列的数据聚集存储,数据即索引,只访问查询涉及的列时,可以大量降低系统的I/O
3、稀疏性:传统行数存储的数据存在大量NULL的列,需要占用存储空间,造成存储空间的浪费,而HBase为空的列并不占用空间,因此表可以设计的很稀疏
4、扩展性:HBase底层基于HDFS,支持扩展,并且可以随时添加或者减少节点。
5、高可靠:基于zookeeper的协调服务,能够保证服务的高可用行。HBase使用WAL和replication机制,前者保证数据写入时不会因为集群异常而导致写入数据的丢失,后者保证集群出现严重问题时,数据不会发生丢失和损坏。
6、高性能:底层的LSM数据结构,使得HBase具备非常高的写入性能。RowKey有序排列、主键索引和缓存机制使得HBase具备一定的随机读写性能。
##五、Hbase原理
写原理:
1、客户端向hregionServer请求写数据
2、hregionServer将数据先写入hlog中。
3、hregionServer将数据后写入memstore中。
4、当内存中的数据达到阈值64M的时候,将数据Flush到硬盘中,并同时删除内存和hlog中的历史数据。
5、将硬盘中数据通过HFile来序列化,再将数据传输到HDFS进行存储。并对Hlog做一个标记。
6、当HDFS中的数据块达到4块的时候,Hmaster将数据加载到本地进行一个合并(如果合并后数据的大小小于256M则当数据块再次达到4块时(包含小于256M的数据块)将最新4块数据块再次进行合并,此时数据块大于256M)。
7、若数据块大于256M,则将数据重新拆分,将分配后的region重新分配给不同的hregionServer进行管理。
8、当hregionServer宕机后,将hregionServer上的hlog重新分配给不同的hregionServer进行加载(修改.META文件中关于数据所在server的信息)。注意:hlog会同步到HDFS中。
读原理
1) HRegionServer保存着 meta 表以及表数据,要访问表数据,首先 Client 先去访问zookeeper ,从 zookeeper 里面获取 meta 表所在的位置信息,即找到这个 meta 表在哪个HRegionServer 上保存着。
2)接着 Client 通过刚 才获取到的 HRegionServer 的 IP 来访问 Meta 表所在的HRegionServer ,从而读取到 Meta ,进而获取到 Meta 表中存放的元数据。
3)Client 通过元数据中存储的信息,访问对应的 HRegionServer ,然后扫描所在HRegionServer的Memstore和Storefile来查询数据。来查
4) 最后HRegionServerHRegionServer把查询到的数据响应给Client。。
##六、过滤器
过滤器原理
Hbase 提供了种类丰富的过滤器(filter)来提高数据处理的效率,用户可以通过内置或自定义的过滤器来对数据进行过滤,所有的过滤器都在服务端生效,即谓词下推(predicate push down)。这样可以保证过滤掉的数据不会被传送到客户端,从而减轻网络传输和客户端处理的压力。
setFilter(Filter filter)
// Scan 中定义的 setFilter
@Override
public Scan setFilter(Filter filter) {
super.setFilter(filter);
return this;
}
// Get 中定义的 setFilter
@Override
public Get setFilter(Filter filter) {
super.setFilter(filter);
return this;
}
###过滤器分类
####1.比较过滤器
所有比较过滤器均继承自 CompareFilter
。创建一个比较过滤器需要两个参数,分别是比较运算符和比较器实例。
public CompareFilter(final CompareOp compareOp,final ByteArrayComparable comparator) {
this.compareOp = compareOp;
this.comparator = comparator;
}
1.1 比较运算符
- LESS (<)
- LESS_OR_EQUAL (<=)
- EQUAL (=)
- NOT_EQUAL (!=)
- GREATER_OR_EQUAL (>=)
- GREATER (>)
- NO_OP (排除所有符合条件的值)
####1.2比较器 - BinaryComparator : 使用
Bytes.compareTo(byte [],byte [])
按字典序比较指定的字节数组。 - BinaryPrefixComparator : 按字典序与指定的字节数组进行比较,但只比较到这个字节数组的长度。
- RegexStringComparator : 使用给定的正则表达式与指定的字节数组进行比较。仅支持
EQUAL
和NOT_EQUAL
操作。 - SubStringComparator : 测试给定的子字符串是否出现在指定的字节数组中,比较不区分大小写。仅支持
EQUAL
和NOT_EQUAL
操作。 - NullComparator :判断给定的值是否为空。
- BitComparator :按位进行比较。
BinaryPrefixComparator
和 BinaryComparator
的区别不是很好理解,这里举例说明一下:
-
在进行
EQUAL
的比较时,如果比较器传入的是abcd
的字节数组,但是待比较数据是abcdefgh
: -
如果使用的是
BinaryPrefixComparator
比较器,则比较以abcd
字节数组的长度为准,即efgh
不会参与比较,这时候认为abcd
与abcdefgh
是满足EQUAL
条件的; -
如果使用的是
BinaryComparator
比较器,则认为其是不相等的。
比较过滤器种类
- RowFilter :基于行键来过滤数据;
- FamilyFilterr :基于列族来过滤数据;
- QualifierFilterr :基于列限定符(列名)来过滤数据;
- ValueFilterr :基于单元格 (cell) 的值来过滤数据;
- DependentColumnFilter :指定一个参考列来过滤其他列的过滤器,过滤的原则是基于参考列的时间戳来进行筛选 。
Filter filter = new RowFilter(CompareOperator.LESS_OR_EQUAL,
new BinaryComparator(Bytes.toBytes("xxx")));
scan.setFilter(filter);
###3.专用过滤器
专用过滤器通常直接继承自 FilterBase
,适用于范围更小的筛选规则。
####3.1单列列值过滤器 (SingleColumnValueFilter)
基于某列(参考列)的值决定某行数据是否被过滤
- setFilterIfMissing(boolean filterIfMissing) :默认值为 false,即如果该行数据不包含参考列,其依然被包含在最后的结果中;设置为 true 时,则不包含;
- setLatestVersionOnly(boolean latestVersionOnly) :默认为 true,即只检索参考列的最新版本数据;设置为 false,则检索所有版本数据。
3.2 单列列值排除器 (SingleColumnValueExcludeFilter)
3.3 行键前缀过滤器 (PrefixFilter)
基于 RowKey 值决定某行数据是否被过滤。
PrefixFilter prefixFilter = new PrefixFilter(Bytes.toBytes("xxx"));
scan.setFilter(prefixFilter);
3.4 列名前缀过滤器 (ColumnPrefixFilter)
基于列限定符(列名)决定某行数据是否被过滤。
####3.5 分页过滤器 (PageFilter)
可以使用这个过滤器实现对结果按行进行分页,创建 PageFilter 实例的时候需要传入每页的行数。
List<Long> list = new ArrayList<>();
list.add(1554975573000L);
TimestampsFilter timestampsFilter = new TimestampsFilter(list);
scan.setFilter(timestampsFilter);
3.6 时间戳过滤器 (TimestampsFilter)
3.7 首次行键过滤器 (FirstKeyOnlyFilter)
###4.包装过滤器
包装过滤器就是通过包装其他过滤器以实现某些拓展的功能。
4.1 SkipFilter过滤器
SkipFilter
包装一个过滤器,当被包装的过滤器遇到一个需要过滤的 KeyValue 实例时,则拓展过滤整行数据。下面是一个使用示例:
// 定义 ValueFilter 过滤器
Filter filter1 = new ValueFilter(CompareOperator.NOT_EQUAL,
new BinaryComparator(Bytes.toBytes("xxx")));
// 使用 SkipFilter 进行包装
Filter filter2 = new SkipFilter(filter1);
####4.2 WhileMatchFilter过滤器
WhileMatchFilter
包装一个过滤器,当被包装的过滤器遇到一个需要过滤的 KeyValue 实例时,WhileMatchFilter
则结束本次扫描,返回已经扫描到的结果。
###5.过滤器集
以上都是讲解单个过滤器的作用,当需要多个过滤器共同作用于一次查询的时候,就需要使用 FilterList
。FilterList
支持通过构造器或者 addFilter
方法传入多个过滤器。
// 构造器传入
public FilterList(final Operator operator, final List<Filter> filters)
public FilterList(final List<Filter> filters)
public FilterList(final Filter... filters)
// 方法传入
public void addFilter(List<Filter> filters)
public void addFilter(Filter filter)
多个过滤器组合的结果由 operator
参数定义 ,其可选参数定义在 Operator
枚举类中。只有 MUST_PASS_ALL
和 MUST_PASS_ONE
两个可选的值:
- MUST_PASS_ALL :相当于 AND,必须所有的过滤器都通过才认为通过;
- MUST_PASS_ONE :相当于 OR,只有要一个过滤器通过则认为通过。
###6.布隆过滤器
原理
布隆过滤器需要的是一个位数组和k个映射函数(和Hash表类似),在初始状态时,对于长度为m的位数组array,它的所有位都被置为0,如下图:
对于有n个元素的集合S={s1,s2…sn},通过k个映射函数{f1,f2,…fk},将集合S中的每个元素sj(1<=j<=n)映射为k个值{g1,g2…gk},然后再将位数组array中相对应的array[g1],array[g2]…array[gk]置为1:
如果要查找某个元素item是否在S中,则通过映射函数{f1,f2…fk}得到k个值{g1,g2…gk},然后再判断array[g1],array[g2]…array[gk]是否都为1,若全为1,则item在S中,否则item不在S中。这个就是布隆过滤器的实现原理。
即使array[g1],array[g2]…array[gk]都为1,能代表item一定在集合S中吗?不一定,因为有这个可能:就是集合中的若干个元素通过映射之后得到的数值恰巧包括g1,g2,…gk,那么这种情况下可能会造成误判,但是这个概率很小,一般在万分之一以下。所有,布隆过滤器的误判率和这K个映射函数的设计有关。
Hbase布隆过滤器介绍:
2.Bloomfilter在HBase中的作用?
HBase利用Bloomfilter来提高随机读(Get)的性能,对于顺序读(Scan)而言,
设置Bloomfilter是没有作用的(0.92以后,如果设置了bloomfilter为ROWCOL,对于指定了qualifier的Scan有一定的优化,但不是那种直接过滤文件,排除在查找范围的形式)
3.3.Bloomfilter在HBase中的开销?
Bloomfilter是一个列族(cf)级别的配置属性,如果你在表中设置了Bloomfilter,
那么HBase会在生成StoreFile时包含一份bloomfilter结构的数据,称其为MetaBlock;MetaBlock与DataBlock(真实的KeyValue数据)一起由LRUBlockCache维护。所以,开启bloomfilter会有一定的存储及内存cache开销。
4.Bloomfilter如何提高随机读(Get)的性能?
对于某个region的随机读,HBase会遍历读memstore及storefile(按照一定的顺序),将结果合并返回给客户端。如果你设置了bloomfilter,那么在遍历读storefile时,就可以利用bloomfilter,忽略某些storefile。
5.HBase中的Bloomfilter的类型及使用?
a)ROW, 根据KeyValue中的row来过滤storefile
举例:假设有2个storefile文件sf1和sf2,
sf1包含kv1(r1 cf:q1 v)、kv2(r2 cf:q1 v)
sf2包含kv3(r3 cf:q1 v)、kv4(r4 cf:q1 v)
如果设置了CF属性中的bloomfilter为ROW,那么get(r1)时就会过滤sf2,get(r3)就会过滤sf1
b)ROWCOL,根据KeyValue中的row+qualifier来过滤storefile
举例:假设有2个storefile文件sf1和sf2,
sf1包含kv1(r1 cf:q1 v)、kv2(r2 cf:q1 v)
sf2包含kv3(r1 cf:q2 v)、kv4(r2 cf:q2 v)
如果设置了CF属性中的bloomfilter为ROW,无论get(r1,q1)还是get(r1,q2),都会读取sf1+sf2;而如果设置了CF属性中的bloomfilter为ROWCOL,那么get(r1,q1)就会过滤sf2,get(r1,q2)就会过滤sf1
6.ROWCOL一定比ROW效果好么?
不一定
a)ROWCOL只对指定列(Qualifier)的随机读(Get)有效,如果应用中的随机读get,只含row,而没有指定读哪个qualifier,那么设置ROWCOL是没有效果的,这种场景就应该使用ROW
b)如果随机读中指定的列(Qualifier)的数目大于等于2,在0.90版本中ROWCOL是无效的,而在0.92版本以后,HBASE-2794对这一情景作了优化,是有效的(通过KeyValueScanner#seekExactly)
c)如果同一row多个列的数据在应用上是同一时间put的,那么ROW与ROWCOL的效果近似相同,而ROWCOL只对指定了列的随机读才会有效,所以设置为ROW更佳
7.ROWCOL与ROW只在名称上有联系,ROWCOL并不是ROW的扩展,不能取代ROW
8.region下的storefile数目越多,bloomfilter的效果越好
9.region下的storefile数目越少,HBase读性能越好
结论如下:
1.任何类型的get(基于rowkey和基于row+col)bloomfilter都能生效,关键是get的类型要匹配bloomfilter的类型
2.基于row的scan是没办法优化的
scan是一个范围,如果是row的bloomfilter不命中只能说明该rowkey不在此storefile中,但next rowkey可能在。
3.row+col+qualify的scan可以去掉不存在此qualify的storefile,也算是不错的优化了,而且指明qualify也能减少流量,因此scan尽量指明qualify。
而rowcol的bloomfilter就不一样了,如果rowcol的bloomfilter没有命中表明该qualifiy不在这个storefile中,因此这次scan就不需要scan此storefile了
##七、协处理器
1.作用:能够轻易建立二次索引、复杂过滤器(谓词下推)以及访问控制等
2.介绍
协处理器有两种:observer 和 endpoint
(1) Observer 类似于传统数据库中的触发器,当发生某些事件的时候这类协处理器会被 Server 端调用。Observer Coprocessor 就是一些散布在 HBase Server 端代码中的 hook 钩子, 在固定的事件发生时被调用。比如: put 操作之前有钩子函数 prePut,该函数在 put 操作
执行前会被 Region Server 调用;在 put 操作之后则有 postPut 钩子函数
● RegionObserver:提供客户端的数据操纵事件钩子: Get、 Put、 Delete、 Scan 等。
● WALObserver:提供 WAL 相关操作钩子。
● MasterObserver:提供 DDL-类型的操作钩子。如创建、删除、修改数据表等。
(2) Endpoint 协处理器类似传统数据库中的存储过程,客户端可以调用这些 Endpoint 协处 理器执行一段 Server 端代码,并将 Server 端代码的结果返回给客户端进一步处理,最常 见的用法就是进行聚集操作。如果没有协处理器,当用户需要找出一张表中的最大数据,即
max 聚合操作,就必须进行全表扫描,在客户端代码内遍历扫描结果,并执行求最大值的 操作。这样的方法无法利用底层集群的并发能力,而将所有计算都集中到 Client 端统一执 行,势必效率低下。利用 Coprocessor,用户可以将求最大值的代码部署到 HBase Server 端,
HBase 将利用底层 cluster 的多个节点并发执行求最大值的操作。即在每个 Region 范围内 执行求最大值的代码,将每个 Region 的最大值在 Region Server 端计算出,仅仅将该 max 值返回给客户端。在客户端进一步将多个 Region 的最大值进一步处理而找到其中的最大值。
这样整体的执行效率就会提高很多
下图是 EndPoint 的工作原理:
(3)总结
Observer 允许集群在正常的客户端操作过程中可以有不同的行为表现
Endpoint 允许扩展集群的能力,对客户端应用开放新的运算命令
observer 类似于 RDBMS 中的触发器,主要在服务端工作
endpoint 类似于 RDBMS 中的存储过程,主要在 client 端工作
observer 可以实现权限管理、优先级设置、监控、 ddl 控制、 二级索引等功能
endpoint 可以实现 min、 max、 avg、 sum、 distinct、 group by 等功能
协处理器加载方式
1、静态加载
通过修改 hbase-site.xml 这个文件来实现, 启动全局 aggregation,能过操纵所有的表上 的数据。只需要添加如下代码
<property>
<name>hbase.coprocessor.user.region.classes</name>
<value>org.apache.hadoop.hbase.coprocessor.AggregateImplementation</value>
</property>
2、动态加载
启用表 aggregation,只对特定的表生效。通过 HBase Shell 来实现。
disable 指定表。 hbase> disable 'mytable'
添加 aggregation
hbase> alter 'mytable', METHOD => 'table_att','coprocessor'=>
'|org.apache.Hadoop.hbase.coprocessor.AggregateImplementation||'
重启指定表 hbase> enable 'mytable'
3、二级索引案例
row key 在 HBase 中是以 B+ tree 结构化有序存储的,所以 scan 起来会比较效率。单表以 row key 存储索引, column value 存储 id 值或其他数据 ,这就是 Hbase 索引表的结构。
3.1基于ES建立二级索引
HBase的Observer需要监测HBase的插入和更新操作:将相应的业务逻辑代码部署到Put和Delete等钩子函数中,当发生Put操作时,将Put数据转化为JSON格式,索引到Elasticsearch中,并将RowKey和ES的文档ID建立关联。当发生Delete操作时,获取Delete数据的RowKey,删除ElasticSearch中对应ID的document记录,从而完成同步。考虑到未来HBase的写入会比较频繁,Put操作性能太低,借助ES的缓冲机制Bulk,用户的提交的数据积累到某个阈值时才进行批量操作,降低网络I/O负载,提高建引的效率。数据和索引的同步一致性问题,是本方案最为重要的一个环节。HBase为了加快写入速度,通常会将数据先缓存到内存的MemStore(写入缓冲区),当达到一定的阈值时,进行flush操作刷新到磁盘,永久持久化到HFile文件中,存入到HDFS。
##九、LSM Tree以及跳表在Hbase中的作用
9.1、LSM Tree 是什么
所谓 LSM(The Log-Structured Merge-Tree),即日志结构合并树,是由两个或两个以上存储数据的结构组成的,每一个数据结构各自对应自己的存储介质。
9.2、简易模型描述
最简单的 LSM 模型是是被 Patrick O’Neil 提出来的『Two-Level LSM Tree』。这个简易数据结构是由两个树状结构构成,这两颗树分别为 C0 和 C1。C0 比较小,并且全部驻于内存之中,而 C1 则驻于磁盘。一条新的记录先是从 C0 中插入,如果这一次的插入造成了 C0 数据量超出了阀值,那么 C0 中的某些数据片段则会迁出并合并到 C1 树中。其合并排序算法是批量的,由于是顺序存储,速度相当快。
然而通常情况下,每次持久化到硬盘中是一条独立的线程做的,并且生成单独的文件,因此C1树也不止一个文件,当文件数变多的时候,势必导致每一次查询都会涉及到大量文件的打开,每一次文件的打开都是对 I/O 的消耗。为了控制这种 读放大 的情况出现,LSM Tree 必须要考虑小文件合并的问题。
三层模型与合并算法
简单来说,SSTable(Sorted String Table) 的存储是分层的(后续介绍 SSTable 的具体模型),Level 0 中,数据在内存里,往往是以树的结构来展现,当达到一定阀值的时候,其数据以 key 值作为索引,并排列成有序的数据持久化到磁盘上,每个线程在磁盘上对应一个独立的文件或者是一个 key 序列范围的文件集合。如果要执行一次搜索,每一个线程都需要在 Level 0 涉及到的文件中根据指定 key 查相关值。当文件数膨胀之后,每一次 Key 查询将会打开大量的文件(同个 key 可能存在于多个文件中),涉及到大量的 I/O 交互,这样性能很差。这里的检索算法复杂度通常为O(Klg(n)) K 为文件打开数。
在 Level 0 中一个特定的 key 可能会出现在多个不同的文件中(不同版本),大多数应用里都是默认取最新版本的 key-value 数据。而对于 Cassandra 来说,每个 value 对应的是数据库里的一行,而不同版本则对应数据的不同字段。因此查询中可能还需要带版本号查出对应的值,避免不了打开多个文件逐一校对。
三层模型就是从性能角度考虑,将小文件合并成大文件,且除了 Level 0 以外的数据的 key 值是不允许有交集的。
LevelDB 的 LSM 经典实现
SSTable 与三层模型
而这里的 SSTable 并不是树结构,而是单纯的 KV 结构组成的序列,每一个 key 值的 offset 都存储于 index 之中。
结合三层模型来看,Level 0 的 SSTable 就是 MemTable( C0树 )直接刷进磁盘的文件,允许出现 SSTable 的 key 有交集的情况;Level 1 这一层开始的每一个 key 不允许有交集,也就是说这里的处理逻辑是将 Level 0 中与 Level 1 中的 重复key 归并成新的文件到 Level 1。当触发文件个数的阀值时,Level 1 向上一层再进行合并。
对数据每一层都要进行检索,找出默认的版本数据,通常默认为最新版本。
随机读写
1.磁盘上的 SSTable 索引需要永远加载在内存里;
2.所有写操作,直接写 MemTable (写文件缓存,即内存,保证随机写入速度);
3.读数据的时候先从 MemTable 检索,然后再从 SSTable 的索引中检索;
4.MemTable 周期性 flush 到磁盘中;
5.SSTables 周期性合并;
6.落盘的数据不可变更,更新和删除操作并不是真正的物理修改和删除,只是增加版本号。
写是写内存,因此随机写十分快;读也是读内存里的 SSTable 的索引,并且这里每一个SSTable索引如果用二分法查找,算法复杂度大致在 O(lg(n))与O(n)之间,因此随机读也不慢。
HBase 的 LSM 实现
HBase 则和上边的类似,把 HBase 套用到 LSM 中,Memstore 就是上边的 Memtable,HFiles 就是上边的 SSTables,除此之外,HBase 也和上边对比有一些区别。
##十、Hbase优化
1) NameNode 元数据备份使用 SSD
2) 定时备份 NameNode 上的元数据
每小时或者每天备份,如果数据极其重要,可以 5~10 分钟备份一次。备份可以通过定时任务复制元数据目录即可。
3) 为 NameNode 指定多个元数据目录
使用 dfs.name.dir 或者 dfs.namenode.name.dir 指定。这样可以提供元数据的冗余和健壮性, 以免发生故障。
4) NameNode 的 dir 自恢复
设置 dfs.namenode.name.dir.restore 为 true,允许尝试恢复之前失败的 dfs.namenode.name.dir
目录,在创建 checkpoint 时做此尝试,如果设置了多个磁盘,建议允许。
5) HDFS 保证 RPC 调用会有较多的线程数
6) HDFS 副本数的调整
7) HDFS 文件块大小的调整等
1)预分区及RowKey设计
2)内存优化
##十一、基本命令
##十二、CDH集成Hbase
##十三、数据迁移的方案
Hadoop层有一类,HBase层有三类。
Hadoop层数据迁移:DistCp
hadoop distcp hdfs://src-hadoop-address:9000/table_name hdfs://dst-hadoop-address:9000/table_name
独立的MR执行
hadoop distcp \
-Dmapreduce.job.name=distcphbase \
-Dyarn.resourcemanager.webapp.address=mr-master-ip:8088 \
-Dyarn.resourcemanager.resource-tracker.address=mr-master-dns:8093 \
-Dyarn.resourcemanager.scheduler.address=mr-master-dns:8091 \
-Dyarn.resourcemanager.address=mr-master-dns:8090 \
-Dmapreduce.jobhistory.done-dir=/history/done/ \
-Dmapreduce.jobhistory.intermediate-done-dir=/history/log/ \
-Dfs.defaultFS=hdfs://hbase-fs/ \
-Dfs.default.name=hdfs://hbase-fs/ \
-bandwidth 20 \
-m 20 \
hdfs://src-hadoop-address:9000/region-hdfs-path \
hdfs://dst-hadoop-address:9000/tmp/region-hdfs-path
第一步:如果是迁移实时写的表,最好是停止集群对表的写入,迁移历史表的话就不用了,此处举例表名为test;
第二步, flush表, 打开HBase Shell客户端,执行如下命令:
flush 'test'
第三步,拷贝表文件到目的路径,检查源集群到目标集群策略、版本等,确认没问题后,执行如上带MR参数的命令
第四步, 检查目标集群表是否存在,如果不存在需要创建与原集群相同的表结构
第五步,在目标集群上,Load表到线上,在官方Load是执行如下命令:hbase org.jruby.Main add_table.rb /hbase/data/default/test
第六步,检查表数据是否OK,看bulkload过程是否有报错
三、HBase层数据迁移
copyTable方式
copyTable也是属于HBase数据迁移的工具之一,以表级别进行数据迁移。copyTable的本质也是利用MapReduce进行同步的,与DistCp不同的时,它是利用MR去scan 原表的数据,然后把scan出来的数据写入到目标集群的表。这种方式也有很多局限,如一个表数据量达到T级,同时又在读写的情况下,全量scan表无疑会对集群性能造成影响。
1.表深度拷贝:相当于一个快照,不过这个快照是包含原表实际数据的,0.94.x版本之前是不支持snapshot快照命令的,所以用copyTable相当于可以实现对原表的拷贝, 使用方式如下:
create 'table_snapshot',{NAME=>"i"}
hbase org.apache.hadoop.hbase.mapreduce.CopyTable --new.name=tableCopy table_snapshot
2.集群间拷贝:在集群之间以表维度同步一个表数据,使用方式如下:
create 'table_test',{NAME=>"i"} #目的集群上先创建一个与原表结构相同的表
hbase org.apache.hadoop.hbase.mapreduce.CopyTable --peer.adr=zk-addr1,zk-addr2,zk-addr3:2181:/hbase table_test
3.增量备份:增量备份表数据,参数中支持timeRange,指定要备份的时间范围,使用方式如下:
hbase org.apache.hadoop.hbase.mapreduce.CopyTable ... --starttime=start_timestamp --endtime=end_timestamp
4.部分表备份:只备份其中某几个列族数据,比如一个表有很多列族,但我只想备份其中几个列族数据,CopyTable提供了families参数,同时还提供了copy列族到新列族形式,使用方式如下:
hbase org.apache.hadoop.hbase.mapreduce.CopyTable ... --families=srcCf1,srcCf2 #copy cf1,cf2两个列族,不改变列族名字
hbase org.apache.hadoop.hbase.mapreduce.CopyTable ... --families=srcCf1:dstCf1, srcCf2:dstCf2 #copy srcCf1到目标dstCf1新列族
3.2 Export/Import方式
Export阶段: 将原集群表数据Scan并转换成Sequence File到Hdfs上,因Export也是依赖于MR的,如果用到独立的MR集群的话,只要保证在MR集群上关于HBase的配置和原集群一样且能和原集群策略打通(master®ionserver策略),就可直接用Export命令,如果没有独立MR集群,则只能在HBase集群上开MR,若需要同步多个版本数据,可以指定versions参数,否则默认同步最新版本的数据,还可以指定数据起始结束时间,使用如下:
# output_hdfs_path可以直接是目标集群的hdfs路径,也可以是原集群的HDFS路径,如果需要指定版本号,起始结束时间
hbase org.apache.hadoop.hbase.mapreduce.Export <tableName> <ouput_hdfs_path> <versions> <starttime> <endtime>
Import阶段: 将原集群Export出的SequenceFile导到目标集群对应表,使用如下:
#如果原数据是存在原集群HDFS,此处input_hdfs_path可以是原集群的HDFS路径,如果原数据存在目标集群HDFS,则为目标集群的HDFS路径
hbase org.apache.hadoop.hbase.mapreduce.Import <tableName> <input_hdfs_path>
3.3 Snapshot方式
略
##十四、Hbase的RowKey设计方案
rowkey长度原则
rowkey是一个二进制码流,可以是任意字符串,最大长度 64kb ,实际应用中一般为10-100bytes,以 byte[] 形式保存,一般设计成定长。
建议越短越好,不要超过16个字节,原因如下:
1.数据的持久化文件HFile中是按照KeyValue存储的,如果rowkey过长,比如超过100字节,1000w行数据,光rowkey就要占用100*1000w=10亿个字节,将近1G数据,这样会极大影响HFile的存储效率;
2.MemStore将缓存部分数据到内存,如果rowkey字段过长,内存的有效利用率就会降低,系统不能缓存更多的数据,这样会降低检索效率。
3.目前操作系统都是64位系统,内存8字节对齐,控制在16个字节,8字节的整数倍利用了操作系统的最佳特性。
rowkey散列原则
如果rowkey按照时间戳的方式递增,不要将时间放在二进制码的前面,建议将rowkey的高位作为散列字段,由程序随机生成,低位放时间字段,这样将提高数据均衡分布在每个RegionServer,以实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息,所有的数据都会集中在一个RegionServer上,这样在数据检索的时候负载会集中在个别的RegionServer上,造成热点问题,会降低查询效率。
rowkey唯一原则
必须在设计上保证其唯一性,rowkey是按照字典顺序排序存储的,因此,设计rowkey的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块。
加盐、哈希、反转、时间戳反转
##十五、Hbase的高可用
下回分解
##十、Hbase的RPC机制
下回分解