HBase 实战学习笔记

直接安装了一个单机版的 Hbase,先敲几个命令来熟悉一下 Hbase 的基本情况。

> hbase shell                // 进入交互界面

查看 hbase 下有多少张表

> list                       // 查看有哪些表

存储数据

// 创建一个表,mytable 是表名,cf 是列簇
// 每个表至少要有一个列簇
> create 'mytable', 'cf'

// 查看刚刚创建的表
> list


// 写入数据
> put 'mytable', 'first',  'cf:message', 'hello HBase'
> put 'mytable', 'second', 'cf:foo',     0x0
> put 'mytable', 'third',  'cf:bar',     3.14159

这句话要好好体会:

我们说,“在 'mytable' 表的 'first' 行中的 'cf:message' 列对应的数据单元中插入字节数组 'hello HBase' ”,好好体会这句话!!!

image

  • 列不用提前定义
  • 列的数据类型可以不同的

 

读数据

读数据的两种方式:get 和 scan。

现在玩一下 get。

// 读 mytable 这个表的, 主键为 'first‘ 的那行
> get 'mytable', 'first'

 

image

按列组织的结果,而且还有时间戳。

  • HBase 可以存储每个单元格的多个时间版本。
  • 存储的版本数量默认是 3 个,但是可以重新设置。
  • 读取的时候如果不特别指定,否则默认返回最新的时间版本。
  • 不过不希望存储多个时间版本,可以设置 HBase 只存储一个版本,但是绝不要禁用这个特性。

用 scan 可以扫描整个表。

// 扫描整个表
> scan 'mytable'

image

HBase 返回行的顺序是按行的名字排序的,行的名字也就是 行键(rowkey)

以上就是 HBase 的入门操作了。 

下一个主题

  • HBase 的逻辑数据模型
  • 访问 HBase 的方式和细节
  • HBase 模式设计

 

现在以一个小项目的形式来使用 HBase,可以看作是给 Twitter 来搭建 HBase。

基本的数据包括:

  • 用户(user)
  • 推贴(twit):用户公开发表的短文
  • 关系(relationship):用户之间的连接

 

创建表

首先创建 users 表开始:

// 创建 users 表
> create 'users', 'info'

'users' 是表名,HBase 中的列组成列族(column family,难怪简写是 cf)。info 就是 users 表的一个列族。

  • HBase 中的表至少要有一个列族。
  • 列族直接影响 HBase 数据存储的物理特性。
  • 创建表的时候必须指定一个列族。
  • 表创建完了之后列族还可以更改,但是这么做很麻烦。

 

目前 users 表很简单,就一个 info 列族。

// 查看当前创建的表
> list

// 查看表的更详细的信息
> describe 'users'

 

image

 

  • HBase 的唯一标识符,叫做 行键(rowkey),其他部分用来存储在 HBase 表里的数据。
  • 行键是第一重要的,可以类比关系型数据库中的主键。
  • HBase 表中每行的行键值都是不同的。
  • 每次访问表中的数据都是从行键开始。
  • 用户是唯一的,所以使用用户名作为 行键 很方便。

 

HBase API 相关的命令:

  • Get(读)
  • Put(写)
  • Delete(删除)
  • Scan(扫描)
  • Increment(递增)

 

存储数据

 

// 往表里存数据

Put p = new Put(Bytes.toBytes("Mark Twain"));

 

为什么不直接存储用户的名字?

答:HBase 中所有的数据都是作为原始数据(raw data)使用字节数组的形式存储的,行键 也是如此。

 

使用独一无二的用户名作为 行键,而不是用户的真实姓名,真实姓名不作为行键而是存储在一个列里面。

 

HBase 使用坐标来定位表中的数据。

  • 行键是第一个坐标
  • 列族是第二个,列族作为坐标表示一组列。
  • 下一个是 列限定符(column qualifier),简称 列(column)或者 标志(qual)。

 

 

这个例子中使用的 列限定符 是:name、email 和 password。

 

HBase 是无模式的,不需要事先定义 列限定符 或者设定数据类型。

只要在写入的时候给出列的名字。

 

修改数据

HBase 中修改数据和存储新数据一样:创建 Put 对象,在正确的坐标上给出数据,提交到表。

