HBase基础入门|原理

HBase是一个面向海量数据场景,分布式的、多版本、面向列的开源KV数据库。运行在HDFS的基础上,支持PB级别、百万列的数据存储。

基本概念

Region

    • 数据的集合,HBase中的一个表拥有1到多个Region,一个Region是多个row的集合,在Region中排序是按照rowKey进行字典排序。
    • Region不能跨服务器,一个RegionServer上有一个或者多个Region
    • 数据量小的时候一个Region足以存储所有数据,当数据量足够大时会对Region进行拆分
    • 当HBase进行负载均衡的时候,有可能从一台机器的RegionServer上把Region移动到另一台RegionServer上

RegionServer

    • 一个服务器一般只会安装一个RegionServer,如果安装多个,修改默认端口去部署
    • 当客户端从Zookeeper获取RegionServer地址之后,会直接从RegionServer获取数据
    • 插入、删除等操作都是直接操作RegionServer,不需要经过master

Master

只负责各种例如建表、删表、移动Region、合并等操作。他们的共性就是需要跨RegionServer,这些操作由哪个RegionServer都不合适,所有HBase将这些操作放到了Master。

rowKey

完全由用户指定的一串不重复的字符串。根据字典排序,如果插入HBase的时候rowKey出现碰撞,就会把之前的row更新掉。之前的数据会被放到拉链表里面,需要根据版本号才能查询该被覆盖调的数据。

单元格

一个列上可以存储多个版本的单元格,单元格就是数据存储的最小单元,多个版本的值存储在多个单元格里面,多个版本之间用版本号version来区分,所以唯一确定一条数据的表达式rowKey && column family && column && version。如果不写版本号,默认返回是最后一个版本的数据,每一个列或者单元格的值都被赋予了一个时间戳,这个时间戳可以自动生成,也可以用户自定义。

column family(列族)

在HBase中,若干列可以组成列族。

HBase会把想用的列族信息尽量放到同一台机器上,如果想让某几列被放到一起,就给他们使用相同的列族。一个表要设置的列族越少越好,因为HBase不希望用户指定太多的列族,虽然HBase是分布式数据库,但是数据分布在同一台机器上数据查询会更快。

HBase中每一行数据都是离散的,因为列族,一行里不同的列会被分配到不同的服务器,行的概念被削弱到一个抽象的存在。如果多个列标记为同一个rowKey,则说明他们是同一行数据。

在HBase中,每一个存储语句都必须精确的写出数据要被存储到哪些单元格,而单元格是由表是由表:列族:行:列来定义,也就是说一行10列的数据要精确的写出数据被存储哪个表哪个列族的哪一行的哪一列,而传统数据库存储语句可以把整行数据一次性写在行语句里。例如(HBase版本:hbase:0.94-adh3u11.7.9):

HTablePool hTablePool;
Configuration conf = HBaseConfiguration.create();
conf.setBoolean(DiamondAddressHelper.DIMAOND_HBASE_UNITIZED, true);
conf.set(DiamondAddressHelper.DIAMOND_HBASE_KEY_NEW, "");
conf.set(DiamondAddressHelper.DIAMOND_HBASE_GROUP, "");
this.hTablePool = new HTablePool(conf, Integer.valueOf(htablePoolMaxSize));
HTableInterface table = this.hTablePool.getTable("表名");
// 指定行rowKey
Put put = new Put(Bytes.toBytes(rowKey));
put.add(Bytes.toBytes("列族"), Bytes.toBytes("列名1"), Bytes.toBytes("列值1"));
put.add(Bytes.toBytes("列族"), Bytes.toBytes("列名2"), Bytes.toBytes("列值2"));
put.add(Bytes.toBytes("列族"), Bytes.toBytes("列名3"), Bytes.toBytes("列值3"));
put.setDurability(Durability.SYNC_WAL);  
table.put(put);
table.close();  

HBase与传统数据库区别

HBase模块说明

HBase基础入门|原理_第1张图片

