前言
现今互联网科技发展日新月异,大数据、云计算、人工智能等技术已经成为前瞻性产品,海量数据和超高并发让传统的 Web2.0 网站有点力不从心,暴露了很多难以克服的问题。为此,Google、Amazon 、Powerset 等各大平台纷纷推出 NoSQL 技术以应对市场的急速发展,近10年间NoSQL技术百花齐放,HBase、Redis、MongoDB、Cassandra 等技术纷纷涌现。
本文主要向各位介绍 HBase 的发展历史,基础结构与原理,应用的场景,对常用的 JAVA API 操作进行梳理,在最后一节还会详细讲述 HBase 与 MR 之间关系。
目录
一、HBase 的概述
二、HBase 的原理
三、简述 RowKey 设计原理
四、HBase 的 Java API 开发实例
五、HBase与MapReduce的相互调用
一、HBase 的概述
1.1 HBase 的发展历史
HBase(Hadoop Database)是一个高可靠性、高性能、面向列、可伸缩的分布式数据库,典型的 NoSQL(Not Only SQL)数据库。它起源于 Hadoop 的子项目,由 Powerset 公司在2007年创建,同年10 月 HBase 的第一版与 Hadoop 0.15.0 捆绑发布,初期的目标是弥补 MapReduce 在实时操作上的缺失,方便用户可随时操作大规模的数据集。随着大数据与 NoSQL 的流行和迅速发展,在 2010年5月,Apache HBase 脱离了 Hadoop,成为 Apache 基金的顶级项目。次年即 2011年1月 ZooKeeper 也脱离 Hadoop,成为 Apache 基金的顶级项目。
1.2 HBase 的特点
面向列设计:面向列表(簇)的存储和权限控制,列(簇)独立检索。
支持多版本:每个单元中的数据可以有多个版本,默认情况下,版本号可自动分配,版本号就是单元格插入时的时间戳。
稀疏性:为空的列不占用存储空间,表可以设计得非常稀疏。
高可靠性:WAL机制保证了数据写入时不会因集群异常而导致写入数据丢失,Replication机制保证了在集群出现严重的问题时,数据不会发生丢失或损坏。
高性能:底层的LSM数据结构和 Rowkey 有序排列等架构上的独特设计,使得Hbase具有非常高的写入性能。通过科学性地设计RowKey 可让数据进行合理的 Region 切分,主键索引和缓存机制使得Hbase 在海量数据下具备高速的随机读取性能。(下文将再作介绍)
1.3 HBase (NoSQL)与 RDBMS 的区别
1.3.1 传统的 RDBMS 具有以下特征
它是面向表格、视图设计的标准化数据,表中的数据类型也会进行预定义,数据保存后表的结构不易修改。每个表格对列的数据有所限制,最大不会超过几个百个,这将导致不同的数据可能会存放到多个表,表格之间存在一对一,一对多,多对一,多对多等复杂关系。正因如此也限制了 RDBMS 的使用场景更适合于高度结构化的的行业,例如医疗,机关,教育等行业。
1.3.2 HBase 是典型的 NoSQL代表
相对于 RDBMS ,它属于一种高效的映射嵌套型弱视图设计,以Key-Value的方式存储数据,每一行数据都可以有不同的列设计。数据依赖于行键作为唯一标识,当行数据的结构发生变生时,HBase 也能根据需求作出灵活调整。数据以文本方式保存,HBase 把数据的解释任务交给了应用程序,因此它更适合于灵活的数据结构项目。
1.4 HBase 的版本问题
HBase 对 Hadoop 和 JDK 的版本支持性有一定要求,详细内容可在官网查询 http://hbase.apache.org/book.html
Hadoop version support matrix
"S" =supported ,"X"= not supported ,“NT"=Not tested
返回目录
二、HBase 的原理
2.1 HBase 的总体结构
HBase 的架构是依托于 Hadoop 的 HDFS 作为最基本存储基础单元,在 HBase 的集群中由一个 Master 主节点管理多个 Region Server ,而 Zookeeper 进行协调操作,其关系如下图所示:
2.1.1 HMaster
HMaster 用于启动任务管理多个HRegionServer,侦测各个HRegionServer之间的状态,当一个新的HRegionServer登录到HMaster时,HMaster 会告诉它等待分配数据,平衡 HRegionServer 之间的负载。而当某个 HRegionServer 死机时,HMaster会把它负责的所有HRegion标记为未分配,然后再把它们分配到其他 HRegionServer 中,并恢复HRegionServer的故障。事实上 HMaster 的负载很轻, HBase 允许有多个 HMaster 节点共存,但同一时刻只有一个 HMaster 能为系统提供服务,其他的 HMaster 节点处于待命的状态。当正在工作的 HMaster 节点宕机时,其他的 HMaster 则会接管HBase的集群。
2.1.2 HRegionServer
HBase中的所有数据从底层来说一般都是保存在HDFS中的,用户通过一系列HRegionServer获取这些数据。集群一个节点上一般只运行一个HRegionServer,且每一个区段的HRegion只会被一个HRegionServer维护。HRegionServer主要负责响应用户I/O请求,向HDFS文件系统读写数据,是HBase中最核心的模块。
2.1.3 Zookeeper
Apache Zookeeper 起源于 Hadoop 的分布式协同服务,是负责协调集群中的分布式组件,在 2011年1月 ZooKeeper 脱离 Hadoop,成为 Apache 基金的顶级项目。经过多年的发展 Zookeeper 已经成为了分布式大数据框架中容错性的标准框架,被多个分布式开源框架所应用。HBase 的组件之间是通过心跳机制协调系统之间的状态和健康信息的,这些功能都是通过消息实现,一旦消息因外界原因丢失,系统侧需要根据不同的情况进行处理, Zookeeper 的主要作用正是监听并协调各组件的运作。它监听了多个节点的使用状态,保证了 HMaster 处于正常运行当中,一旦 HMaster 发生故障时 Zookeeper 就会发出通知,备用的 HMaster 就会进行替代。Zookeeper 也会监测 HRegionServer 的健康状态, 一旦发生故障就会通知 HMaster ,把任务重新分配给正常的 HRegionServer 进行操作,并恢复有故障的 HRegionServer。
2.2 HBase 运作原理
在介绍完 HBase 的总体结构后,下面将为大家介绍一下 HRegion、HStore、MemStore、HFile、WAL 等组件是如何进行协调操作的,HBase 的运作原理图如下:
2.2.1 HRegion
每个 HRegionServer 内部管理了一系列 HRegion ,他们可以分别属于不同的逻辑表,每个 HRegion 对应了逻辑表中的一个连续数据段。HRegionServer 只是管理表格,实现读写操作。Client 直接连接到 HRegionServer,并通信获取 HBase 中的数据。而 HRegion 则是真实存放 HBase 数据的地方,也就说 HRegion 是 HBase 可用性和分布式的基本单位。当表的大小超过预设值的时候,HBase会自动将表划分为不同的区域,每个区域就是一个HRegion,以主键(RowKey)来区分。一个HRegion会保存一个表中某段连续的数据,一张完整的表数据是保存在多个 HRegion 中的,这些 HRegion 可以在同一个HRegionServer 中,也可以来源于不同的 HRegionServer。
2.2.2 HStore
每个 HRegion 由多个HStore组成,每个 HStore 对应逻辑表在这个 HRegion 集合中的一个 Column Family,建议把具有相近 IO 特性的 Column 存储在同一个 Column Family 中,以实现高效读取 。HStore 由一个 Memstore 及一系列 HFile 组成,Memstore 存储于内存当中,而 HFiles 则是写入到 HDFS 中的持久性文件。用户写入的数据首先会放入 MemStore,当 MemStore大小到达预设值(可通过 hbase.hregion.memstore.flush.size 进行配置)后就会 Flush 成一个StoreFile(即 HFile)文件。
2.2.3 MemStore
MemStore 是一个缓存 (In Memory Sorted Buffer),当所有数据完成 WAL 日志写后,就会写入MemStore 中,由 MemStore 根据一定的算法将数据 Flush 到地层 HDFS 文件中(HFile),每个 HRegion 中的每个 Column Family 有一个自己的 MemStore。当用户从 HBase 中读取数据时,系统将尝试从 MemStore 中读取数据,如果没找到相应数据才会尝试从 HFile 中读取。当服务器宕机时,MemStore 中的数据有可能会丢失,此时 HBase 就会使用 WAL 中的记录对 MemStroe 中的数据进行恢复。
2.2.4 HFile
HFile 是最终保存 HBase 数据行的文件,一个 HFile 文件属于一张表中的某个列簇,当中的数据是按 RowKey、Column Family、Column 升序排序,对相同的Cell(即这三个值都一样),则按timestamp倒序排列。HFile 的具体格式如下图:
HFile 中每条键值存储的开发都包括2个固定长度的数字,分别表示键和值的长度,目的是让客户端可根据字节的偏移访问值域中的数据。根据上图可以看到 KeyValue 类中getKey和getRow方法的区别, getKey() 方法返回的是整个键(图中绿色部分),而 getRow() 方法返回的只是行键 RowKey(图中第4格)。
HFile 文件是根据表的列簇进行区分的,在执行持久化时,记录是有序的。但当 HFile 的文件内容增长到一定阈值后就会触发合并操作,多个 HFiles 就会合并成一个更大的 HFile,由于这几个 HFile 有可能在不同的时间段产生,为保证合并后数据依然是有序排列,HFile 会通过小量压缩或全量压缩进行合并,对 HFile 文件记录进行重新排序。由于全量压缩是一个耗费资源的操作,因此应该保证在资源充足的情况下进行(由于数据压缩问题已超出本文的界限,在以后的章节将会详细介绍)。
当单个 HFile 大小超过一定阈值后,会触发 Split 拆分操作,用户可通过配置 hbase.regionserver.region.split.policy 选择拆分的策略,拆分策略由 RegionSplitPolicy 类进行处理,目前系统已支持 IncreasingToUpperBoundRegionSplitPolicy、ConstantSizeRegionSplitPolicy、DelimitedKeyPrefixRegionSplitPolicy、KeyPrefixRegionSplitPolicy 等多种拆分策略。默认情况下 HRegion 将被拆分成 2 个 HRegion,父 HRegion 会下线,新分出的 2 个子 HRegion 会被 HMaster 分配到相应的 HRegionServer。
2.2.5 WAL
WAL(Write-Ahead-Log,又名 HLog)是 HRegionServer 中的日志记录的工具,当系统发生故障时,可以通过 WAL 恢复数据。在每次用户操作将数据写入MemStore的时候,也会写一份数据到 WAL 当中,WAL 当中包含了部分还没有写入 HFile 的文件。WAL 文件会定期滚动刷新,并删除旧的文件(已持久化到 HFile中的数据)。当 HMaster 通过 Zookeeper 感知到某个HRegionServer意外终止时,HMaster首先会处理遗留的 WAL 文件,将其中不同 HRegion 的 WAL 数据进行拆分,分别放到相应 WAL 的目录下,然后再将失效的 HRegion 重新分配,领取到这些 HRegion 的 HRegionServer 在加载 HRegion的过程中,会发现有历史 WAL 需要处理,因此会把 WAL 中的数据加载到 MemStore 中,然后 Flush 到HFiles,完成数据恢复。
用户可以通过禁用 WAL 方式提高HBase 的性能,然而这将有导致数据丢失的风险,用户应该谨慎处理。一旦禁用了 WAL,系统应该在接收到 HRegionServer 宕机的消息后重新启动写入程序,然而这有可能导致数据重复输入。
返回目录
三、简述 RowKey 设计原理
由于HBase属于分布式系统,数据会根据 RowKey 进行分块存储,只要合理设计好 RowKey 让数据均匀分散多台 HRegionServer 管理,时常被同时查找的数据被存储到同一个HRegion之内,这样就能最大限度地提高系统的性能(在第四节介绍到分页查询方法中,如果能巧妙地设计 RowKey 把每页的数据存储在一个HRegion之内,将有效地提升性能)。反之,如若 RowKey 设计不合理,让大量的数据存储在同一个 HRegionServer 而其他 HRegionServer 长期处理闲置状态,就会减低系统性能,还有可能让 HRegionSever 资源耗尽会发生错误。在拜读过 Sameer Wadkar 等大师所著的《Pro Apache Hadoop》和Nick Dimiduk 所著的 《HBase In Action》等文献后,本人对关于RowKey设计原则归纳成了下面几点:
注意 RowKey 的长度,RowKey越长系统I/O开销越大,在上千万条数据的系统当中RowKey设置超过100个字节,光RowKey就会消耗近1G 的流量
可将 Rowkey 的前位作为散列字段,由程序循环生成,扩展位放时间、表格、属性、时间戳等字段,这样可提高数据均衡分布在每个HRegionServer 实现负载均衡的几率。
利用组合式行键方式,以主机名,事件,时间戳作为行键,根据组件的访问特性进行排序分组。
以随机的散列作为前缀这点很好理解,例如有下面的Order表
OrderId Goods Price
20180802001 iPhone X 8000
20180802002 LYNK&CO Tools 5000
20180823003 AUSU N75S 8600
20180713004 Nikon D7500 7300
若只以OrderId作为RowKey,以递增方式进行存储,数据很有可能只存储于同一个HRegionServer,此时可以用Hash函数随机生成散列,加上必要的属性(可以是辨别符、时间戳等唯一属性),生成类似于154432_180802001、879531_180802002、544688_180823003、687851_180713004等数据。此数据的分配会更均衡,但查找时会更耗资源。
我们也可以利用主机名,事件,时间戳组合键的方式定制行键,例如系统配置了4个HRegionServer,分别用A、B、C、D代表,此时可以将 RowKey 配置为类似于 A-84548454-C、B-3223265-C、C-656565-C、D-333256-C,这里只是举个简单的例子,当然实际操作上组合方式有多种。此方法好处在于用户更容易地控制数据的分布,但会增加管理的繁琐度,如果服务器有变动时,数据存储可能需要重新整理。
其实RowKey设计本来就是一个复杂的问题,在这里介绍的只是冰山一角,希望对大家的RowKey设计有所启发。
返回目录
四、HBase 的 Java API 开发实例
4.1 HBase 的基础操作类
由于各版 HBase 的 Java API 会有不同,下面以比较稳定的 hbase-client 1.2.6 为例子介绍一下 HBase 的具体操作
4.1.1 org.apache.hadoop.hbase.HBaseConfiguration 类
继承了 org.apache.hadoop.conf.Configuration 类,主要用于配置系统运行环境,管理资源池
函数 描述
static Configuration create() 获取当前运行环境下的 Configuration 配置对象
void addResource(Path file) 通过给定的路径所指的文件来添加资源
void clear() 清空所有已设置的属性
String get(String name) 获取属性名对应的值
String getBoolean(String name, boolean defaultValue) 获取为boolean类型的属性值,如果其属性值类型部位boolean,则返回默认属性值
void set(String name, String value) 通过属性名来设置值
void setBoolean(String name, boolean value) 设置boolean类型的属性值
下面是HBaseConfiguration常用的方式
复制代码
1 public class HBaseUtils {
2 public static Configuration config;
3 public static Connection connection;
4
5 static{
6 config=HBaseConfiguration.create();
7
8 try {
9 connection=ConnectionFactory.createConnection(config);
10
11 } catch (IOException e) {
12 // TODO 自动生成的 catch 块
13 e.printStackTrace();
14 }
15 }
16 ......
17 }
复制代码
4.1.2 org.apache.hadoop.hbase.client.Connection 接口
与 SQL 的 Connection 连接相似,用户管理 HBase 客户端与服务端的连接,在操作完成后可通过 connetion.close ()及时释放资源。
通过 Connection 类的 Admin getAdmin()方法可获取 Admin 管理类。
通过 ConnetionFactory 类的 static Connection createConnection (config) 静态方法可获取当前连接。
4.1.3 org.apache.hadoop.hbase.client.HBaseAdmin 类
用于管理HBase数据库的表信息,它提供的方法包括:创建表,删除表,列出表,使表有效或无效,以及添加或删除表列簇成员等。
方法 说明
void addColumn(String tableName, HColumnDescriptor column) 向一个已经存在的表添加列
static void checkHBaseAvailable(HBaseConfiguration conf) 静态函数,查看HBase是否处于运行状态
void createTable(HTableDescriptor desc) 创建一个表,同步操作
void deleteTable(String tableName) 删除一个已经存在的表
void enableTable(String tableName) 使表处于有效状态
void disableTable(String tableName) 使表处于无效状态
HTableDescription listTables() 列出所有用户表
void modifyTable(byte[] tableName, HTableDescriptor htd) 修改表的模式,是异步的操作,可能需要花费一定的时间
boolean tableExists(String tableName) 检查表是否存在
下面例子可用于判断表格是否存在
复制代码
1 public class HBaseUtils {
2 public static Configuration config;
3 public static Connection connection;
4
5 static{
6 config=HBaseConfiguration.create();
7
8 try {
9 connection=ConnectionFactory.createConnection(config);
10 } catch (IOException e) {
11 // TODO 自动生成的 catch 块
12 e.printStackTrace();
13 }
14 }
15
16 public static void checkTable(String tableName)
17 throws Exception {
18 HBaseAdmin admin = (HBaseAdmin) connection.getAdmin();
19
20 if (admin.tableExists(tableName)) {
21 System.out.println(tableName + " exists!");
22 }
23 }
24 }
复制代码
4.1.4 org.apache.hadoop.hbase.HTableDescriptor 类
用于操作表名及其对应表的列簇
方法 说明
HTableDescriptor addFamily(HColumnDescriptor) 添加一个列簇
HColumnDescriptor removeFamily(byte[] column) 移除一个列簇
String getNameAsString() 获取表的名字
String getValue(String) 获取属性的值
HTableDescriptor setValue(String key, String value) 设置属性的值
4.1.5 org.apache.hadoop.hbase.HColumnDescriptor 类
维护列簇的信息,例如版本号,压缩设置等,通常在创建表或者为表添加列簇的时候使用。
列簇被创建后不能直接修改,只能通过删除然后重新创建的方式。 列簇被删除的时候,列簇里面的数据也会同时被删除。
方法 说明
String getNameAsString() 获取列簇的名字
String getValue(String) 获取对应的属性的值
HColumnDescriptor setValue(String key, String value) 设置对应属性的值
一个表通常可以包含1~5个列簇,下面例子可用于新建表,如需要包含多个列簇,可以在columnFamily参数中用 “ , ” 输入
复制代码
1 //新建表
2 public static boolean createTable(String tableName, String columnFamily)
3 throws Exception {
4 HBaseAdmin admin = (HBaseAdmin) connection.getAdmin();
5
6 if (admin.tableExists(tableName)) {
7 System.out.println(tableName + " exists!");
8 return false;
9 } else {
10 //建立列簇
11 String[] columnFamilyArray;
12 if(columnFamily.contains(","))
13 columnFamilyArray = columnFamily.split(",");
14 else{
15 columnFamilyArray=new String[1];
16 columnFamilyArray[0]=columnFamily;
17 }
18 HColumnDescriptor[] hColumnDescriptor = new HColumnDescriptor[columnFamilyArray.length];
19 for (int i = 0; i < hColumnDescriptor.length; i++) {
20 hColumnDescriptor[i] = new HColumnDescriptor(columnFamilyArray[i]);
21 }
22 //建立表对象
23 HTableDescriptor familyDesc = new HTableDescriptor(TableName.valueOf(tableName));
24 for (HColumnDescriptor columnDescriptor : hColumnDescriptor) {
25 familyDesc.addFamily(columnDescriptor);
26 }
27 HTableDescriptor tableDesc = new HTableDescriptor(TableName.valueOf(tableName), familyDesc);
28 //新建表
29 admin.createTable(tableDesc);
30 admin.close();
31 return true;
32 }
33 }
复制代码
4.1.6 org.apache.hadoop.hbase.client.Table 接口
由于 HTable 是非线性安全类,已经被系统丢弃,在 hbase-client v1.0 版本后在数据更新删除时应该使用线性安全的 Table 接口进行操作
方法 说明
boolean checkAdnPut(byte[] row, byte[] family, byte[] qualifier, byte[] value, Put put 自动的检查row/family/qualifier是否与给定的值匹配
void close() 释放所有的资源或挂起内部缓冲区中的更新,请谨记在数据更新删除后使用以释放资源
boolean exists(Get get) 检查Get实例所指定的值是否存在于Table中
Result get(Get get) 获取指定行的某些单元格所对应的值
Result[] get(List
list) 获取多行的单元格所对应的值
ResultScanner getScanner(Scan scan) 获取当前给定列簇的scanner实例
HTableDescriptor getTableDescriptor() 获取当前表的HTableDescriptor实例
void delete(Delete delete) 删除数据
void delete(List list) 删除多行数据
void put(List list) 向表中添加多个值
void put(Put put) 向表中添加值
此接口是HBase最常用的对表格操作的接口,具体的使用方法将在下一节再详细介绍
4.1.7 org.apache.hadoop.hbase.client.Put 类
主要用于对单个行执行添加操作,在 hbase-client v1.0 版本后 add 方法已经被 addColumn 方法所代替
方法 说明
Put addColumn(byte[] family, byte[] qualifier, byte[] value) 将指定的列和对应的值添加到Put实例中
Put addColumn(byte[] family, byte[] qualifier, long ts, byte[] value) 将指定的列和对应的值及时间戳添加到Put实例中
List get(byte[] family,byte[] qualifier) 获取指定列簇和对应值的列
4.1.8 org.apache.hadoop.hbase.client.Get 类
用于获取单行数据的相关信息
方法 说明
Get addColumn(byte[] family, byte[] qualifier) 获取指定列簇和列修饰符对应的Get对象
Get addFamily(byte[] family) 获取指定列簇对应列的Get对象
Get setTimeRange(long timeStamp) 获取指定时间戳的Get对象
Get setFilter(Filter filter) 执行Get操作时设置服务器端的过滤器
4.1.9 org.apache.hadoop.hbase.client.Delete类
用于获取单行数据的相关信息
方法 说明
Delete addColumn(byte[] family, byte[] qualifier) 获取指定列簇和列修饰符的Delete对象(只包含当前version)
Delete addColumn(byte[] family, byte[] qualifier,long timestamp) 获取指定列簇、列修饰符和时间戳的Delete对象(只包含当前version)
Delete addColumns(byte[] family, byte[] qualifier) 获取指定列簇和列修饰符(包含所有version)
Delete addColumns(byte[] family, byte[] qualifier,long timestamp) 获取指定列簇、列修饰符和时间戳的Delete对象(包含所有version)
Delete addFamily(byte[] family) 获取指定的列簇的Delete对象
Delete setTimeR
|