卧槽,这么简单的吗?

 

工作机制:HBase 写路径

写和修改数据的内部流程是一样的,HBase 接到命令之后存下变化的信息,或者写入失败抛出异常。

 

默认回写到两个地方:

  • 预写式日志(write-ahead log,也简称 HLog)
  • MemStore

HBase 默认把数据写到这两个地方,以保证数据的持久化。只有当这两个地方的变化信息都写入并确认之后,才会认为写入动作完成。

 

  • MemStore 是内存中的 写入缓冲区,HBase 中的数据在永久写入硬盘之前在这里累积。
  • MemStore 被填满后,其中的数据会刷写到硬盘,生成一个 HFile。
  • HFile 是 HBase 使用的底层存储格式。
  • HFile 对应于列族,一个列族可以有多个 HFile,但一个 HFile 不能存储多个列族的数据。
  • 在集群的每个节点上,每个列族有一个 MemStore。

 

如果 MemStore 还没刷写,服务器就崩溃了,内存中没有写入的数据就会丢失,怎么办?

HBase 集群会维护一个 WAL 来记录发生的变化。

WAL 是底层文件系统上的一个文件。

直到 WAL 新记录成功写入后,写动作才会被认为成功完成。

这可以保证 HBase 和 支撑它的文件系统满足持久性。

 

HBase 使用 Hadoop 分布式文件系统(HDFS)来作为底层文件系统。

 

如果 HBase 服务器宕机,没有从 MemStore 里刷写到 HFile 的数据可以通过回放 WAL 来恢复。

这个过程不需要手工执行,HBase 内部机制中有恢复流程来处理。

每台 HBase 服务器有一个 WAL,这台服务器上的所有表(和它们的列族)共享这个 WAL。

 

image

 

 

HBase 写路径。每次写入 HBase 需要来自 WAL 和 MemStore 的确认。

这两个确认确保每次写入 HBase 在尽可能快的同时保证持久性。

当 MemStore 写满时刷写到一个新的 HFile。

 

不建议禁用 WAL,除非你愿意在出问题时丢失数据。

读数据

// 读数据

Get g = new Get(Bytes.toBytes("TheRealMT"));
Result r = usersTable.get(g);

 

返回包含数据的 Result 实例。实例中包含行中的所有列族的所有列。

返回的数据可能大大超出你所需要的。

可以在 Get 实例中加入限制条件来减少返回的数据量。

 

 

工作机制:HBase 写路径

HBase 在读动作必须衔接持久化到硬盘的 HFile 和内存中的 MemStore 里的数据。

HBase 在操作上使用了 LRU 缓存技术,也叫 BlockCache,和 MemStore 在一个 JVM 堆里。

BlockCache 设计用来保存从 HFile 里读入内存频繁的数据,从而避免硬盘读。

每个列族都有自己的 BlockCache。

 

 

BlockCache 中的 Block 是 HBase 从硬盘完成一次读取的数据单位。

HFile 物理存放形式是一个 Block 的序列外加这些 Block 的索引。

 

HBase 里读取一个 Block 需要现在索引上查找一次该 Block 然后从硬盘读出。

Block 是建立索引的最小数据单位,也是从硬盘读取的最小数据单位。

Block 大小按照列族设定,默认值是 64 KB。

可以根据使用场景来调大或者调小该值。

 

如果主要用于随机查询,你可能要细粒度的 Block 索引,小一点的 Block 更好一些。Block 变小会导致索引变大,进而消耗更多的内存。

如果经常顺序扫描,一次读取多个 Block,大一点儿的 Block 更好一些。Block 变大意味着索引项变少,索引变小,因此节省内存。

 

从 HBase 中读出一行,首先检查 MemStore 等待修改的队列,然后检查 BlockCache 看包含改行的 Block 是否被访问过,最后访问硬盘上对应的 HFile。

 

image

删除数据

 

HBase 删除数据和存储数据的方式类似。

 

合并:HBase 的后台工作

Delete 命令并不立即删除数据,而是给记录打上删除的标记。

针对删除的内容,会写入一条新 “墓碑” 记录,作为删除的标记。