Master

  1. Master用于协调多个Region Server,侦测各个Region Server之间的状态,平衡Region Server之间的负载。
  2. 负责分配Region给Region Server
  3. 在Zookeeper的协调下,多个Master节点可以共存,但只有一个Master提供服务。当提供的服务的Master节点宕机时,其他Master会接管HBASE集群。

Region Server

  1. 一个Region Server包含多个Region,Region Server的作用只是管理表格,以及实现读写操作。
  2. Client通过直连Region Server并通信获取HBASE数据。
  3. Region是真实存放HBASE数据的地方,是HBASE可用性和分布式的基本单位。
  4. 如果一个表格很大,并由多个CF组成,表的数据将存放在多个Region之间,并且每个Region中会关联多个存储的单元。

Zookeeper

  1. 保证Hbase master集群的HA,保证至少有一个HBASE master处于运行状态
  2. 负责的Region和Region Server的注册

  1. HBase集群是通过zk来进行机器之间的协调,HBase master和Region Server之间都是通过zk来维护。
  2. 当client访问HBASE集群时,client需要先和zk通信,然后找到对应的Region Server(Meta表)。
  3. 每个Region Server管理着多个Region,Region是HBASE并行化的基本单位(分布式存储最小单位,但不是最小的存储单位),每一个Region都只存储一个Column Family的数据,并且是该CF中一段。Region存储的数据大小是有上限的,当达到Threshold时,Region会进行分裂,数据会分裂到多个Region中,以便提高数据的并行化和容量。
  4. 每个Region包含着多个Store对象,每个Store包含一个MemStore和一个或多个HFile。MemStore是数据在内存中的实体并且是有序的,当数据写入时会先写入MemStore,当达到MemStore配置的阈值时,会把内存中的数据像底层文件系统倾倒(Dump),此时Store便会创建StoreFile,StoreFile就是对HFile的一层封装。MemStore中的数据最终会写到HFile中(磁盘)。StoreFile是只读的,一旦创建后就不可以再修改,因此HBase的更新操作其实是不断追加的操作。
  5. HBase的数据可靠性是通过HLog来实现的。不同Region的日志会混在一起,这样做的目的不断追加单个文件,相对于同时写多个文件可以减少磁盘寻址次数,提高对table的写性能。缺点是当一台Region Server下线,为了恢复他的Region,需要将此Region Server的log进行拆分,然后分发到其他Region Server进行恢复。
  6. HLog机制是WAL(write-ahead-logging)的一种实现。每个Region Server都有一个HLog实例,Region Server会将更新操作先记录到HLog中,然后再写入MemStore。当Hbase宕机时,MemStore还没写入HFile或者StoreFile还没有持久化,这时候数据可用通过HLog找回。HLog就是一个普通的HadoopSequence File,SequenceFile的Key是HLogKey对象。HLogKey对象中记录了写入数据的归属信息,包括Table、Region、SequenceNumber、Timestamp。
  7. 对于HFile的可靠性是有HDFS来保证,数据默认会有3个备份。HFile由很多歌数据块(Block)组成,并且有一个固定的结尾块。
    1. Data Block由一个Header(包含一些随机数字,防止数据损坏)和多个Key-Value的键值组成。DataBlock保存表中的数据,是HBase I/O的基本单位,为了提高效率,Region Server采用了LRU的blockcache机制。DataBlock大小默认64K,可以在创建table的时候指定,大号的block有利于顺序scan,小号的scan有利于随机查询。
    2. Trailer包含了数据相关的索引信息,有指针指向其他数据块的起始点。读取一个HFile时,会首先读取Trailer,然后DataBlockIndex会被读取到内存中,这样当检索某个Key时,不需要扫描整个HFile,只需要从内存中找到key所在的block,通过一次磁盘IO就将整个block读取到内存中,然后找到指定key。
  1. 在HStore中,由于MemStore每次刷写都会生成一个新的HFile,且同一个字段的额不同版本和不同类型有可能会分布在不同HFile中,因此查询时需要遍历所有的HFile。为了减少HFile的个数以及清理过期和删除的数据,当StoreFile文件数量增长到一个阈值,会触发compact合并操作,将多个StoreFiles合并成一个StoreFile,合并过程中会进行版本合并和数据删除。
      • Compation分为Minor Compaction和Major Compaction两种
      • Minor Compaction会将临近的若干较小的HFile合并成一个较大的HFile,但不会清理过期和删除的数据。
      • Major Compaction会将一个Store下所有HFile合并成一个HFile,并且会清理调过期和删除的数据。

