HBase Overview

  • Use HBase when u need random, realtime read/write access to ur Big Data.
  • HBase is an open-source, distributed, versioned, non-relational database modeled after Google's Bigtable: A Distributed Storage System for Structured Data
  • Bigtable是一个疏松的、分布式的、持久的多维排序的map。这个map被行键,列键,和时间戳索引.每一个值都是非解释的byte数组.【引自BigTable论文:A Bigtable is a sparse, distributed, persistent multidimensional sorted map. The map is indexed by a row key, column key, and a timestamp; each value in the map is an uninterpreted array of bytes.】
  • HBase是一个构建在HDFS上的分布式列存储系统
  • Hbase是基于Google BigTable模型开发的,典型的key/value系统
  • 面向列族的存储和控制权限,列族独立检索
  • 空列并不占用存储空间,表可以设计得非常稀疏 --> 因此数据可以很稀疏 (schema with store)
  • 数据类型单一:Hbase中数据都是字符串,无类型。

Features

  • Linear and modular scalability; 可扩展性
  • Strictly consistent reads and writes; 强读写一致性
  • Automatic and configurable sharding of tables; 自动、可配置的表分区
  • Automatic failover support between RegionServers; RegionServers间自动的失效备援
  • Block cache and Bloom filters for real-time queries; 块缓存 & bloom filter for实时查询 [for high volume query optimization]

Data Model

Conceptual View

  • Row: Hbase中的一行由 a row key + one or more columns with values associated with them组成。 
  • 行是按row key字母顺序存储的。因此,row key的设计是十分重要的。 [rowKey是主键]
  • 列由列族 + 列修饰符组成。
  • column family: 列族一般是处于性能考虑,每个列族都有一系列的存储特性:比如值是否cache到内存,数据如何压缩,row key如何编码等等。一个表中的每一行都有相同的列族,尽管一个给定的行在给定列族上不存储任何东西。
  • timestamp: 时间戳是与每个值一起写入的,用做值的版本修饰符。缺省情况下,timestamp表示数据写入regionServer时的时间,你也可以指定。

Physical View

  • 尽管在概念层面上,表可以被看成行的稀疏集合。但在物理上它们是通过列族存储的。一个新的列修饰符(column_family:column_qualifier)可以随时被添加到已有的列族中。
Table 5. ColumnFamily anchor
Row KeyTime StampColumn Family anchor

"com.cnn.www"

t9

anchor:cnnsi.com = "CNN"

"com.cnn.www"

t8

anchor:my.look.ca = "CNN.com"

  • 每个列族存储在HDFS上的一个单独文件中,空值不会被保存;

HBase架构

  • 经典的Master/Slave架构,节点包括:HMaster, HRegionServer, Zookeeper集群,底层HDFS集群。

HMaster

  • 功能:
    • 管理HRegionServer,实现其负载均衡
    • 管理和分配HRegion,比如在HRegion split时分配新的HRegion;在HRegionServer退出时迁移其内的HRegion到其他的HRegionServer上。
    • 实现DDL操作(Data Definition Language,namespace和table的增删改,column family的增删改等)。
    • 管理namespace和table的元数据(实际存储在HDFS上)。
    • 权限控制ACL

动态负载均衡

  • 背景:如果一个RegionServer上的Region过多,那么该RS会承担过多的读写服务请求,在高并发情况下,性能会下降。
    --> 解决热点问题
  • 目标:实现RegionServer之间region的动态负载均衡
  • 实现:
    1. HMaster在初始化时会创建一个用来触发HRegion执行负载迁移的工作线程。(这里的负载迁移不仅仅是A-Sever到B-Server,也包括了两个Server的HRegion的交换操作)
    2. 默认情况下,该线程每隔5min执行一次balance方法,判断是否需要balance操作。
    3. 如果需要,首先计算当前cluster的cost花销,默认有几个维度:region的数量,region move的花销,数据的本地性,表的负载。

 HMaster Failover

  • 通过Zookeeper的Master Election机制重新选择一个新的Master。
  • 无master过程中,数据读取仍照常进行,但region切分、负载均衡等无法进行(master的职责)。 [这是不是说明table的schema是存在zk中的呢]
  • 一般情况下会启动两个HMaster,非Active的HMaster会定期的和Active HMaster通信以获取其最新状态(standby去pull),从而保证它是实时更新的,因而如果启动了多个HMaster反而增加了Active HMaster的负担。

 

