分布式存储—— HBase数据模型 详解

目录

[1.3 HBase数据模型](#1.3 HBase数据模型)

[1.3.1 两类数据模型](#1.3.1 两类数据模型)

[1.3.2 数据模型的重要概念](#1.3.2 数据模型的重要概念)

[1.3.3 数据模型的操作](#1.3.3 数据模型的操作)

[1.3.4 数据模型的特殊属性](#1.3.4 数据模型的特殊属性)

[1.3.5 CAP原理与最终一致性](#1.3.5 CAP原理与最终一致性)

[1.3.6 小结](#1.3.6 小结)


本文章参考、总结于学校教材课本《HBase开发与应用》

1.3 HBase数据模型

在开始学习HBase之前非常有必要先学习HBase的特性,因此本节将介绍HBase的逻辑模型、物理模型和访问HBase的方法等。和传统的关系型数据库类似,HBase以表(Table)的方式组织数据,应用程序将数据存入HBase的表中。HBase的表由行(row)和列(Column)共同构成,与关系型数据库不同的是HBase有一个列族(Column Family)的概念,它将一列或者多列组织在一起,HBase的列必须属于某一个列族。

行和列的交叉点称为单元格(Cell), 单元格是版本化的。单元格的内容也就是列的值是不可分割的字节数组,以二进制形式存储。HBase没有数据类型,任何列值都被转换成字节数组进行存储。HBase表中的行是通过行键(Rowkey)进行区分的,行键也是用来唯一确定一行的标识,不同的行键代表不同的行,行键也是一段字节数组,不论是字符串还是数字,最终都会被转换成字节数组进行存储。HBase表中的行是按Rowkey排序的,排序方式采用字典顺序,所有表中的行都必须要有Rowkey。同时HBase是一种面向列的分布式的数据库,其物理模型和逻辑模型与传统的关系型数据库有很大的不同。下面我们将详细讲述HBase数据模型中的一些重要概念。

1.3.1 两类数据模型

本节将从逻辑模型和物理模型两方面来了解HBase的数据模型,表是HBase表达数据的逻辑组织方式,而基于列的存储则是数据在底层的组织方式。本节将首先学习关于逻辑模型的一些重要概念及基本操作以及HBase实际存储数据的一些特点,为后面的学习打好基础。

1.3.1.1 逻辑模型

HBase是一个类似GoogleBigTable的开源分布式数据库,大部分特性和BigTable相同,可以理解为是一个稀疏的、长期存储的、多维度的和排序的映射表,表中的每一行可以有不同的列。与关系型数据库不同,关系型数据库要求表在被创建时明确定义列以及列的数据类型,而HBase的同一个表的记录可以有不一样的列。

HBase中最基本的一单位是列,一列或者多列构成了行,行有行键(Rowkey),每一行的行键都是唯一的,相同行键的插入操作被认为是对同一行的操作,也就是说如果做了两次写入操作,而行键是同一个,那么后面的操作可以认为是对该行的某些列的更新操作。

HBase中的一个表有若干行,每行有很多列,列中的值有多个版本,每个版本的值称为一个单元格,每个单元存储的是不同时刻该列的值。图 1.3.1是Google的Big Table论文中的webtable表的逻辑模型,由于HBase被认为是BigTable的开源实现,所以该图对HBase完全适用,表名为Webtable,包含两个列族:contents和anchor。在该实例中,列族anchor:有两个列(anchor:cssnsi.com和anchor:my.look.ca),列族contents仅有一个列contents:html。

其中,列名是由列族前缀和修饰符(Quallfier)连接而成,分隔符是英文冒号。例如,列anchor:my.look.ca是列族anchor前缀和修饰符my.look.ca组成。所以在提到HBase的列的时候应该用"列族前缀+修饰符"的方式才准确。

如图 1.3.1所示,在表Webtable的逻辑模型中,所有的列族和列都紧凑在一起,其中并没有附带物理存储方式的概念。该逻辑视图是为了使读者更好地、更直观地理解HBase的数据模型,并不代表实际的数据存储也是这种格式。

图 1.3.1 Webtable的逻辑模型

如果熟悉java语言里的Map数据结构,可以把HBase理解为这种结构的无限嵌套版本。

1.3.1.2 物理模型

虽然在逻辑模型中,表可以被看成一个稀疏的行的集合。但在物理上,表是按列分开存储的。HBase的列是按列族分组的,HFile是面向列的,存放行的不同列的物理文件,一个列族的数据存放在多个HFile中,最重要的是一个列族的数据会被同一个Region管理,物理上存放在一起。Region是管理HFile的一种机制,这个将会在后面讨论。这种物理上存储的不同可以从下面的物理视图中直观看出,如表 1.3.1和表 1.3.2所示。表 1.3.1中展示了列族anchor的集中存储,表 1.3.2中展示了列族contents的集中存储。

表 1.3.1 列族anchor存储模型

|-------------|-----|-----------------------------|
| 行键 | 时间戳 | 列族和单元格值 |
| com.cnn.www | t9 | anchor:cnnsi.com="CNN" |
| com.cnn.www | t8 | anchor:my.look.ca="CNN.com" |

表 1.3.2 列族contents存储模型

|-------------|-----|-----------------------------|
| 行键 | 时间戳 | 列族和单元格值 |
| com.cnn.www | t6 | contents:html="<html>..." |
| com.cnn.www | t5 | contente:html="<html>..." |
| com.cnn.www | t3 | contente:html="<html>..." |

HBase的表被设计成可以不禁用表而随时加入新的列,因此可以将新列直接加入一个列族而无须声明。

1.3.2 数据模型的重要概念

HBase是一种列式存储的分布式数据库,其核心概念是表(Table)。与传统的关系型数据库一样,表由行和列组成,但HBase同一列可以存储不同时刻的值,同时多个列可以组成一个列族(Column Family),这种组织形式是出于存取性能考虑的。理解HBase的这些概念是很重要的,因为数据模型设计的好坏将直接影响你的查询性能。本节我们将讨论最重要的概念。

1.3.2.1 表

在HBase中数据以表的形式存储。使用表的主要原因是把某些列组织起来一起访问,同一个表中的数据通常是相关的,通过列族进一步把一些列组织在一起进行访问。用户可以通过命令行或Java API来创建表。表名通常使用Java string类型或byte[](二进制数组)表示,表名作为HDFS存储路径的一部分来使用,因此必须要符合文件名规范,所以构成表名的字符是有限制的。可以直接查看底层存储系统,在HDFS中可以看到每个表的表名都作为独立的目录结构,在某些情况下,用户可能需要查看这部分信息。

HBase列式存储格式允许用户存储大量的信息到相同的表中,而在RDBMS模型中,大量信息则需要切分成多个表存储。通常的数据库规范规则不适用于HBase,因此HBase中表的数量相对较少。

虽然理论上HBase的表是由行和列组成的,但是从物理结构上看,表存储在不同的分区,即不同的Region。每个Region只在一个RegionServer中提供服务,而Region直接向客户端提供存储和读取服务。

与传统的关系型数据库一样,HBase提供了命令行创建表,创建表时只需要指定表名和至少一个列族。列族影响表的物理存储结构,创建表后列族还可以更改,但是比较麻烦。这就是HBase表中的全部,和传统关系型数据库不同,HBase的表没有列定义,没有类型,这就是HBase被称为无模式数据库的原因。

如何与HBase建立连接呢?可以使用Shell,或者通过Java API,与使用JDBC或ODBC访问关系型数据库不同,访问HBase不需要用户名和密码,没有Schema;将HBase-site.xml配置文件复制一份到自己的工程中,HBase API会读取配置文件完成对HBase的连接。创建连接是一项非常消耗资源的工作,HBase为我们提供了一个连接池,可以更好地管理资源重用。

1.3.2.2 行键

行键,即Rowkey,是HBase中最为重要的概念之一,在本书1.4.2节会详细讲解关于行键的设计原则。行键是不可分割的字节数组。行键是按字典排序由低到高存储在表中的,以一个空的数组来标识表空间的起始或者结尾。图 1.3.2展示了行键的排列规则。

图 1.3.2 行键按照字典进行排序

这可能和你预期的顺序不太一样,在字典序中,数据按照二进制字节从左至右逐一对比形成最终的次序。例如row-1小于row-2,无论后面如何,row-1都排在row-2之前。

在HBase中行键是唯一的索引,不过在新版本的HBase中考虑了对辅助索引的支持。

为了高效检索数据,应该仔细设计Rowkey以获得最高的查询性能:首先Rowkey被冗余存储,所以长度不宜过长,过长的Rowkey将会占用大量的空间同时会降低检索效率;其次Rowkey应该尽量分布均匀,这样不会产生热点现象;最后是Rowkey唯一原则,必须在设计上保证其唯一性。

在1.4.2节将学习Rowkey的设计,针对不同的场景Rowkey需要有不同的设计以提高检索的效率。

1.3.2.3 列族

HBase中的列族是一些列的集合。一个列族中所有列成员有着相同的前缀。比如,列courses:history和courses:math都是列族courses的成员。冒号(:)是列族的分隔符,用来区分列族前缀和列名。Column前缀必须是可打印的字符,剩下的部分(称为Column Qualifier),可以由任意字节数组组成。

在物理上,一个列族的成员在文件系统上都是存储在一起的。因为存储优化都是针对列族级别的,这就意味着,一个列族的所有成员是通过相同的方式访问的。

在创建表的时候至少要指定一个列族,新的列族可以随后按需、动态地加入,但是修改列族要先停用表。应该将经常一起查询的列放在一个列族中,合理划分列族将减少查询时加载到缓存的数据,提高查询的效率,但也不要有太多的列族,因为跨列族访问是非常低效的。

1.3.2.4 单元格

HBase中的一单元格由行键、列族、列、时间戳唯一确定。一单元格的内容是不可分割的字节数组。每个单元格都保存着同一份数据的多个版本,不同时间版本的数据按照时间顺序倒序排序,最新时间的数据排在最前面,时间戳是64位的整数,可以由客户端在写入据时赋值,也可以由RegionServer自动赋值。

1.3.3 数据模型的操作

HBase对数据模型的4个主要操作包括Get、Put、Scan和Delete。通过HTable实例进行操作,用户可以完成向HBase存储和检索数据,以及删除无效数据之类的操作。所有修改数据的操作都保证行级别的原子性,多个客户端或线程对同一行的读写操作都不会影响该行数据的原子性,要么读到最新的数据,要么等待系统允许写入行的修改。创建HTable实例是有代价的。每个实例都需要扫描HBase:meta表,以检查该表是否存在,是否可用。此外还有一些其他操作,这些检查和操作导致实例调用非常耗时。因此,推荐用户只创建一次HTable实例,而且是每个线程创建一个,如果用户需要使用多个HTable实例,应考虑使用HTablePool类,它可以复用多个HTable实例。

1.3.3.1 读Get

Get操作是指从客户端API中获取已存储数据的方法。HTable类中提供了get()方法,同时还有与之对应的Get类,Get操作返回一行或多行数据。Get()方法默认一次取回该行全部列的数据,我们可以限定只取回某个列族对应的列的数据,或者进一步限定只取回某些列的数据,之前也说过HBase的列的数据是多版本的,我们可以限定取回该列全部版本的数据或者限定只取回最新的一个或几个版本的数据。

当用户使用get()方法获取数据时,HBase返回的结果包含所有匹配的一单元格数据,这些数据将被封装在一个Result实例中返回给用户。用Result类提供的方法,可以从服务器端获取匹配指定行的特定返回值,这些值包括列族、列限定符和时间戳等。

1.3.3.2 写Put

Put操作要么向表增加新行(如果Key是新的)或更新行(如果Key已经存在)。可以一次向表中插人一行数据,也可以一次操作一个集合,同时向表中写入多行数据。如果要频繁修改某些行的数据,用户应该创建一个RowLock实例来防止其他用户对该行数据进行修改。

Put操作每次都会发起一次到服务器的RPC操作,如果有大量的数据要写入表中,就会有数千次RPC操作,这样效率很低。HBase客户端有一个缓冲区,负责将数据批量地仅通过一次RPC操作发送到服务端,这样可大大提高写入性能,默认客户端写缓冲区是关闭的,需要显式打开该选项。

当将一个Put集合提交到服务端的时候,可能会出现部分成功部分失败的情况,失败的数据会被保存到缓存区中进行重试。

HBase还提供了一个compare-and-set操作,这个操作先进行检查,条件满足后再执行,这个操作对于行是原子性的。

HBase没有Update操作,是通过Put操作完成对数据的修改的。

1.3.3.3 扫描Scan

Scan操作允许多行特定属性迭代,其使用与get()方法非常类似。工作方式类似于迭代器,可以指定startRow参数来定义扫描读取HBase表的起始行键,同时可选stopRow参数来限定读取到何处停止。

在创建Scan实例之后,用户可以增加更多的限定条件,没有参数将扫描整个表,包括所有列族以及所有列,可以用多种方法限定读取的数据。

扫描操作执行后将得到执行结果, 此结果被封装在一个ResultScanner实例中。下面是一个基于HTable表实例的示例:假设表有几个行键值为"row1"、"row2"、"row3",还有一些行键值为"abc1"、"abc2"和"abc3"。下面的代码展示了startRow和stopRow可以应用到一个Scan实例,以返回"row"开头的行。

Configution conf = HBaseConfiguration.create();

HTable htable = new HTable(conf, "table1"); //instantiate HTable

scan scan = new Scan();

scan.addColumn(Bytes.toBytes("cf"),Bytes.toBytes("attr"));

scan.setStartRow(Bytes.toBytes("row")); //start key is inclusive

scan.setStopRow(Bytes.toBytes("row" + (char )0)); //stop key is exclusive

ResultScanner rs = htable.getScanner(scan);

try {

for (Result r = rs.next(); r != null; r = rs.next()) {

// process result...

} finally {

rs.close(); //always close the ResultScanner!

}

1.3.3.4 删除Delete

Delete用于从表中删除数据。HTable除了提供删除方法delete()外,还有一个与之对应的类Delete,用户可以通过多种方法限定要删除的列。

与关系型数据库的Delete操作不同,HBase的Delete操作可以指定删除某个列族或者某个列,或者指定某个时间戳,删除比这个时间早的数据。

HBase的Delete操作并不是真正地从磁盘删除数据。而是通过创建墓碑(tombstones)标志进行处理。这些墓碑标记的值和小于该时间版本的单元格在大合并(major compact)时被清除。

1.3.4 数据模型的特殊属性

讨论完HBase的基本概念,我们来学习一下它特有的功能。这些常常使人感到困惑,对使用过关系型数据库的读者来说尤为如此。HBase的这些特性使得完成某些功能更加方便,同时它也存在一些有待完善的地方,这也正是HBase在大力改进的地方。

1.3.4.1 版本

Rowkey、column(列族和列)、version组合在一起称为HBase中的一个单元格。有可能会有很多单元的行和列是相同的,要区分不同的单元可以使用版本。Rowkey和Column的值是用字节数组表示的,Version则是用一个长整型表示的。这个长整型值是使用java.util.Date.getTime()或者System.currentTimeMillis()产生的,这就意味着它的含义是"当前时间和1970-01-01UTC的时间差,单位毫秒"。

在HBase中,版本是按倒序排列的,因此当读取这个文件的时候,最先找到的是最近的版本。关于版本的一些常见问题主要有如下两个:

  • Ø 如果有多个包含版本的写操作同时发起,HBase会保存全部还是会保持最新的一个?
  • Ø 可以发起包含版本的写操作,但是它们的版本顺序和操作顺序相反吗?

下面介绍一下在HBase中版本是如何工作的。

1. 含版本的操作

这里详细讲解如何在HBase的各个主要操作中使用版本属性。

(1) Get/Scan

Get是在Scan的基础上实现的。Get同样可以用Scan来描述。在默认情况下,如果没有指定版本,一旦使用Get操作,会返回最近版本的Cell(该Cell可能是最新写入,但不能保证一定是)。默认的操作可以这样修改:

如果想要返回两个以上的版本,可以使用Get类的setMax Versions(),如果想要返回的版本不只是最近的,可以使用Get类的setTimeRange()。

要想查询的最新版本小于或等于给定的这个值,这就意味着给定的"最近"的值可以是某一个时间点。可以使用0到想要的时间来设置,还要把MaxVersionS设置为1。

默认Get例子如下,其中的Get操作会只获得最新的一个版本。

Get get = new Get(Bytes.toBytes("row1));

Result r = htable.get(get);

byte [] b = r.getValue(Bytes.toBytes("cf"), Bytes.toBytes("attr"));

//returns current version of value

含有版本的Get例子如下,其中的Get操作会获得最近的3个版本。

Get get = new Get(Bytes.toBytes("row1"));

get.setMaxVersions(3);

//will return last 3 versions of row

Result r = htable.get(get);

Byte[] b = r.getVable(Bytes.toBytes("cf"), Bytes.toBytes("attr"));

//returns current version of value

List<KeyValue> kv = r.getColumn(Bytes.toBytes("cf"), Bytes.toBytes("attr"));

//return all versions of this column

(2) Put操作

一个Put操作会为一个Cell创建一个版本,默认使用当前时间戳,当然也可以自己设置时间戳,这就意味着可以把时间设置在过去或者未来,或者随意使用一个长整型值。覆盖一个现有的值,就意味着新写入Cell的Rowkey、ColumnFamily、columnQulifier和version必须和原来的完全相同。

下面是不指明版本的例子,其中的Put操作不指明版本,所以HBase会将当前时间作为版本。

Put put = newPut(Bytes.toBytes(row));

put.add(Bytes.toBytes("cf"),Bytes.toBytes("attr1"),Bytes.toBytes(data));

htable.put(put);

下面是指明版本的例子,其中的Put操作指明了版本。

Put put = newPut(Bytes.toBytes(row));

long explicitTimelnMs = 555; //just an example

put.add(Bytes.toBytes("cf'"), Bytes.toBytes("attr1"), explicitTimeInMs,Bytes.toBytes(data));

htable.put(put);

(3) Delete

内部删除标记有三种不同类型:

  • Delete:删除列的指定版本。
  • DeleteColumn:删除列的所有版本。
  • DeleteFamily:删除特定列族所有列。

当删除一行时,HBase将内部为每个列族创建墓碑(非每个单独列)标记。

删除操作的实现是创建一个墓碑标记。例如,要删除一个版本,HBase不会去改那些数据,数据不会立即从文件中删除。它使用删除标记来屏蔽掉这些值。如果被标记的是最新一个版本的数据,就意味着这一行中的所有数据都会被删除。

2. 现有的限制

关于版本还有一些未实现的功能,计划在以后的版本中实现。

(1) 删除标记后错误读取新数据

删除标记操作可能会标记其后Put的数据。注意,在写下一个墓碑标记后,只有下一个主合并(major ompact)操作发起之后,墓碑标记才会清除。假设删除所有小于等于时间T的数据,但之后又执行了一个Put操作,其时间戳小于等于T,那么就算这个Put发生在删除操作之后,该数据也会被打上墓碑标记。这个Put并不会失败,但你做Get操作时,则无法查询刚刚Put进去的数据。只有一个主合并(major compact)执行后,一切才会恢复正常。所以使用一系列时间戳一直增长的Put操作就不会发生该问题。

(2) 主合并改变查询的结果

一个Cell有三个版本数据t1、t2和t3,maxVersion设置为2,当请求获取全部版本的时候,只会返回两个:t2和t3。如果将t2和t3删除,就会返回t1。但是如果在删除之前,发生了major compaction操作,t1的值将会从磁盘上被彻底删除,结果是什么值都不会返回了。

1.3.4.2 排序

Get和Scan操作返回的是经过排序的数据。列在服务器端也是字典排序的,所以按照名称的字典序返回。总体来说,返回的数据首先按行字典序排序,其次是列族,然后是列修饰符(column qualifier),最后是时间戳反向排序,最新的在最前面。

1.3.4.3 列的元数据

对于HBase表中的列族,除了KeyValue实例以外,没有关于元数据的描述,KeyValue对象表示HBase的最小单位是Cell。HBase的表不仅在一行中支持很多列,而且支持行之间有不同的列,所以需要单独维护行和列之间的关系。获取列族的完整列名的唯一方法是处理所有行。

1.3.4.4 连接查询

HBase是否支持连接查询,即Join查询,是一个常见问题。简单来说是不支持,至少不像传统RDBMS那样支持。正如前面描述的,读数据模型只有Get和Scan两种操作。但这并不表示Join查询不能在应用程序中使用,只是必须用户自己实现。一般来讲,实现方法有两种:要么写入HBase的时候已经做好连接;要么查询并在应用层实现连接。哪个更好?依赖于准备做什么,所以没有一个准确的答案适合所有情况。

1.3.4.5 计数器

Increment Columnvalue(简称ICV)是HBase的级计数器,可以使用它完成一些诸如计算页面浏览量(PV)的操作。如果没有ICV,则需要首先读出HBase单元格中的值,然后加1再存入。ICV操作发生在RegionServer上,而不是在客户端,所以速度快,使用方式也没有那么烦琐。当其他客户端也在访问同一个单元格时,可以避免出现不一致的情况。可以把ICV等同于Java的AtomicLong.addAndGet()方法。

1.3.4.6 原子操作

类似Java的原子类,HTbleInterface接口也提供checkAndPut()和checkAndDelete()方法,它们可以在维持原子语义的同时提供更精细的控制。可以用checkAndPut()来实现上一节提到的incrementColumnValue()方法:

Configuration conf = HBaseConfiguration.create();

HTable htable = new HTable(conf,"table1"); //instantiate HTable

Get g = new Get(Bytes.toBytes(rowkey));

Resultr = htable.get(g);

long curVal = Bytes.tolong(r•getColumnLatest(Bytes.toBytes(family),

Bytes.toBytes(qualifier)).getValue());

long incVal = curVal+1;

Put P = new Put(Bytes.toBytes(rowkey));

P.add(Bytes.toBytes(family), Bytes.toBytes(qualifier), Bytes.toBytes(incVal));

htable.checkAndPut(Bytes.toBytes(rowkey),Bytes.toBytes(family),

Bytes.toBytes(qualifier), Bytes.toBytes(curVal),P);

上面的代码虽然有点长,但可以试试。使用checkAndDelete()的方式与此类似。

1.3.4.7 事务特性ACID

传统的SQL数据库的事务通常都是支持ACID的强事务机制。而HBase这种NoSQL数据库仅提供对行级别的原子性保证,也就是说同时对同一个Key下的数据进行的两个操作,在实际执行的时候是会串行的执行,保证了每一个KeyValue对不会被破坏。

之前版本的HBase提供行级的事务,不过每次事务只能执行一个写操作,假如连续地执行一系列Put、Delete操作,那么这些操作是单独一个个的事务,其整体并不是原子性执行的。而在0.94.*版本中,可以实现Put、Delete在同一个事务中一起原子性执行。

同一Region有多行原子性,因此对一个多Region表来说,还是无法保证每次修改都能封装为一个事务。HBase不是一个具备完整ACID特性的数据库,它只实现了某些属性。

HBase的ACID操作是复杂的,下面总结了ACID操作的一些主要原则,这些原则可以由点及面,逐步了解HBaseACID的特征。

HBase中考虑了事务(ACID)特性的数据操作包含以下这些:

  • Ø 获取数据的API: Get、Scan
  • Ø 修改数据的API: Put、Batch put、Delete
  • Ø 多项操作在一起的API: IncrementColumnValue、CheckAndPut

1) 关于HBaseACID设计原则如下:对于同一行所有列的修改是原子性的,对于该行的Put操作要么整体成功要么整体失败。

2) 一个返回"成功"标志的操作肯定是完全成功的。

3) 一个返回"失败"标志的操作肯定是完全失败的。

4) 超时的操作可能成功也可能失败。但也不会是部分成功或失败。

5) 对于同一行跨多个列族的操作也遵循上面的原则。

6) 多行操作不能保证原子性,例如:对a、b、c三行进行操作,一些可能有返回,一些可能没有,在这种情况下,API会返回一个对这三行操作的结果列表,包括成功、失败或者超时。

7) CheckAndPut()操作就像许多编程语言的CompareAndset()操作一样是原子性的。

8) 所有修改操作是保证顺序的,例如:如果一个写操作将数据修改成"a=1,b=1,c=1",另一个修改成,"a=2,b=2,c=2",那么行的状态肯定是"a=1,b=1,c=1"或者"a=2,b=2,c=2",不可能出现"a=1,b=2,c=1"这种状态。

9) 请注意批量修改操作不能跨越多行。

10) 一致性和隔离性。通过API得到的行的数据是一个完整的行,数据由表中某个时刻的数据构成。