如下图所示:

  1. Region Split

默认情况下,每个table起始只有一个Region,随着数据不断写入,Region会自动进行拆分。刚拆分时,两个子Region都位于当前Region Server,但出于负载均衡的考虑,HMaster有可能会将某个Region转移到其他Region Server。

split时机

0.94之前

    • 当1个Region中的某个Store下所有StoreFile总大小超过hbase.hregion.max.filesize,该Region进行拆分

0.94之后

    • 当1个Region中某个Store下所有的StoreFile总大小超过了Min(R^2 * hbase.hregion.memstore.flush.size,hbase.hregion.max.filesize),进行拆分,R为当前Region Server中属于该Table的个数。

HBase数据库是否高效与rowKey设计关系密切,概括起来宗旨只有一个,那就是尽可能选择一个可以使你数据均匀分布在集群中的rowKey。

对于频繁写的场景,随机rowKey性能更好;

对于频繁读的场景,有序的rowKey性能更好;

对于时间连续的数据,有序的rowKey方便使用Scan操作。

双主集群下HBase数据一致性

Last-write-wins(Hbase)

在对一条记录应用多个修改的时候,最后的改动会覆盖之前的操作,返回给客户端的记录都以最后一次改动的为准。在实际操作中,基于LWW的策略并一定正常,比如多个节点对同一个记录进行修改,如果节点服务的始终不是严格对等的,不一定完全遵循LWW,其实我们在实际业务场景操作幂等性的数据时比较适合使用双主操作。

vector-clock(Cassandra)

vector-clock是由Lamport演化而来,Lomport只有每个进程本的本地时间,没有其他进程时间。向量时钟算法利用了向量这种数据结构将全局的各个进程的逻辑时间戳广播给各个进程,每个进程发送事件时都会将当前一直的所有进程时间写入一个向量中。需要说明一下向量时钟是用来检测发现分布式系统中多副本的数据冲突问题,但是他并不解决问题,解决还是通过LWW。

v0-->v1-->v2-->[v1,v2]

v0-->v1-->v1-->[v0,v2]

二级索引

二级索引的思想:简单理解就是,根据列族的列的值,查出rowkey,再按照rowkey就能很快从hbase查询出数据,我们需要构建出根据列族的列的值,很快查出rowkey的方案

常见的二级索引方案

MapReduce方案

原表:

row rowkey1 f:nam a

row rowkey2 f:name b

row rowkey3 f:name c

如果我们想根据Id查询,需要再建一张hbase表

row a f:id rowkey

row b f:id rowkey2

row c f:id rowkey3

先通过name找到rowkey,然后通过rowKey找到原表数据,和MySQL的回表操作类似。

Coprocessor方案

1、Coprocessor提供了一种机制可以让开发者直接在RegionServer上运行自定义代码来管理数据。通常我们使用get或者scan来从Hbase中获取数据,使用Filter过滤掉不需要的部分,最后在获得的数据上执行业务逻辑。但是当数据量非常大的时候,这样的方式就会在网络层面上遇到瓶颈。客户端也需要强大的计算能力和足够大的内存来处理这么多的数据,客户端的压力就会大大增加。但是如果使用Coprocessor,就可以将业务代码封装,并在RegionServer上运行,也就是数据在哪里,我们就在哪里跑代码,这样就节省了很大的数据传输的网络开销。

2、Coprocessor有两种:Observer和Endpoint EndPoint主要是做一些计算用的,比如计算一些平均值或者求和等等。而Observer的作用类似于传统关系型数据库的触发器,在一些特定的操作之前或者之后触发。学习过Spring的朋友肯定对AOP不陌生,想象一下AOP是怎么回事,就会很好的理解Observer了。Observer Coprocessor在一个特定的事件发生前或发生后触发。在事件发生前触发的Coprocessor需要重写以pre作为前缀的方法,比如prePut。在事件发生后触发的Coprocessor使用方法以post作为前缀,比如postPut。Observer Coprocessor的使用场景如下:

