文章目录
- 1、 初识 HBase
- 1.1、海量数据与 NoSQL
- 1.1.1、关系型数据库的极限
- 1.1.2、CAP理论
- 1.1.3、NoSQL
- 1.2、HBase是怎么来的?
- 1.3、为什么要用HBase
- 1.4、基本概念
- 1.4.1、整体架构
- 1.4.1.1、Region 是什么
- 1.4.1.2、RegionServer 是什么
- 1.4.1.3、Master 是什么
- 1.4.2、存储架构
- 1.4.2.1、行键(rowkey)是什么
- 1.4.2.2、列族是什么
- 1.4.2.3、单元格(cell)
- 1.4.2.4、Region 跟行的关系
- 1.4.3、与传统数据库的对比
- 2、HBase 是怎么存储数据的
- 2.1、HBase 宏观架构
- 2.2、RegionServer的内部架构
- 2.3、预写日志
- 2.3.1、如何关闭/打开 WAL
- 2.3.2、延迟(异步)同步写入 WAL
- 2.3.3、WAL 滚动
- 2.3.4、WAL 文档归档之后去了哪
- 2.3.5、MemStore
- 2.3.6、HFile
- 2.4、增删改查的实现
- 2.4、数据单元层次图
- 3、HBase 读写数据
- 3.1、写数据流程
- 3.2、读数据流程
- 4、Region 的定位
- 4.1、三层查询架构(0.96版本之前)
- 4.2、二层查询架构(0.96版本之后)
1、 初识 HBase
1.1、海量数据与 NoSQL
1.1.1、关系型数据库的极限
类似 MySQL 或者 Oracle 关系型数据库,当用户表的数据达到几千万甚至几亿级别的时候,对单挑数据的检索将花费数秒甚至达到分钟级别。目前在海量数据下进行快速开发,并进行高效运行的需求越来越多,传统的关系型数据库似乎达到了瓶颈,怎么办?
1.1.2、CAP理论
有的专家尝试将关系型数据库做成分布式数据库,把压力分摊到多个服务器上,但是,随之而来的问题则是很难保证原子性。原子性可是数据库最根本的 ACID 中的元素,如果没有了原子性,数据库就不可靠了。如果增加一些必要的操作,那么原子性是保证了,但是性能却大幅下降。专家们始终没有办法构建出一个既有完美原子性又具高性能的分布式数据库。
此时人们想起来20世纪90年代初期 Berkerly 大学教授提出来的 CAP 理论。
全称是 Consistency Availability and Partition tolerance;
- Consistency(一致性):数据一致更新,所有数据变动都是同步的。
- Availability(可用性):良好的响应性能。
- Partition tolerance(分区容错性):可靠性。
教授给出的定理是:
任何分布式系统只可同时满足二点,没法三者兼顾。
教授给出的忠告是:
架构师不要将精力浪费在如何设计能满足三者的完美分布式系统,而是应该进行取舍。
1.1.3、NoSQL
对 CAP 特性的放弃带来了一种全新型的数据库——非关系型数据库。和关系型数据库正好相反,非关系型数据库NoSQL对事务性的要求并不严格,甚至可以说是相当马虎。
HBase 正是这种类型的 NoSQL 数据库,很多人以为 NoSQL 是非 SQL 的意思,其实它是 Not Only SQL 的缩写,意思是不只是 SQL。
1.2、HBase是怎么来的?
2006年 Google 发布了一篇 Bigtable 文章,该文章向世人介绍了一种分布式的数据库,这种数据库可以再局部几台服务器崩溃的情况下继续提高性能的服务。
2007年 Powerset 公司的工作人员基于此文研发了 BigTable 的 Java 开源版本,即 HBase。
2008年 HBase 成了 Apache 的顶级项目。HBase 几乎实现了 BigTable 的所有特性。它被成为一个开源的非关系型分布式数据库。
1.3、为什么要用HBase
HBase 的存储是基于 Hadoop 的。Hadoop 是这些年崛起的拥有高性能,高稳定,可管理的大数据应用平台。 Hadoop 已经快要变为大数据的代名词了,基于 Hadoop 衍生出了大量优秀的开源项目。
Hadoop 实现了一个分布式文件系统(HDFS)。HDFS 有高容错性的特点,被设计用来部署在低廉的硬件上,而且它提供高吞吐量以访问应用程序的数据,适合哪些有着超大数据集的应用程序。基于 Hadoop 意味着 HBase 与生俱来的超强的扩展性和吞吐量。
HBase 采用的是 Key/Value 的存储方式,这意味着,即使随着数据量增大,也几乎不会导致查询性能下降。HBase又是列式数据库(对比于传统的行式数据库而言),当你的字段很多的时候,你甚至可以把其中几个字段放在集群中的一部分机器上,而另外几个字段放到另外一部分机器上,充分分散了负载压力。然而,如此复杂的存储结构和分布式的存储方式带来的代价就是:哪怕只是存储少量数据,它也不会很快。所以说:
HBase并不快,只是当数据量很大的时候它慢的不明显
凡事都不可能只有优点儿没有缺点。数据分析是HBase的弱项,因为对于HBase乃至整个 NoSQL 生态圈来说,基本上都是不支持表关联的。当你想实现group by或者 order by 的时候,你会发现,你需要写很多的代码来实现 MapReduce 。
因此,请不要盲目地使用HBase。
当你的情况大体上符合一下任意一种的时候,请不要使用 HBase :
- 主要需求是数据分析,比如做报表。
- 单表数据量不超过千万。
当你的情况是以下场景,可以考虑使用 HBase:
- 单表数据量超千万,而且并发还挺高。
- 数据分析需求较弱,或者不需要那么灵活或者实时。
1.4、基本概念
1.4.1、整体架构
从 HBase 的部署架构上来说,HBase 有两种服务器:Master 服务器和 RegionServer 服务器。一般一个 HBase 集群有一个 Master 服务器和几个 RegionServer 服务器。Master 服务器负责维护表结构信息,实际的数据都存储在 RegionServer 服务器上,RegionServer 保存的表数据直接存储在 Hadoop 的 HDFS 上,整体架构图如下:
RegionServer 非常依赖 ZooKeeper 服务,可以说没有 ZooKeeper 就没有 HBase。ZooKeeper 在 HBase 中扮演的角色类似一个管家。ZooKeeper 管理了 HBase 所有 RegionServer 的信息,包括具体的数据段存放在哪个 RegionServer 上。
1.4.1.1、Region 是什么
Region 就是一段数据的集合。HBase 中的表一般拥有一个多个 Region。Region 有以下特征:
- Region 不能跨服务器,一个 RegionServer 上有一个或者多个 Region 。
- 数据量小的时候,一个 Region 足以存储所有数据;但是,当数据量大的时候,HBase 会 拆分 Region。
- 当 HBase 在进行负载均衡的时候,也有可能会从一台 RegionServer 上把 Region 移动到另一台 RegionServer 上。
- Region 是基于HDFS 的,它的所有数据存取操作都是调用了 HDFS 的客户端接口来实现的。
1.4.1.2、RegionServer 是什么
RegionServer 就是存放 Region 的容器,直观上说就是服务器上的一个服务。一般来说,一个服务器上只会安装一个 RegionServer 服务,不过也可以安装多实例,当客户端从 Zookeeper 获取 RegionServer 的地址后,它会直接从 RegionServer 获取数据。
1.4.1.3、Master 是什么
可能都觉得 Master 是 Hbase 的领导,所有的数据、所有的操作都会进过它。错!其实在 HBase 中 Master 的角色不像是领导,更像是打杂的。客户端从 Zookeeper 获取了 RegionServer 的地址后,会直接从RegionServer 获取数据。其实不光是获取数据,包括插入、删除等所有的数据操作都是直接操作的 RegionServer,而不需要经过 Master。
Master 只负责各种协调工作(其实就是打杂),比如建表、删表、移动Region、合并等操作。他们的共性就是需要跨 RegionServer,这些操作由哪个 RegionServer 来执行都不合适,所有 HBase 就将这些操作放到了 Master 上了。
这种结构的好处是大大降低了集群对 Master 的依赖。而 Master 节点一般只有一个到两个,一旦宕机,如果集群对 Master 的依赖度很大,那么就会产生单点故障问题。在 HBase 中,即使 Master 宕机了,集群依然可以正常地运行,依然可以存储和删除数据。
1.4.2、存储架构
最基本的存储单位是列(column),一个列或者多个列形成一行(row)。传统数据库是严格的行列对齐。但是在 HBase 中,行跟行的列可以完全不一样,这个行的数据跟另外一个行的数据也可以存储在不同的机器上,甚至同一行内的列也可以存储在完全不同的机器上。
HBase 每个行(row)都拥有唯一的行键(row key)来标定这个行的唯一性。每个列都有多个版本,多个版本的值都存在单元格(cell)中。
若干个列又可以被归并为一个列族,存储结构如下:
1.4.2.1、行键(rowkey)是什么
rowkey 就是 row 存储的顺序的唯一凭证。而这个排序也很简单:根据字段排序。根据字典排序结果:
- row-1
- row-11
- row-2
rowkey 如果在插入 HBase 的时候,不小心用了之前已经存在的 rowkey 呢?
那就会把之前存在的那个 cell 更新掉。之前已经存在的值会被放到这个 cell 的历史记录里面,并不会丢掉,只是需要带上版本参数才可以找到这个值。
什么是单元格?
一个列上可以存储多个版本的单元格。单元格就是数据存储的最小单元。
1.4.2.2、列族是什么
在 HBase 中,若干列可以组成列族(column family)。同一个表里的不同列族可以有完全不同的属性配置,一个没有列族的表示没有意义的,因为列必须依赖列族而存在。
列族存在的意义是:HBase 会把相同列族的列尽量放在同一台机器上,所以说,如果想让某几个列被放在一起,你就给他们定义相同的列族。
一个表设置多少个列族比较合适?官方的建议是:越少越好,因为 HBase 并不希望大家指定太多的列族。为什么呢?因为没有必要,虽然HBae是分布式数据库,但是数据在同一台物理机上依然会加速数据的查询过程。所以请根据实际需要来指定列族,列族太多会极大程度降低数据库性能;而且根据目前的 HBase 实现,列族定义得太多,容易出BUG。
1.4.2.3、单元格(cell)
你以为 行键:列族:列 就能唯一确认一个值了吗?错!虽然列已经是 HBase 的最基本单位了,但是,一个列上可以存储多个版本的值,多个版本的值被存储在单元格里面,多个版本之间用版本(Version)来区分。所以,唯一确定一条结果的表达式应该是 行键:列族:列:版本号(rowkey:column family:column:version)
1.4.2.4、Region 跟行的关系
Region 和 行之前的关系是什么呢?
一个 Region 就是多个行的集合。在 Region 中行的排序按照行键(rowkey)字典排序。
1.4.3、与传统数据库的对比
对比维度 | HBase | RDBMS |
硬件架构 | 类似于 Hadoop 的分布式集群,硬件成本低廉 | 传统的多核系统,硬件成本昂贵 |
容错性 | 由软件架构实现,由于由多个节点组成,所以不担心一点或几点宕机 | 一般需要额外硬件设备实现 HA 机制 |
数据库大小 | PB | GB、TB |
数据排布方式 | 稀疏的、分布的多维的 Map | 以行和列组织 |
数据类型 | Bytes | 丰富的数据类型 |
事物支持 | ACID 只支持单个 Row 级别 | 全面的 ACID 支持,对 Row 和表 |
查询语言 | 只支持 Java API (除非与其他框架一起使用,如 Phoenix、Hive) | SQL |
索引 | 只支持 Row-key,除非与其他技术一起应用,如 Phoenix、Hive | 支持 |
吞吐量 | 百万查询/每秒 | 数千查询/每秒 |
RDBMS代表的是数据库管理系统,RDBMS是SQL的基础,诸如MS SQL Server,IBM DB2,Oracle,MySQL和Microsoft Access等一类的数据库的类型是RDBMS。现在的市面上有许多种 NoSQL 数据库,如 BerkeleyDB 是本地 NoSQL 数据库的例子, HBase 则为大型分布式 NoSql 数据库。
从技术上来说,Hbase 更像是"数据存储"而非"数据库"(HBase 和 HDFS 都属于大数据的存储层)。因此,HBase 缺少很多 RDBMS 特性,如列类型,二级索引,触发器和高级查询语言等。
2、HBase 是怎么存储数据的
2.1、HBase 宏观架构
HBase宏观架构图:
从这张图上可以看出一个 HBase 集群由一个 Master (也可以两个 Master 做成 HighAvailable)和多个 RegionServer 组成。右下角是其中一个 RegionSever 的内部构造图,一下是各个角色服务器的介绍。
- Master:负责启动的时候分配 Region 到具体的 RegionServer ,执行各种管理操作,比如 Region 的分割和合并。在 HBase 中的 Master 的角色功能比其他类型集群弱很多。其他的集群系统中的主要节点都是至关重要的,比如 Hadoop 的 namenode 就负责了管理所有 data 的映射,而 MongoDB 的 mongos 负责了路由,他们的主节点只要宕机了,整个集群就瘫痪了,但是 HBase 的 Master 很特别,因为数据的读取和写入都跟它没什么关系,它挂了业务系统照样运行。但是 Master 也不能宕机太久,有很多必要的操作,比如创建表、修改列族配置、以及更重要的分割和合并都需要它的操作。
- RegionServer:RegionServer 上有一个或者多个 Region 。我们读写的数据就存储在 Region 上。如果你的 HBase 是基于 HDFS 的,那么 Region 所有数据存取操作都是调用了 HDFS 的客户端接口来实现的。
- Region:标的一部分数据。HBase是一个会自动分片的数据库。一个 Region 就相当于关系型数据库中分区表的一个分区,或者 MongoDB 的一个分片。
- HDFS:Hadoop 的一部分。HBase 并不直接跟服务器的硬盘交互,而是跟 HDFS 交互,所以 HDFS 是真正承载数据的载体。
- ZooKeeper:ZooKeeper 虽然是自称一家的第三方组件,不属于 HBase 体系,但是 ZooKeeper 在 HBase 中的重要性甚至超过了 Master。为什么这么说呢?你可以做一个实验:把 Master 服务器关掉你的业务系统照样跑,能读能写;但是把 ZooKeeper 关掉,你就不能读取数据了,因为你读取数据所需要的元数据表 hbase:meata 的位置存储在 ZooKeeper 上。
2.2、RegionServer的内部架构
RegionServer 的内部架构图:
从图中我们可以看出一个 RegionServer 包含有:
- 一个 WAL :预写日志,WAL 是 Write-Ahead Log 的缩写。从名字就可以看出来他的用途,就是:预先写入。当操作到达 Region 的时候,HBase 先不管三七二十一把操作写到 WAL 里面去。HBase 会先把数据放到基于内存实现的 Memstore 里,等数据达到一定的数据量时才刷写(flush)到最终存储的 HFile 内。而如果在这个过程中服务器宕机或者断电了,那么数据就丢失了。WAL 是一个保险机制,数据在写到 Memstore 之前,先被写到 WAL 了,这样当故障恢复的时候可以从 WAL 中恢复数据。
- 多个 Region :Region 相当于一个数据分片。每一个 Region 都有起始 rowkey 和 结束 rowkey,代表了它所存储的 row 范围。
- 多个 Store :每一个 Region 内都包含有多个 Strore 实例。一个 Store 对应一个列族的数据,如果一个表有两个列族,那么在一个 Region 里面就有两个 Store。Store 内部有 MemStore 和 HFile 这两个组成部分。
2.3、预写日志
预写日志(Write-Ahead Log,WAL)就是设计来解决宕机之后的操作恢复问题的。数据到达 Region 的时候是先写入 WAL,然后再被加载到 MemStore 的。就算 Region 的机器宕机了,由于 WAL 的数据是存储在 HDFS 的,所以数据并不会丢失。
2.3.1、如何关闭/打开 WAL
WAL 是默认开启的。可以通过调用下面语句:
Mutation.setDurability(Durability.SKIP_WAL)
来关闭 WAL 特性。Put、Append、Increment、Delete 都是 Mutation 的子类,所以他们都有 setDurability 方法。这样可以让该数据操作快一点,但是最好不要这样做,因为当服务器宕机时,数据就会丢失。
2.3.2、延迟(异步)同步写入 WAL
如果实在想通过不惜通过关闭 WAL 来提高性能。其实还有折中选择,就是异步写入 WAL。此时改动会先放在内存中,这些改动立刻就会被写入 WAL。写入的方式是调用 HDFS 客户端来写入 HDFS,也就是说及时只有一个改动,也会调用 HDFS 接口来同步(sync)数据。如果不想关闭 WAL,又不想每次改动都写入 WAL,也可以采用异步的同步 WAL。设定方式是调用 setDurability() 方法:
Mutation.setDurability(Durability.ASYNC_WAL)
这样设定 Region 会等到条件满足的时候才会把操作写入 WAL。这里提到的条件主要指的是时间间隔 hbase.regionserver.optionallogflushinterval,这个时间间隔的意思是 HBase 间隔多久会把操作从内存写入 WAL,默认值是 1s。
如果异步同步的时候出错了怎么办?出错了是没有任何事务保证的,写入 WAL 的数据即使写入成功了,如果失败的话也会丢失。如果你的系统对性能要求极高、对数据一致性要求不高,并且系统的性能瓶颈出现在 WAL 上的时候,有你可以考虑使用异步写入 WAL。否则,使用默认的配置即可。
2.3.3、WAL 滚动
WAL 是一个环状的滚动日志结构,因为这种结构写入效果最高,而且可以保证空间不会持续变大。
WAL 的检查间隔由 hbase.regionserver.logroll.period 定义,默认值是1小时。检查的内容是把当前 WAL 中的操作跟实际持久化到 HDFS 上的操作比较,看哪些操作已经被持久化了,被持久化的操作就会被移动到 .oldlogs 文件夹内(这个文件夹也是在 HDFS 上的)。
一个 WAL 示例包含有多个 WAL 文件。WAL 文件的最大数量通过 hbase.regionserver.maxlogs(默认值是32)参数来定义的,这个值的大小定义设计性能调优,建议默认值就行。
其他的触发滚动的条件是:
- 当 WAL 文件所在块(Block)快要满了。
- 当 WAL 所占的空间大于或者等于某个阈值,该阈值的计算公式是:
hbase.regionserver.hlog.blocksize * hbase.regionserver.logroll.multiplier
hbase.regionserver.hlog.blocksize 是标定存储系统的块(Block)大小的,建议把他设置成HDFS块大小。
hbase.regionserver.logroll.multiplier 是一个百分比,默认设定成0.95,也就是95%,如果 WAL 文件占的空间大于或者等于95%的块大小,则这个 WAL 文件就会被归档到 .oldlogs 文件夹内。
2.3.4、WAL 文档归档之后去了哪
WAL 文件被创建出来后放在 /hbase/.log 下(这里说的路径都是基于 HDFS ),一旦文件被判定为要归档,则会被移动到 /hbase/.oldlogs 文件夹。
那什么时候日志会被从 .oldlogs 文件夹中彻底删除呢?
Master 会负责定期地去清理 .oldlogs 文件夹,条件是 “当这个 WAL 不需要作为用来恢复数据的备份” 的时候。判断的条件是 “没有任何引用指向这个 WAL 文件”。目前有两种服务可能会引用 WAL 文件:
- TTL 进程:该进程会保证 WAL 文件一直存活知道达到 hbase.master.logcleaner.ttl 定义的超时时间(默认10分钟)为止
- 备份(replication)机制:如果你开启了 HBase 的备份机制,那么 HBase 要保证 备份集群已经完全不需要这个 WAL 文件了,才会删除这个 WAL 文件。这里提到的 replication 不是备份数,这个新特性用于把一个集群的数据实时备份到另外一个集群。
只有当该 WAL 文件没有被以上两种情况引用时,才会被系统彻底地删除掉。
在Store中有两个重要的组成部分:
- MemStore:每个 Store 中有一个 MemStore 实例。数据写入 WAL 之后就会被放入 MemStore。MemStore 是内存的存储对象,只有当 MemStore 满了的时候才会将数据刷写(flush)到 HFile 中。
- HFile:在 Store 中有多个 HFile,当 MemStore 满了之后 HBase 就会在 HDFS 上生成一个新的 HFile,然后把 MemStore 中的内容写到这个 HFile 中。HFile 直接跟 HDFS 打交道,它是数据的存储实体。
至此,我们可能会有这样的疑问:
- WAL 是存储在 HDFS 上的, MemStore 是存储在内存中的,HFile 又是存储在 HDFS 上的。
- 数据是先写入 WAL,再被放入 MemStore,最后被持久化到 HFile 中。
数据在进入 HFile 之前已经被存储到 HDFS 一次了,为什么还要被放入 MemStore
这是因为 HDFS 上的文件只能创建、追加、删除,但是不能修改。对于一个数据库来说,按顺序存放数据是非常重要的,这是性能的保障,所以我们不能按照数据到来的顺序来写入硬盘。虽然很困难,但是办法还是有的。那就是使用内存先把数据整理成顺序存放,然后再一起写入硬盘。这就是 MemStore 存在的意思。虽然 MemStore 是存储在内存中的,HFile 和 WAL 是存储在 HDFS 上的。但由于数据在写入 MemStore 之前,要先被写入 WAL,所以增加 MemStroe 的大小并不能加速写入速度。MemStore 存在的意义是维持数据按照 rowkey 顺序排列,而不是做一个缓存。
2.3.5、MemStore
数据被写入 WAL 之后就会被加载到 MemStrore 中去。MemStore 的大小增加到超过一定阈值的时候就会被刷写到 HDFS 上,以 HFile 的形式被持久化起来。
设计 MemStore 的原因有以下几点。
- 由于 HDFS 上的文件不可修改,为了让数据顺序存储从而提高读取效率,HBase 使用了 LSM 树结构来存储数据。数据会先在 MemStore 中整理成 LSM 树,最后再刷写到 HFIle 上。不过不要想当然地认为读取也是先读取 MemStore 再读取磁盘!读取的时候有专门的缓存叫 BlockCache,这个 BlockCache 如果开启了,就是先读 BlockCache,读不到才是读 HFile+MemStore。
- 优化数据的存储。比如一个数据添加后就马上删除了,这样在刷写的时候就可以直接不把这个数据写到 HDFS 上。
早期 HDFS 上所有文件都是只能写入不能修改的。后来加入了追加(append)特性,实现了数据可以增加,但是必须是顺序增加,还是不能修改之前的数据。而 HBase 是一个随机读写的数据库。MemStrore 会在数据最终刷写到 HDFS 上之前对文件进行排序处理,这样随机写入的数据就变成了顺序存储的数据,可以提高读取效率。
所以 MemStore 是实现 LSM 树存储的必须设计组件。在 LSM 树的实现方式中,有一个毕竟的步骤,那就是在数据存储之前先对数据进行排序。而 LSM 数也是保证 HBase 能稳定地提高性能的读能力的基本算法。《LSM树(Log-Structured Merge-Tree)是什么?》
2.3.6、HFile
HFile 是数据存储的实际载体,我们创建的所有表、列等数据都存储在 HFile 里面。HFile 类似 Hadoop 的 TFile 类,它模仿了 BigTable 的 SSTable 格式。HFile 的组成部分如下:
我们可以看到 HFile 是由一个一个的块组成的。在 HBase 中一个块的大小默认为 64KB,由列族上的 BLOCKSIZE 属性定义。这些块区分了不同的角色:
- Data:数据块。每个 HFile 有多个 Data 块。我们存储正在 HBase 表中的数据就在这里。Data 块其实是可选的,但是几乎很难看到不包含 Data 块的 HFile。
- Meta:元数据块。Meta 块是可选的,Meta 块只有文件关闭的时候才会写入。Meta 块存储了该 HFile 文件的元数据信息,在 v2 之前布隆过滤器(Bloom Filter)的信息直接放在 Meta 里面存储,v2 之后分离出来单独存储。
- FileInfo:文件信息,其实也是一种数据存储块。FileInfo 是 HFile 的必要组成部分,是
必选的
。它只有在文件关闭的时候写入,存储的是这个文件的信息,比如最后一个 Key (Last Key),平均的 Key 长度(Avg Key Len)等。 - DataIndex:存储 Data 块索引信息的块文件。索引的信息其实也是 Data 块的偏移值(offset)。DataIndex 也是可选的。有 Data 块才有 DataIndex。
- MetaIndex:存储 Meta 块索引信息的块文件。MetaIndex 块也是可选的,有 Meta 块才有 MetaIndex。
- Trailer:
必选的
,它存储了 FileInfo、DataIndex、MetaIndex 块的偏移值。
2.4、增删改查的实现
HBase 是一个可以随机读写的数据库,而它所基于的持久化层 HDFS 却是要么新增,要么整个删除,不能修改的系统。那 HBase怎么实现我们的增删改查的?其实真实情况是:HBase 几乎总是再做新增操作。
- 当你新增一个单元格的时候,HBase 在 HDFS 上新增一条数据。
- 当你修改一个单元格的时候,HBase 在 HDFS 又新增一条数据,只是版本号比之前那个大(或者你自己定义)。
- 当你删除一个单元格的时候,HBase 还是新增一条数据!只是这条数据没有 value,类型为 DELETE,这条数据叫做墓碑标记(Tombstone)。
真正删除发生在什么时候?
由于数据在使用过程中积累了很多增删改查操作,数据的连续性和顺序性必然会被毁坏。为了提升性能,HBase 每间隔一段时间都会进行一次合并(Compaction),合并的对象为 HFile 文件。合并分为两种 minor compaction 和 major compaction。HBase在进行major compaction 的时候,它会把多个 HFile 合并成1个 HFile,在这个过程中,一旦检测到有被打上墓被标记的记录,在合并的过程中就忽略这条记录。这样在新产生的 HFile 中,就没有这条记录了,自然也就相当于被真正地删除了。
2.4、数据单元层次图
根据 HBase 存储架构,我们大致了解了 一个 RegionServer 里面有几个 Region,一个 Region 里面有几个 Store,都是怎么划分的。对应关系图如下:
归纳起来就是这样:
- 一个 RegionServer 包含多个 Region。划分规则是:一个表的一段键值在一个 RegionServer 上会产生一个 Region 。不过当你1行的数据量太大了(要非常大,否则默认都是不切分的),HBase 也会把你的这个 Region 根据列族切分到不同的机器上去。
- 一个 Region 包含多个 Store,划分规则是:一个列族分为一个 Store,如果一个表只有一个列族,那么这个表在这个机器上的每一个 Region 里面都只有一个 Store。
- 一个 Store 里面只有一个 MemStore。
- 一个 Store 里面有多个 HFile (StoreFile 是 HFile 的抽象对象,所以如果说 StoreFile 就等于 HFile)。每次 MemStore 的刷写(flush)就山城一个新的 HFile 出来。
3、HBase 读写数据
3.1、写数据流程
- 客户端首先从 zk 找到 meta 表的 region 位置,然后读取 meta 表中的数据,meta 表中存储了用户表的 region 信息
- 根据 namespace、表名和 rowkey 信息。找到数据对应的 Region 信息
- 找到这个 Region 对应的 ReginServer,然后发送请求
- 把数据分别写到 WAL (Write-Ahead Log)和 MemStore 各一份
- MemStore 达到阈值后把数据刷写到磁盘,生成 StoreFile 文件
注意:数据第一时间被写入 WAL,但是 WAL 只是一个暂存的日志,它是不区分 Store 的,这些数据是不能被直接读写和使用的。数据随后会立即被放入 MemStore 中进行整理。MemStore 会负责按照 LSM 数的结构来存放数据。这个过程就像我们打牌的时候,抓牌之后在手上对牌进行整理的过程。
3.2、读数据流程
特别说明:HBase 集群,只有一张 meta 表,此表只有一个 Region,该 Region 数据保存在一个 RegionServer 上。
- 客户端首先与 zk 进行连接;从 zk 找到 meta 表的 region 位置,即 meta 表的数据存储在某一个 RegionServer 上;客户端与此 RegionServer 建立连接,然后读取 meta 表的数据;meta 表中存储了所有用户表的 Region 信息,我们可以通过 scan ‘hbase:meta’ 来查看 meta 表信息
- 根据要查询的 namespace、表名和 rowkey 信息。找到写入数据对应的 Region 信息
- 找到这个 Region 对应的 ReginServer,然后发送请求
- 查找并定位到对应的 Region
- 先从 MemStore 查找数据,如果没有,再从 BlockCache 上读取,HBase 上 RegionServer 的内存分为两部分:一部分作为 MemStore ,主要用来写;另外一部分作为 BlockCache,主要用于读数据
- MemStore 达到阈值后把数据刷写到磁盘,生成 StoreFile 上进行读取,从 StoreFile 中读取到数据之后,不是直接把结果数据返回给客户端,而是把数据先写入到 BlockCache 中,目的是为了加快后续的查询;然后再返回给客户端。
4、Region 的定位
4.1、三层查询架构(0.96版本之前)
如上图:
第一层定位是Zookeeper中的节点数据,记录了-ROOT-表的位置信息;
第二层-ROOT-表记录了.META.region位置信息,-ROOT-表只有一个region,通过-ROOT-表可以访问.META.表中的数据
第三层.META.表,记录了用户数据表的region位置信息,.META.表可以有多个region。
整个查询步骤如下:
- 第一步:用户通过查找zk(ZooKeeper)的/hbase/root-regionserver节点来知道-ROOT-表的RegionServer位置。
- 第二步:访问-ROOT-表,查找所需要的数据表的元数据信息存在哪个.META.表上,这个.META.表在哪个RegionServer上。
- 第三步:访问.META.表来看你要查询的行键在什么Region范围里面。
- 第四步:连接具体的数据所在的RegionServer,这个一步才开始在很正的查询数据。
4.2、二层查询架构(0.96版本之后)
整个查询步骤如下:
- 第一步:客户端先通过ZooKeeper的/hbase/meta-region-server节点查询到哪台RegionServer上有hbase:meta表。
- 第二步:客户端连接含有hbase:meta表的RegionServer。hbase:meta表存储了所有Region的行键范围信息,通过这个表 就可以查询出你要存取的rowkey属于哪个Region的范围里面,以及这个Region又是属于哪个RegionServer。
- 第三步:获取这些信息后,客户端就可以直连其中一台拥有你要存取的rowkey的RegionServer,并直接对其操作。
- 第四步:客户端会把meta信息缓存起来,下次操作就不需要进行以上加载hbase:meta的步骤了