墓碑记录用来标志删除的内容不能在 Get 和 Scan 命令中返回结果。

因为 HFile 文件是不能改变的,直到执行一次大合并(major compaction),这些墓碑记录才会被处理,被删除记录的空间才会释放。

 

合并分为两种:

  • 大合并(major compaction)
  • 小合并(minor compaction)

 

两者将会重整存储在 HFile 里的数据。

小合并 把多个小 HFile 合并生成一个大的 HFile。

因为读出一条完整的行可能引用很多文件,限制 HFile 的数量对于读性能很重要。

执行合并时,HBase 读出已有的多个 HFile 的内容,把记录写入一个新文件。然后把新文件设置为激活状态,删除构成这个新文件的所有老文件。

HBase 根据文件的号码和大小决定合并哪些文件。

小合并设计的出发点时轻微影响 HBase 的性能,所以涉及的 HFile 的数量有上限。这些都可以设置。

 

image

 

 

大合并将处理给定 region 的一个列族的所有 HFile。

大合并完成后,这个列族的所有 HFile 合并成一个文件。可以从 Shell 中手工触发整个表(或者特定 region)的大合并。

这个动作是相当耗资源的,不要经常使用。

另外,小合并是轻量级的,可以频繁发生。

大合并是 HBase 清理被删除记录的唯一机会。

因为我们不能保证被删除的记录和墓碑标记记录在一个 HFile 里面。大合并是唯一的机会,HBase 可以确保同时访问到两种记录。

 

有时间版本的数据

HBase 除了是无模式的数据库外,还有 时间版本概念(versioned)的数据库。

 

单元的新建、修改和删除都会同样的处理,都会留下新时间版本。

Get请求根据提供的参数调出相应的版本。

时间版本是访问特定单元时的最后一个坐标。

当没有设定时间版本时,HBase 以毫秒为单位使用当前时间,所以版本数字用长整型 long 表示。

HBase 默认只存储3个版本,这个可以基于列族来设置。

如果一个单元的版本超过了最大数量,多出的记录在下一次大合并时会扔掉。

除了删除整个单元,你也可以删除一个或几个特定的版本。

 

数据模型概括

HBase 里面的逻辑实体如下:

  • 表(table):HBase 用表来组织数据。表名时字符串(String),又可以在文件系统路径是用的字符组成。
  • 行(row):在表里,数据按行存储。行由行键(rowkey)唯一标识,行键没有数据类型,总是视为字节数组 byte[]。
  • 列族(column family):行里的数据按照列族分组,列族也影响到 HBase 数据的物理存放,因此,它们必须事前定义并且不轻易修改。表中每行拥有相同列族,尽管行不需要在每个列族里存储数据。列族名字是字符串(String),由可以在文件系统路径里使用的字符组成。
  • 列限定符(column qualifier):列族里的数据通过列限定符或列来定位。列限定符不必事先定义。列限定符不必在不同行之间保持一致。就像行键一样,列限定符没有数据类型,总是视为字节数组 byte[]。
  • 单元(cell):行键、列族和列限定符一起确定了一个单元。存储在单元里的数据称为单元值(value)。值也没有数据类型,总是视为字节数组 byte[]。
  • 时间版本(version):单元值有时间版本。时间版本用时间戳标识,是一个 long。没有指定时间版本时,当前时间戳作为操作的基础。HBase 保留单元值时间版本的数量基于列族进行配置。默认数量是 3 个。

 

上述的 6 个概念构成了 HBase 的基础。用户最终看到的是通过 API 展现的 6 个基本概念的逻辑视图,它们是对 HBase 物理存在硬盘上数据进行管理的基石,要牢牢记住这 6 个概念。

数据坐标

HBase 使用的坐标依次是:行键、列族、列限定符和时间版本。

users 表的坐标如下:

 

image

 

把所有坐标视为一个整体,HBase 可以看作是一个 键值(key-value)数据库。

image

 

HBase 可以认为是一种键值存储,定位一个单元的 4 个坐标可以视为 键。

 

在使用 HBase 检索数据时,不需要提供全部坐标。

如果你在 Get 命令中省略了时间版本,HBase 返回数据值多个时间版本的映射集合。

HBase 允许你在一次操作中得到多个数据,它们按照坐标的降序排列。

 