2.1. 安全性:在执行Get或Put操作前,通过preGet或prePut方法检查是否允许该操作;

2.2. 引用完整性约束:HBase并不直接支持关系型数据库中的引用完整性约束概念,即通常所说的外键。但是我们可以使用Coprocessor增强这种约束。比如根据业务需要,我们每次写入user表的同时也要向user_daily_attendance表中插入一条相应的记录,此时我们可以实现一个Coprocessor,在prePut方法中添加相应的代码实现这种业务需求。

2.3.二级索引:可以使用Coprocessor来维持一个二级索引。正是我们需要的

索引设计思想

我们的需求是找出满足cf1:col2=c22这条记录的cf1:col1的值,实现方法如图,首先根据cf1:col2=c22查找到该记录的行键,然后再通过行健找到对应的cf1:col1的值。其中第二步是很容易实现的,因为Hbase的行键是有索引的,那关键就是第一步,如何通过cf1:col2的值找到它对应的行键。很容易想到建立cf1:col2的映射关系,即将它们提取出来单独放在一张索引表中,原表的值作为索引表的行键,原表的行键作为索引表的值,这就是Hbase的倒排索引的思想。

elasticesearch+HBASE方案

比如说你现在有一行数据

id name age ….30 个字段

但是你现在搜索,只需要根据 id name age 三个字段来搜索

如果你傻乎乎的往 es 里写入一行数据所有的字段,就会导致说70%的数据是不用来搜索的,结果硬是占据了es机器上的filesystem cache的空间,单挑数据的数据量越大,就会导致 filesystem cahce 能缓存的数据就越少仅仅只是写入 es 中要用来检索的少数几个字段就可以了,比如说,就写入 es id name age 三个字段就可以了,然后你可以把其他的字段数据存在 mysql 里面,我们一般是建议用 es + hbase 的这么一个架构。

hbase 的特点是适用于海量数据的在线存储,就是对 hbase 可以写入海量数据,不要做复杂的搜索,就是做很简单的一些根据 id 或者范围进行查询的这么一个操作就可以了。

从 es 中根据 name 和 age 去搜索,拿到的结果可能就 20 个 doc id,然后根据 doc id 到 hbase 里去查询每个 doc id 对应的完整的数据,给查出来,再返回给前端。

HBase基础入门|原理_第2张图片

Hbase数据结构

很多数据库或文件系统都使用 B+ 树作为存储数据的数据结构,但是HBase 却使用的是 LSM(Log-Structured Merge Tree)树。

B+ 树虽然适合在磁盘中存储,并且从原理上来看它的读速度很快。但是它并非总是顺序读写磁盘,例如它的节点进行分裂操作时在内存中会拆成两个新的页表,存储到磁盘上很可能就是不连续的;或者其他更新插入删除等操作,需要循环利用磁盘快,也会造成不连续问题。这也是 HBase 不使用 B+ 树的原因,不进行优化的话随机 I/O 太多,范围查询和大量随机写时尤其明显。

LSM 树在读写之间作出取舍,通过牺牲部分读性能,使用顺序写来大幅提高写性能,因此适合写多读少,以及大规模数据读取的场景。

LSM 树首先在内存中构建一颗有序的小树,随着小树的逐渐增大,达到一定阈值时会 flush 到磁盘上。所以 LSM 树不像 B+ 树一样是一棵完整的大树,一棵 LSM 树就是一个个 B+ 树合起来。多次flush之后会形成多个数据存储文件,后台线程会按照配置自动将多个文件合并成一个,此时多颗小树就会被合并成一棵大树。但是读取时,由于不知道数据在哪棵小树上,因此必须遍历所有小树(所以才说 LSM 牺牲了部分读的性能),每棵小树内部数据是有序的。查询是先查内存中的部分,再去查磁盘上的部分。

你可能感兴趣的:(数据库,hbase,大数据,分布式)