11) 持久性。所有可以读取到的数据保证都是已经被持久化到磁盘上的。也就是说不会读到没有写到磁盘上的数据。所有返回成功的操作的数据都是处于持久化到磁盘上的。返回失败的都没有持久化。

关于HBase的ACID语义详情可以参见:http://HBase.apache.org/acid-semantics.html。

1.3.4.8 行锁

HBase API中put()、delete()、check AndPut()等修改操作是独立执行的,这意味着在一个串行方式的执行中,对于每一行必须保证行级别的操作是原子性的。RegionServer提供了一个行锁特性,这个特性保证了只有一个客户端能获取一行数据相应的锁,同时对该行进行修改。

1.3.4.9 自动分区

HBase中一个表的数据会被划分成很多的Region,Region可以动态扩展并且HBase保证Region的负载均衡。Region实际上是行键排序后的按规则分割的连续的存储空间。如果Region太大,会被动态拆分,相反,多个Region会合并成一个较大的Region,以减少HDFS上存储文件的数量。这两个过程就是HBase的split和compaction。

刚刚创建的表只有一个Region,随着数据的写入,达到Region上限配置值时,Region会按照中间键自动地拆分成两个大致相等的Region,每个Region由一个RegionServer管理,一个RegionServer处理器管理着许多的Region。图 1.3.3展示了多个Region是如何分布在不同RegionServer上的,注意每个Region包含起始Rowkey的记录,不包含结束Rowkey的记录。