HRegionServer

  • 功能
    • 存储和管理本地HRegion
    • 读写HDFS,管理Table中的数据
    • Client直接通过HRegionServer读写数据(从HMaster中获取元数据,找到RowKey所在的HRegion/HRegionServer后)
  • 底层Table存储于HDFS中,而HRegion所处理的数据尽量和数据所在的DataNode在一起,实现数据的本地化。数据的本地化并不是总能实现,比如在HRegion移动时,需要等下一次Compact才能继续回到本地化。

数据本地化

 

RegionServer Failover

  • 首先,每个RegionServer会在zk的rs目录下注册临时节点(具体可以参考下面zk信息存储的部分)
  • 当RS崩溃后,该RS与zk之间的socket连接会断开,但是二者之间的Session由于有超时时间的存在,需要等到Session超时之后临时节点才会被删除。
  • 由于HMaster在rs下注册了watcher监听,就可以感知到。
  • 失效服务器上“预写”日志(WAL)由主服务器进行分割并派送给新的RegionServer

 

ZooKeeper

  • 功能:
    • 存放整个HBase集群的元数据以及集群的状态信息
    • 实现HMaster主从节点的failover(HA)。

Zookeeper信息存储

  • 集群相关信息存储在/hbase目录下(可配置)
  • master目录存储HMaster的位置等相关信息
    • HMaster启动时,会在zk集群中创建自己的znode临时节点并获得该节点的独占锁,该节点位于/hbase/master目录下。 [临时节点:通过socket连接发起session会话,节点状态由zk集群根据session的状态来维护。]
    • 同时会在所有的其他目录下创建监听,这样HMaster就可以立即感知其他节点的状态变化并做出相应的处理。
  • rs目录存储所有的RegionServer的位置等相关信息
    • RegionServer启动时,会在/hbase/rs目录下创建znode临时节点并获得独占锁
    • RegionServer作为客户端,向zk集群的server端发起session会话请求。session建立后,会以唯一的SessionID作为标示。client会定期向Server端发送Ping消息来表达该session的存活状态,而server端收到Ping消息后会更新当前Session的超时时间。

 

 

 HBase Client

  • client通过RPC与HMaster,HRegionServer通信

读数据

  1.  如何根据rowKey定位到相应的RegionServer

 

写数据

 

存储细节

HRegion

  • HBase首先使用RowKey将表水平切割成多个HRegion。从HMaster角度,每个HRegion都记录了它的StartKey和EndKey。
  • 由于RowKey是sorted的,因而Client可以通过HMaster快速地定位每个RowKey在哪个HRegion中。
  • HRegion由HMaster分配到相应的HRegionServer中。 进而由HRegionServer负责HRegion的启动和管理,和Client的通信,负责数据的读写(based on HDFS)。 
  • region是HBase中分布式存储和负载均衡的最小单元
  • 但region不是HBase存储的最小单元。Region由一个或多个store组成,每个store保存一个列族每个store又由一个memStore和多个storeFile组成。memStore存储在内存中,storeFile存储在HDFS上。

 

数据恢复

  • HBase采用类LSM的架构体系(关于LSM见后文),数据写入不是直接到磁盘,而是会先写入缓存memStore,之后才会flush到磁盘(异步)。
  • 为了防止数据写入缓存之后不会因为RegionServer挂掉而导致数据丢失,在写入缓存之前会先将数据顺序写入HLog中(WAL)。如果发生异常,可以从HLog进行日志回放来补救,从而保证数据不丢失。

HLog

  • HLog组成:可以看到,一个HLog由RegionServer上所有Region的日志数据组成。
    • 日志数据的最小单位为 <HLogKey, WALEdit>,其中HLogKey由sequenceid,writetime,clusterid,regionname以及tablename组成。其中sequenceid是一个自增数字。
  • HLog滚动:HBase后台线程启动了一个线程会每隔一段时间进行日志滚动。
    • 可见,HLog不是一个大文件,而是一个个小文件
    •  --> 一个个小文件更便于整体删除。
  • HLog失效:HLog失效之后就会被删除,并且删除是以文件为单位的。那么怎么判断一个日志文件失效了呢?
    • --> 原理上讲,一旦数据从memStore中落盘,对应的日志就可以被删除了。因此,只要看日志文件中最大的sequenceid是否落盘即可。
  • HLog删除