image

 

HBase 可以视为键值数据存储的另一种视角。单元坐标的维度越少,对应值得集合范围越广。

数据模型

HBase:半结构化数据。

逻辑模型里针对结构化或半结构化数据的导向影响了数据系统物理模型的设计,HBase 物理模型也会考虑到半结构化数据的特点。

因为这种双向的紧密联系,优化数据系统必须深入理解逻辑模型和物理模型。

 

除了半结构化, HBase 的另一个重要因素是:可扩展性。

在半结构化逻辑模型里数据构成是松耦合的,有利于物理分散存放。

但是这种分散存放也影响了逻辑模型。

这种物理模型设计迫使 HBase 放弃了一些关系型数据库的特性:

  • 约束
  • 事务

 

逻辑模型:有序映射的映射集合

image

 

有序映射的映射。

HBase 逻辑上把数据组织成嵌套的映射的映射。

每层映射集合里,数据按照映射集合的键字典序排序。

在上图中,"email" 排在 "name" 前面,最新时间版本排在稍晚时间版本前面。

 

用 Java 来理解,就是:

// 可以这么理解

Map<RowKey, Map<ColumnFamily, Map<ColumnQualifier, Map<Version, Data>>>>

 

实践中,设计 HBase 表模式时这种排序设计是一个关键考虑因素。 

物理模型:面向列族

 

HBase 中的列按照列分组。

列族表现在物理模型中。

每个列族在硬盘上有自己的 HFile 集合。

这种物理上的隔离允许在列族底层的 HFile 层面上分别进行管理。

进一步考虑到合并,每个列族的 HFile 都是独立管理的。

 

HBase 的记录按照键值对存放在 HFile 里。 HFile 自身是二进制文件,不可以直接读的。

 

image

 

对应 users 表 info 列族的 HFile 数据。每条记录在 HFile 里是完整的。

 

文件里没有空记录(null)。

如果没有数据,HBase 是不会存储任何东西。

因此列族的存储是面向列的。

一行中的一个列族的数据不一定放在同一个 HFile 里。

唯一的要求是,一行中列族的需要物理存放在一起。

 

每个列族都使用自己的 HFile,这意味着,当执行读操作时 HBase 不需要读出一行中所有的数据,只需要读取用到列族的数据。

 

这意味着当检索指定单元时,HBase 不需要读占位符(placeholder)记录。

 

这两个物理细节有利于稀疏数据集合的高效存储可快速读取。

 

image

 

从图中可以看到,访问不同列族的数据涉及完全不同的 MemStore 和 HFile。

列族 activity 数据的增长并不影响列族 info 的性能。

表扫描

HBase 没有查询语句。

 

查询包含某个特定值的记录的唯一办法是,使用扫描(Scan)命令读出表的某些部分,然后再使用过滤器(filter)来得到有关的记录。

扫描返回的记录是排好序的,HBase 设计上支持这种方式,因此速度很快。

设计用于扫描的表

为 HBase 表设计模式(Schema)也需要考虑数据形态和访问模式。

推贴采用:用户名+时间戳 作为 rowkey。

变长的采用 MD5 进行分散处理,生成固定长度的值。

然后再将未经处理的 用户 ID 另外再存放在一个列中,以防后面用到。

执行扫描

 

 

 

扫描缓存器

 

使用过滤器

 

行键很难完美匹配你的访问模式。

有时你的使用场景需要扫描 HBase 的一组数据但是只返回它的子集给客户端。

这时需要使用 过滤器(filter)。

 

过滤器是在 HBase 服务器端上而不是在客户端执行判断动作。

当你在 Scan 里设定 Filter 时,HBase 使用它来决定一个记录是否返回。这样避免了许多不必要数据的传输。

 

数据在到达客户端之前在 region 中编译和使用了正则表达式。

HBase 中过滤器可以应用到行键、列限定符或者数据值。

 

原子操作

 

ACID语义

 

 

执行扫描、扫描缓存器、使用过滤器、原子操作 和 ACID 语义先过掉。

 

 

总之,对 HBase 有一个大概的理解了。

转载于:https://www.cnblogs.com/tuhooo/p/11126090.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值