图 1.3.3 Region物理分布

每个RegionServer管理多少个Region合适?每个Region大小是多少合适?按照现在主流硬件的配置,每个RegionServer可以管理大约100~1000个Region,每个Region的大小可以是1~20GB。

Region的拆分和转移是由HBase自动完成的,用户感知不到,当一个RegionServer服务器发生故障时,Region可以快速地被转移到其他服务器上,Region的拆分过程也是瞬间完成的。当Region进行拆分时,首先要将该Region下线(offline),拆分完成后新的Region再上线(online),下线的Region暂时不可用,不过由于速度极快,通常不会对数据的读写造成影响。

1.3.5 CAP原理与最终一致性

CAP原理是数据库软件的理论基础,它指出对于一个数据库系统来说,不可能同时满足以下三点:

  • Ø 一致性(Consistency):所有节点在同一时间具有相同的数据。
  • Ø 可用性(Availability):保证每个请求不管成功或者失败都有响应。
  • Ø 分区容忍性(Partition tolerance):系统中任意信息的丢失或失败不会影响系统的继续运作。

分布式数据库系统也只能满足三项中的两项。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的,因此只能在一致性和可用性之间进行权衡,大多数的分布式数据库系统选择了牺牲一致性提高可用性。图 1.3.3所示是不同数据库系统在这三方面的侧重点。