恢复过程

  • RegionServer宕机之后,HMaster会马上检测到(通过zk实现的)。
  • 将RegionServer上的所有Region重新分配到集群中其他正常的RegionServer上去。
  • 再根据HLog进行丢失数据恢复。[恢复之前要切分HLog,是因为上面提到的一个HLog存储了该RegionServer上所有HRegion的WAL。]

Compaction

  • HFile 过多会影响读性能
  • 但很明显compaction过程会引入很高的磁盘IO,因为要不停地重写数据。本质上来说,compaction其实就是用当前更高的磁盘IO来换取将来更低的磁盘寻道时间。
  • -->  那么,什么时候执行compaction,也需要一定的决策。 
  • compaction的触发条件:
    • memstore刷写磁盘:每次flush的时候判断文件数是否超标
    • 用户执行compact,major_compact
    • HBase后台线程周期性触发检查
  • HBase 的compaction分为 minor 和 major 两种。
    • minor compaction负责将几个小文件合并成一个较大的文件
    • major compaction是将一个HStore中所有文件合并成一个大文件。

Major Compaction

  • major compaction是将一个HStore中所有文件合并成一个大文件。
  • 更重要的是major compaction会删除那些被标记为删除的数据,超过TTL的数据,以及超过版本数量限制的数据,将HStore中的所有HFile重写成一个HFile。 
  • major compaction可能导致某台server短时间内无法响应client的请求

Minor Compaction 

  • minor compaction负责将几个小文件合并成一个较大的文件
  • minor compaction只能做部分文件的合并操作,不做任何删除数据、多版本数据清理工作。(这里是不做还是不能?)
  • minor Compaction的关键是如何挑选被合并的小文件
    • 优先选小文件:
      • 所有大小超过 hbase.hstore.compaction.max.size 的文件被排除在外
      • 尽可能包括大小低于 min.size 的文件
    • 优先选老的文件

 

存储引擎

哈希

  • 哈希存储引擎是哈希表的持久化实现,支持增删改以及随机读写,但不支持顺序扫描
  • 对应的存储系统为key-value存储系统。
  • O(1) 时间的插入和查询。
  • 如果不需要有序的遍历数据,哈希表就是很好的选择

动态查找树

  • 动态查找树主要有:BST,Balanced BST, Red-Black Tree, B Tree, B+ Tree, B* Tree
  • 前三者是典型的二叉查找树结构,时间复杂度是 O(log2N)
  • 考虑一个实际问题:在大规模数据存储中,实现索引查询。既然查询效率与树的深度有关,那么自然会想到通过多叉树结构来降低树的深度啦。
    • 也就是说,由于磁盘的操作费时费资源,如果过于频繁的多次查找势必效率低下。那么核心思想就变成了如何避免磁盘的多次查找
    • 依此提出的新结构就是多路查找树啦。

B Tree

  • B树是平衡多路查找树结构,B即balanced。
  • 实际上,B树是为了磁盘或其它存储设备而设计的一种多叉平衡查找树。与红黑树相似,但在降低磁盘I/O操作方面要更好一些。
  • [模拟查找文件29]整个查找过程如下:
    • 根据根节点指针找到文件目录的根磁盘块1,将其中信息导入内存。[1st 磁盘IO]
    • 在内存中对有序数组进行二分查找定位到指针p2
    • 根据p2定位到磁盘块3,并将其中信息导入内存。[2nd 磁盘IO]
    • ...

B+ Tree

  • B+树常用于数据库和操作系统的文件系统中。
  • B+树是应文件系统所需而产生的一种B树的变形树。
  • 与B树的区别:
    • 所有的叶子节点包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子节点本身依关键字的大小自小而大顺序链接。
    • 所有非终端节点可以看成是索引部分,节点中仅含有其子树根节点中最大(or最小)关键字。(而B树的非终端节点也包含需要查找的有效信息)
  • 为什么说B+树比B树更适合实际应用中os的文件索引和数据库索引呢?
    • B+树的磁盘读写代价更低:B+树内部节点并没有指向关键字具体信息的指针,因此其内部结构相对B树更小。那么盘块能容纳的关键字数量更多,相对来说IO次数也就降低了。
    • B+树的查询效率更稳定:由于非终端节点并不是最终指向文件内容的节点,而只是叶子节点中关键字的索引。所以任何关键字查找都必须从root到叶子。所有查询的路径长度相同。
    • 同时,也有说法是说重点在于,B树在提高磁盘IO性能的同时并没有解决元素遍历的效率低下的问题。B+树只要遍历叶子节点就可以实现整棵树的遍历。而且在数据库中,基于范围的查询是非常频繁的,而B树不支持这样的操作(或者说效率太低)。 [通过叶子节点之间的指针可以支持顺序扫描。]
    • 重点应该还是在于,B+树提供了兄弟指针,方便扫库,B+Tree直接从叶子挨个扫一遍即可,而B-Tree必须用中序遍历按序扫库。 B+ Tree支持range-query,这是数据库选用B+ Tree最主要的原因。

特点

  • 能够保持数据稳定有序,其插入和修改拥有较稳定的log时间复杂度
  • 具体来说,B+树的高度低,而磁盘本身是一个顺序读写快,随机读写慢的系统(因为磁盘寻道时间一般很长)。

问题

  • B+树最大的性能问题是会产生大量的随机IO,随着新数据的插入,叶子节点会慢慢分裂,逻辑上连续的叶子节点在物理上往往不连续,甚至分离的很远。
  • --> 这就导致了在做范围查询时,会产生大量的随机读IO。对于大量的随机写更是如此。

LSM

  • LSM全称 Log Structured-Merge Tree
  • LSM被设计来提供比传统的B+树或者ISAM更好的写操作吞吐量,提供消除随机的本地更新操作来达到这个目标。

设计

  • 本质上还是磁盘随机读写慢,顺序读写快的老问题。
  • 所以,如果对写操作敏感,最好怎么做?
    •  一个经典的解法是简单的将数据添加到文件。这种策略常用于日志or堆文件中,因为它们完全是顺序的,所以可以提供很好的写操作性能,大约等于磁盘的理论速度,约200~300MB/s
    • 因为简单而高效,基于日志的策略在大数据直接越发流行。但其缺点也很明显:
      • 读数据耗时,只能倒序扫描,直接找到所需的内容。
      • 因此,只适用一些简单的场景:
        • 数据是被整体访问的,像大部分数据库的WAL
        • 知道明确的offset,比如在kafka中。
  • 一般来说,我们优化读操作有以下几种方式:
    • 二分查找:将文件有序保存
    • 哈希:用哈希将数据分桶
    • B+树:使用B+树 or ISAM等方法,可以减少外部文件的读取
    • 外部文件:将数据保存为日志,并创建一个hash or 查找树映射相应的文件。

   很多树结构可以不用update-in-place,最流行的就是 append-only Btree,也称为 Copy-On-Write-Tree。(也存在一些缺点,略)

  • LSM的设计目标:保持日志文件写性能,以及微小的读操作性能损失。本质上还是让所有操作顺序化。

Algorithm

  • 基本思想:将之前使用一个大的查找结构,变换为将写操作顺序地保存到一些相似的有序文件。文件是不可修改的,它们永远不会被更新,新的更新操作只会写到新的文件中。
  • 更新操作:
    1. 首先被写入内存(即memtable)中,memtable使用树结构来保持key的有序。在大部分实现中,memtable会通过WAL方式备份到磁盘,用来恢复数据,防止数据丢失。
    2. memtable到达一定规模后会被flush到磁盘成一个新文件(sstable)。重要的是系统只做了顺序磁盘读写。
    3. 重复的记录只会通过创建新的记录来覆盖,这也就产生了一些冗余的数据。
    4. --> 系统会周期地执行合并操作(compaction)。compaction选择一些文件,并把他们合并到一起,移除重复的更新或删除操作,同时也会删除上述的冗余。
    5. --> 因为sstable也是有序的,所有合并操作非常高效。
  • 读操作:
    1. 检查memtable
    2. 如果memtable中没有找到,就会逆序地一个个检查sstable,直到key被找到。[注意这里仅讨论LSM结构,没有细分到HBase场景。对应的是HBase中已经对应要HRegion的情况。]
    3. --> 因为每个sstable都是有序的,所以查找比较高效(O(logN))。
    4. --> 但是读操作会随着sstable个数增加,变得越来越慢(O(klogN))。
  • 提高读性能
    • 页缓存:将sstable按照LRU缓存在内存中,减少二分查找的消耗 
    • compaction
    • bloom filter:避免大量的读文件操作,通过快速判定sstable中是否包含一个特定的key。

 

FYI