图 1.3.4 CAP模型及各种数据库分类

HBase的设计基于这样一些方面考虑,首先不要求严格的数据库事务,保证数据最终一致即可;其次数据库的写入可能在几秒之后读取出来用户也是能够忍受的,也就是说不能实时地读取刚刚写入的数据,另外就是复杂SQL的查询在产品设计阶段就避免了,更多的查询集中在针对主键的查询。

1.3.6 小结

HBase作为NoSQL数据库的一个代表和传统关系型数据库在概念上有相似之处,但也有非常大的差别,HBase更多的是为了扩展性和性能考虑,弱化了事务性,广大读者在学习时需要带着一个全新的思维方式来认识HBase,本节对HBase最重要的概念全部进行了概述,在后面的章节将分别针对各个方面进行详细描述。鼓励大家将HBase应用于实际的项目中,经过实践的积累会越发地感受到HBase在解决特定问题时所带来的优势。

相关推荐
waicsdn_haha21 分钟前
DBeaver 25.0 社区版安装与数据库连接配置指南(Windows平台)
数据库·人工智能·mysql·postgresql·sqlite·知识图谱·dbeaver
etcix1 小时前
android13为账号密码做文件存储功能
运维·服务器·数据库
编程修仙2 小时前
mysql视图
数据库·mysql
m0_748245923 小时前
Mysql COUNT() 函数详解
数据库·mysql
ashane13143 小时前
MariaDB Galera 原理及用例说明
数据库·mariadb
考虑考虑3 小时前
PostgreSQL中id自增长
数据库·后端·postgresql
PythonFun3 小时前
Python如何制作并查询sql数据库
数据库·python·sql
啥都想学的又啥都不会的研究生4 小时前
Redis设计与实现-数据结构
数据结构·数据库·redis·笔记·缓存·性能优化·skiplist
极限实验室4 小时前
INFINI Labs 产品更新 | Coco AI 开启智能知识管理新篇章
数据库