Hadoop 综合揭秘——HBase的原理与应用
前言
现今互联网科技发展日新月异,大数据、云计算、人工智能等技术已经成为前瞻性产品,海量数据和超高并发让传统的 Web2.0 网站有点力不从心,暴露了很多难以克服的问题。为此,Google、Amazon 、Powerset 等各大平台纷纷推出 NoSQL 技术以应对市场的急速发展,近10年间NoSQL技术百花齐放,HBase、Redis、MongoDB、Cassandra 等技术纷纷涌现。
本文主要向各位介绍 HBase 的发展历史,基础结构与原理,应用的场景,对常用的 JAVA API 操作进行梳理,在最后一节还会详细讲述 HBase 与 MR 之间关系。
目录
一、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常用的方式
public class HBaseUtils {
public static Configuration config;
public static Connection connection; static{
config=HBaseConfiguration.create(); try {
connection=ConnectionFactory.createConnection(config); } catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
......
}
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) | 检查表是否存在 |
下面例子可用于判断表格是否存在
public class HBaseUtils {
public static Configuration config;
public static Connection connection; static{
config=HBaseConfiguration.create(); try {
connection=ConnectionFactory.createConnection(config);
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
} public static void checkTable(String tableName)
throws Exception {
HBaseAdmin admin = (HBaseAdmin) connection.getAdmin(); if (admin.tableExists(tableName)) {
System.out.println(tableName + " exists!");
}
}
}
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参数中用 “ , ” 输入
//新建表
public static boolean createTable(String tableName, String columnFamily)
throws Exception {
HBaseAdmin admin = (HBaseAdmin) connection.getAdmin(); if (admin.tableExists(tableName)) {
System.out.println(tableName + " exists!");
return false;
} else {
//建立列簇
String[] columnFamilyArray;
if(columnFamily.contains(","))
columnFamilyArray = columnFamily.split(",");
else{
columnFamilyArray=new String[1];
columnFamilyArray[0]=columnFamily;
}
HColumnDescriptor[] hColumnDescriptor = new HColumnDescriptor[columnFamilyArray.length];
for (int i = 0; i < hColumnDescriptor.length; i++) {
hColumnDescriptor[i] = new HColumnDescriptor(columnFamilyArray[i]);
}
//建立表对象
HTableDescriptor familyDesc = new HTableDescriptor(TableName.valueOf(tableName));
for (HColumnDescriptor columnDescriptor : hColumnDescriptor) {
familyDesc.addFamily(columnDescriptor);
}
HTableDescriptor tableDesc = new HTableDescriptor(TableName.valueOf(tableName), familyDesc);
//新建表
admin.createTable(tableDesc);
admin.close();
return true;
}
}
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<Get> list) | 获取多行的单元格所对应的值 |
ResultScanner getScanner(Scan scan) | 获取当前给定列簇的scanner实例 |
HTableDescriptor getTableDescriptor() | 获取当前表的HTableDescriptor实例 |
void delete(Delete delete) | 删除数据 |
void delete(List<Delete> list) | 删除多行数据 |
void put(List<Put> 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<Cell> 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 setTimeRange(long timeStamp) | 获取指定时间戳的Delete对象 |
4.1.10 org.apache.hadoop.hbase.client.Result 类
用于封装数据查找后的单行查询结果,用 Map 结构保存符合列的对应属性。旧版本一直使用KeyValue来封装列的属性,但自从v1.0版本后,系统都会使用 Cell 对象来封装列的属性,多个旧方法已经被弃用,各位在使用时可以注意一下
方法 | 说明 |
byte[] getRow() | 获取当前行键值 |
byte[] getValue(byte[] family, byte[] qualifier) | 获取指定列簇和列修饰符的最新数据值 |
Cell getColumnLastestCell(byte[] family, byte[] qualifier) | 获取指定列簇、列修饰符最新版本的列 |
List<Cell> getColumnCells(byte[] family, byte[] qualifier) | 获取指定列簇、列修饰符多个版本的列 |
boolean containsColumn(byte[] family, byte[] qualifier) | 判断是否存在指定列簇、列修饰符的列 |
Cell[] listCells() | 获取多个版本所有列 |
NavigableMap<byte[], byte[]> getFamilyMap(byte[] family) | 获取批定列簇的列 |
NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> getMap() | 获取所有列簇所有版本的列 |
4.1.11 org.apache.hadoop.hbase.client.Scan类
当用户想在查询时一次返回多选结果,可以通过Scan进行条件查询,通过Scan可以设定多种过滤器和起始行,从而根据用户的需要对整张表进行扫描。然而在默认情况下,Scan对象是用迭代形式进行查询一次只返回一行,为了提高内存的使用率可以通过 Scan.setCaching (int caching)方法把返回行数进行调整,从而提高系统性能。
方法 | 说明 |
Scan addFamily(byte[] family) | 获取指定列簇的Scan |
Scan addColumn(byte[] family, byte[] qualifier) | 获取指定列簇和列修饰符的Scan |
Scan setCaching(int caching) | 设置缓存数量 |
Scan setStartRow(byte[] startRow) | 设置开始行rowkey,如果不设置将由第一行开始查找 |
Scan setStopRow(byte[] stopRow) | 设置结束行rowkey |
Scan setTimeStamp(long stamp) | 获取指定时间戳的Scan对象 |
Scan setMaxResultSize(long maxResultSize) | 设置最大返回数量 |
Scan setFilter(Filter filter) | 设置查询条件 |
4.1.12 org.apache.hadoop.hbase.client.ResultScanner 接口
由于Scan 查找会返回多行数据,为了实现逐行查询功能,ResultScanner类出现,它把扫描到每一行数据封装成一个Result实例,并将所有的Result实例放入一个迭代器中。
ResultScanner 只有几个简单的方法,在有条件的情况下,使用Result[] next(int paramInt)一次获取多条数据,有利用提升系统的性能。
方法 | 说明 |
Result next() | 返回Result,转到下一行 |
Result[] next (int paramInt) | 返回多行数据的Result数组 |
void close() | 关闭连接释放资源 |
4.2 通用类 HBaseUtils
下面本人参考了一些基础文献对HBase常用的CURD操作进行了归纳,整理出一个通用类HBaseUtils,希望对大家日常开发有所帮助。
由于HBase是分布式系统,其性能与数据的存储方式有莫大的关联,所以在开发时应该与第三节RowKey的设计原理相结合进行调整,要不然有可能影响系统性能。
public class HBaseUtils {
public static Configuration config;
public static Connection connection; static{
config=HBaseConfiguration.create(); try {
connection=ConnectionFactory.createConnection(config);
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
} //新建表
public static boolean createTable(String tableName, String columnFamily)
throws Exception {
HBaseAdmin admin = (HBaseAdmin) connection.getAdmin(); if (admin.tableExists(tableName)) {
System.out.println(tableName + " exists!");
return false;
} else {
//建立列簇
String[] columnFamilyArray;
if(columnFamily.contains(","))
columnFamilyArray = columnFamily.split(",");
else{
columnFamilyArray=new String[1];
columnFamilyArray[0]=columnFamily;
}
HColumnDescriptor[] hColumnDescriptor = new HColumnDescriptor[columnFamilyArray.length];
for (int i = 0; i < hColumnDescriptor.length; i++) {
hColumnDescriptor[i] = new HColumnDescriptor(columnFamilyArray[i]);
}
//建立表对象
HTableDescriptor familyDesc = new HTableDescriptor(TableName.valueOf(tableName));
for (HColumnDescriptor columnDescriptor : hColumnDescriptor) {
familyDesc.addFamily(columnDescriptor);
}
HTableDescriptor tableDesc = new HTableDescriptor(TableName.valueOf(tableName), familyDesc);
//新建表
admin.createTable(tableDesc);
admin.close();
return true;
}
} //插入数据
public static boolean put(String tablename, String row, String columnFamily,
String qualifier, String data) throws Exception {
Table table = connection.getTable(TableName.valueOf(tablename));
Put put = new Put(Bytes.toBytes(row));
put.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(qualifier),
Bytes.toBytes(data));
table.put(put);
System.out.println("put '" + row + "', '" + columnFamily + ":" + qualifier
+ "', '" + data + "'");
table.close();
return true;
} //插入多列数据
public static boolean put(String tablename, String row, String columnFamily,
String[] qualifierList, String[] dataList) throws Exception {
Table table = connection.getTable(TableName.valueOf(tablename));
Put put = new Put(Bytes.toBytes(row));
for(int n=0;n<qualifierList.length;n++){
put.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(qualifierList[n]),
Bytes.toBytes(dataList[n]));
}
table.put(put);
table.close();
return true;
} //查看某行
public static Map<String, Object> getRow(String tablename, String row) throws Exception {
Table table = connection.getTable(TableName.valueOf(tablename));
Get get = new Get(Bytes.toBytes(row));
Result result = table.get(get);
table.close();
return resultToMap(result);
} //查看全表
public static List<Map<String, Object>> getTable(String tablename) throws Exception {
Table table = connection.getTable(TableName.valueOf(tablename));
Scan s = new Scan();
ResultScanner rs = table.getScanner(s); List<Map<String, Object>> resList = new ArrayList<Map<String, Object>>();
for (Result r : rs) {
Map<String, Object> tempmap = resultToMap(r);
resList.add(tempmap);
}
table.close();
return resList;
} //单个qualifier的值等于data
public static List<Map<String, Object>> queryEqual(String tablename, String columnFamily, String qualifier, String data) throws Exception {
//某列等于data的数据
Filter filter = new SingleColumnValueFilter(Bytes.toBytes(columnFamily), Bytes.toBytes(qualifier),
CompareOp.EQUAL, Bytes.toBytes(data));
FilterList filterList = new FilterList();
filterList.addFilter(filter);
return query(tablename, filterList);
} //查询qualifier值在mindata和maxdata之间的数据
public static List<Map<String, Object>> queryBetween(String tablename, String columnFamily, String qualifier, String mindata, String maxdata) throws Exception {
SingleColumnValueFilter filter1 = new SingleColumnValueFilter(Bytes.toBytes(columnFamily), Bytes.toBytes(qualifier),
CompareOp.LESS_OR_EQUAL, Bytes.toBytes(maxdata));
SingleColumnValueFilter filter2 = new SingleColumnValueFilter(Bytes.toBytes(columnFamily), Bytes.toBytes(qualifier),
CompareOp.GREATER_OR_EQUAL, Bytes.toBytes(mindata));
FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL);
//不存在此值时跳过
filter1.setFilterIfMissing(true);
filter2.setFilterIfMissing(true);
//加入过虑器
filterList.addFilter(filter1);
filterList.addFilter(filter2);
return query(tablename, filterList);
} //查询列
public static List<Map<String, Object>> queryColumn(String tablename, String prefix) throws Exception {
Filter filter = new ColumnPrefixFilter(Bytes.toBytes(prefix));
FilterList filterList = new FilterList();
filterList.addFilter(filter);
return query(tablename, filterList);
} //分页查询
public static List<Map<String, Object>> queryRowPage(String tablename, String columnFamily, String qualifier,String value,String count,String startrowkey,String stoprowkey) throws Exception {
FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL);
//设置每页面数量pageCount
Filter filter1 = new PageFilter(Integer.parseInt(count));
Filter filter2 = new SingleColumnValueFilter(Bytes.toBytes(columnFamily), Bytes.toBytes(qualifier),
CompareOp.EQUAL, Bytes.toBytes(value));
//设置查询条件
filterList.addFilter(filter1);
filterList.addFilter(filter2); Table table = connection.getTable(TableName.valueOf(tablename));
Scan s=new Scan();
//设置key的开始值和结束值
if(startrowkey!=null)
s.setStartRow(Bytes.toBytes(startrowkey));
if(stoprowkey!=null)
s.setStopRow(Bytes.toBytes(stoprowkey));
//加入查询条件
s.setFilter(filterList);
ResultScanner rs = table.getScanner(s); List<Map<String, Object>> resList = new ArrayList<Map<String, Object>>();
for (Result r : rs) {
Map<String, Object> tempmap = resultToMap(r);
resList.add(tempmap);
} table.close();
return resList;
} //数据转换为Map
private static Map<String, Object> resultToMap(Result result) {
Map<String, Object> resMap = new HashMap<String, Object>();
List<Cell> listCell = result.listCells();
Map<String, Object> tempMap = new HashMap<String, Object>();
String rowname = "";
List<String> familynamelist = new ArrayList<String>();
for (Cell cell : listCell) {
byte[] rowArray = cell.getRowArray();
byte[] familyArray = cell.getFamilyArray();
byte[] qualifierArray = cell.getQualifierArray();
byte[] valueArray = cell.getValueArray();
int rowoffset = cell.getRowOffset();
int familyoffset = cell.getFamilyOffset();
int qualifieroffset = cell.getQualifierOffset();
int valueoffset = cell.getValueOffset();
int rowlength = cell.getRowLength();
int familylength = cell.getFamilyLength();
int qualifierlength = cell.getQualifierLength();
int valuelength = cell.getValueLength(); byte[] temprowarray = new byte[rowlength];
System.arraycopy(rowArray, rowoffset, temprowarray, 0, rowlength);
String temprow= Bytes.toString(temprowarray); byte[] tempqulifierarray = new byte[qualifierlength];
System.arraycopy(qualifierArray, qualifieroffset, tempqulifierarray, 0, qualifierlength);
String tempqulifier= Bytes.toString(tempqulifierarray); byte[] tempfamilyarray = new byte[familylength];
System.arraycopy(familyArray, familyoffset, tempfamilyarray, 0, familylength);
String tempfamily= Bytes.toString(tempfamilyarray); byte[] tempvaluearray = new byte[valuelength];
System.arraycopy(valueArray, valueoffset, tempvaluearray, 0, valuelength);
String tempvalue= Bytes.toString(tempvaluearray); tempMap.put(tempfamily + ":" + tempqulifier, tempvalue); rowname = temprow;
String familyname = tempfamily;
if (familynamelist.indexOf(familyname) < 0) {
familynamelist.add(familyname);
}
}
resMap.put("rowname", rowname);
for (String familyname : familynamelist) {
HashMap<String,Object> tempFilterMap = new HashMap<String,Object>();
for (String key : tempMap.keySet()) {
String[] keyArray = key.split(":");
if(keyArray[0].equals(familyname)){
tempFilterMap.put(keyArray[1],tempMap.get(key));
}
}
resMap.put(familyname, tempFilterMap);
} return resMap;
} //公共query查找方法
private static List<Map<String, Object>> query(String tablename, FilterList filterList) throws Exception {
Table table = connection.getTable(TableName.valueOf(tablename));
Scan s = new Scan();
s.setFilter(filterList);
ResultScanner rs = table.getScanner(s); List<Map<String, Object>> resList = new ArrayList<Map<String, Object>>();
for (Result r : rs) {
Map<String, Object> tempmap = resultToMap(r);
resList.add(tempmap);
}
table.close();
return resList;
} //删除表
public static boolean delete(String tableName) throws IOException {
HBaseAdmin admin = (HBaseAdmin) connection.getAdmin();
if (admin.tableExists(tableName)) {
try {
admin.disableTable(tableName);
admin.deleteTable(tableName);
admin.close();
} catch (Exception e) {
e.printStackTrace();
return false;
}finally{
admin.close();
}
}
return true;
} //删除ColumnFamily
public static boolean deleteColumnFamily(String tableName,String columnFamilyName) throws IOException {
HBaseAdmin admin = (HBaseAdmin) connection.getAdmin();
if (admin.tableExists(tableName)) {
try {
admin.deleteColumn(tableName,columnFamilyName);
} catch (Exception e) {
e.printStackTrace();
return false;
}finally{
admin.close();
}
}
return true;
} //删除row
public static boolean deleteRow(String tableName,String rowName) throws IOException {
HBaseAdmin admin = (HBaseAdmin) connection.getAdmin();
Table table = connection.getTable(TableName.valueOf(tableName));
if (admin.tableExists(tableName)) {
try {
Delete delete = new Delete(rowName.getBytes());
table.delete(delete);
} catch (Exception e) {
e.printStackTrace();
return false;
}finally{
table.close();
}
}
return true;
} //删除qualifier
public static boolean deleteQualifier(String tableName,String rowName,String columnFamilyName,String qualifierName) throws IOException {
HBaseAdmin admin = (HBaseAdmin) connection.getAdmin();
Table table = connection.getTable(TableName.valueOf(tableName));
if (admin.tableExists(tableName)) {
try {
Delete delete = new Delete(rowName.getBytes());
delete.addColumns(columnFamilyName.getBytes(),qualifierName.getBytes());
table.delete(delete);
} catch (Exception e) {
e.printStackTrace();
return false;
}finally{
table.close();
}
}
return true;
} }
五、HBase与MapReduce的相互调用
5.1 MapReduce 与 HBase 的关系
有朋友常说现在流程NoSQL架构 ,系统常用的是 Spark、HBase、Redis、Cassandra 等技术,MapReducer 已经没落。然而本人觉得 MapReduce 与 HBase 等NoSQL技术并非处于对立发展的关系,反而是相辅相成,互补不足的技术。由于受到数据块、IO、网络资源等限制,MapReduce 的先天架构决定其主要应用于大型文件的存取管理。然而对大量小型文件的管理方面,MapReduce 却是无能为力。因此 HBase 等技术才会诞生并迅速发展,随着 Hadoop 2.0 的发展,HBase 以 HDFS 为基础,补尝了 MapReduce 在海量文件管理方面的不足。
在很多大型的政府机关,金融管理,图书管理,交通航运等平台上,往往会以 MR 作为大型的持久性资源库,以 HBase 进行数据提取作为某个区域或阶段的研究对象,加以分析挖掘,最后形成汇总。有见及此,Hadoop 为 MR与HBase开发出一系列的数据转换工具,方便开发人员利用。
5.2 常用类简介
5.2.1 TableMapReduceUtil 绑定表关系
TableMapReduceUtil为编辑人员准备的几个常用的方法,可快速地绑定Mapper/Reducer与HBase中Table的关系
方法 | 说明 |
static void setScannerCaching(Job job, int batchSize) | 设置Scan缓存大小,默认值为1,应按运行环境设置不适宜过大 |
static void initTableReducerJob(String table,Class<? extends TableReducer> reducer, Job job) | 绑定从Reducer输出数据所要存入的Table |
static void initTableMapperJob(String table, Scan scan,Class<? extends TableMapper> mapper, Class<?> outputKeyClass,Class<?> outputValueClass, Job job) | 绑定要从Table获取到Scan数据所送往的Mapper |
static void initTableMapperJob(List<Scan> scans,Class<? extends TableMapper> mapper,Class<?> outputKeyClass,Class<?> outputValueClass, Job job) | 绑定要从多个Table中获取到的List<Scan>所送往的Mapper |
5.2.2 设置数据格式
在设置Job的运行条件时可使用以下方法按照需要把数据输入或输入设置为Table格式
Job.setInputFormatClass(TableOutputFormat.class);
Job.setOutputFormatClass(TableOutputFormat.class);
5.2.3 TableMapper 与 TableReducer
为提供与 HBase 的数据对接,系统提供了TableMapper与TableReducer两个常用类
public abstract class TableMapper<KEYOUT, VALUEOUT>
extends Mapper<ImmutableBytesWritable, Result, KEYOUT, VALUEOUT> {
}
TableMappler 是继承了 Mapper<ImmutableBytesWritable, Result, KEYOUT, VALUEOUT> 并以 ImmeutableBytesWritable对象(键值RowKey)为输入InputKey,以 Result 为输入 InputValue 的 Mapper,目的是把HBase中某个Table中的行数据输入到 TableMapper 进行处理
public abstract class TableReducer<KEYIN, VALUEIN, KEYOUT>
extends Reducer<KEYIN, VALUEIN, KEYOUT, Mutation> {
}
TableReducer 则继承了 Reducer<KEYIN, VALUEIN, KEYOUT, Mutation>,它以 RowKey 作出输出键 OutputKey,以行数据 Mutation 作为输出值 OutputValue ,把 TableReducer 处理完的数据发送到预先绑定的 Table 中。 Mutation 虚拟类实现了 Row, CellScannable 等接口(Put类就是Mutation最常用的实现类),因此系统可以根据 RowKey 轻松地找到对应的行数据。
5.3 常用实例
5.3.1 利用TableReducer从MapReduce中提取数据发送到HBase中的某个Table
假设某机关单位用MR存储了全国人员的申请审核文件档案,各个省镇部门需要按照自己权限获取相应的资料保存到各自的HBase中
此时,可以在Mapper 中按时地区参数 Area 进行筛选,把符合条件的资料发送到 TableReducer, 通过 TaskInputOutputContext.write(ImmutableBytesWritable arg0, Mutation arg1) 方法,把Reducer 中的数据保存到 HBase 的 Table 中去。其中 ImmutableBytesWritable 为行键 RowKey,Mutation 为行数据。
public class MrToHBaseExample extends Configured implements Tool{ public static class MyMapper extends Mapper<LongWritable,Text,NullWritable,ExamineWritable>{
private static String Area="nothing"; @Override
public void setup(Context context){
String param=context.getConfiguration().getStrings("Area")[0];
if(!param.isEmpty())
Area=param;
} public void map(LongWritable longwritable,Text text,Context context)
throws IOException,InterruptedException{
String[] data=text.toString().split(",");
ExamineWritable examine=new ExamineWritable();
examine.Id=new Text(data[0]);
examine.Name=new Text(data[1]);
examine.Age=new IntWritable(new Integer(data[2]));
examine.Gender=new Text(data[3]);
examine.Area=new Text(data[4]);
examine.Approved=new IntWritable(new Integer(data[5]));
if(Area.equals(data[4])){
context.write(NullWritable.get(), examine);
}
}
} public static class MyReducer extends TableReducer<NullWritable,ExamineWritable,ImmutableBytesWritable>{
public void reduce(NullWritable key,Iterable<ExamineWritable> values,Context context)
throws IOException,InterruptedException{ for(ExamineWritable value : values){
Put put=new Put(Bytes.toBytes(value.Id.toString()));
put.addColumn(Bytes.toBytes("family1"), Bytes.toBytes("id"), Bytes.toBytes(value.Id.toString()));
put.addColumn(Bytes.toBytes("family1"), Bytes.toBytes("name"), Bytes.toBytes(value.Name.toString()));
put.addColumn(Bytes.toBytes("family1"), Bytes.toBytes("age"), Bytes.toBytes(value.Age.toString()));
put.addColumn(Bytes.toBytes("family1"), Bytes.toBytes("gender"), Bytes.toBytes(value.Gender.toString()));
put.addColumn(Bytes.toBytes("family1"), Bytes.toBytes("area"), Bytes.toBytes(value.Area.toString()));
put.addColumn(Bytes.toBytes("family1"), Bytes.toBytes("approved"), Bytes.toBytes(value.Approved.toString()));
context.write(new ImmutableBytesWritable(Bytes.toBytes(value.Id.toString())), put);
}
}
} public int run(String[] arg0) throws Exception {
// TODO 自动生成的方法存根
// TODO Auto-generated method stub
//生成表Examine,列簇family1
HBaseUtils.createTable("Examine", "family1");
//建立任务Job
Job job=Job.getInstance(getConf());
job.setJarByClass(MrToHBaseExample.class);
//初始化HBase,把MyReducer输入数据保存到Examine表
TableMapReduceUtil.initTableReducerJob("Examine", MyReducer.class, job);
//注册Key/Value类型为Text
job.setOutputKeyClass(ImmutableBytesWritable.class);
job.setOutputValueClass(Put.class);
//若Map的转出Key/Value不相同是需要分别注册
job.setMapOutputKeyClass(NullWritable.class);
job.setMapOutputValueClass(ExamineWritable.class);
//注册Mapper及Reducer处理类
job.setMapperClass(MyMapper.class);
job.setReducerClass(MyReducer.class);
//输入输出数据格式化类型为TextInputFormat/TableOutputFormat
job.setInputFormatClass(TextInputFormat.class);
job.setOutputFormatClass(TableOutputFormat.class);
//获取命令参数
String[] args=new GenericOptionsParser(getConf(),arg0).getRemainingArgs();
FileInputFormat.setInputPaths(job,new Path(args[0]));
boolean status=job.waitForCompletion(true);
if(status)
return 0;
else
return 1;
} public static void main(String[] args) throws Exception{
Configuration conf=new Configuration();
ToolRunner.run(new MrToHBaseExample(), args);
}
} public class HBaseUtils {
public static Configuration config;
public static Connection connection; static{
config=HBaseConfiguration.create(); try {
connection=ConnectionFactory.createConnection(config);
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
} //新建表
public static boolean createTable(String tableName, String columnFamily)
throws Exception {
HBaseAdmin admin = (HBaseAdmin) connection.getAdmin(); if (admin.tableExists(tableName)) {
System.out.println(tableName + " exists!");
return false;
} else {
//建立列簇
String[] columnFamilyArray;
if(columnFamily.contains(","))
columnFamilyArray = columnFamily.split(",");
else{
columnFamilyArray=new String[1];
columnFamilyArray[0]=columnFamily;
}
HColumnDescriptor[] hColumnDescriptor = new HColumnDescriptor[columnFamilyArray.length];
for (int i = 0; i < hColumnDescriptor.length; i++) {
hColumnDescriptor[i] = new HColumnDescriptor(columnFamilyArray[i]);
}
//建立表对象
HTableDescriptor familyDesc = new HTableDescriptor(TableName.valueOf(tableName));
for (HColumnDescriptor columnDescriptor : hColumnDescriptor) {
familyDesc.addFamily(columnDescriptor);
}
HTableDescriptor tableDesc = new HTableDescriptor(TableName.valueOf(tableName), familyDesc);
//新建表
admin.createTable(tableDesc);
admin.close();
return true;
}
}
}
只需要输入执行命令
hadoop jar 【Jar名称】 【Main类全名称】-D 【参数名=参数值】 【InputPath】
即可得到以下运行结果( 若要获取广东省范围内的文件,参数为 Area=GuangDong)
5.3.2 通过TableMapper把HBase中某个表的数据汇总到 MR
当系统需要把符合条件的数据从HBase汇总到MR时,可利用TableMapper工具类,先建立Scan对象,加入筛选条件,然后利用 static void initTableMapperJob(String table, Scan scan,Class<? extends TableMapper> mapper, Class<?> outputKeyClass,Class<?> outputValueClass, Job job) 方法绑定TableMapper,Scan,Job等关系,系统就会把 table 中符合筛选条件的数据发送Mapper 进行处理。
下面例子就是从HBase的Examine表中把Area等于GuangDong的数据发送到MR进行保存
public class MrToHBaseExample extends Configured implements Tool{ public static class MyMapper extends TableMapper<Text,ExamineWritable>{ public void map(ImmutableBytesWritable writable,Result result,Context context)
throws IOException,InterruptedException{
ExamineWritable examine=new ExamineWritable();
for(Cell cell:result.listCells()){
String value=Bytes.toString(CellUtil.cloneValue(cell));
//获取行数据
if((Bytes.toString(CellUtil.cloneQualifier(cell))).equals("id"))
examine.Id=new Text(value);
if((Bytes.toString(CellUtil.cloneQualifier(cell))).equals("name"))
examine.Name=new Text(value);
if((Bytes.toString(CellUtil.cloneQualifier(cell))).equals("age"))
examine.Age=new IntWritable(new Integer(value));
if((Bytes.toString(CellUtil.cloneQualifier(cell))).equals("gender"))
examine.Gender=new Text(value);
if((Bytes.toString(CellUtil.cloneQualifier(cell))).equals("area"))
examine.Area=new Text(value);
if((Bytes.toString(CellUtil.cloneQualifier(cell))).equals("approved"))
examine.Approved=new IntWritable(new Integer(value));
} context.write(new Text(Bytes.toString(writable.get())),examine);
}
} public static class MyReducer extends Reducer<Text,ExamineWritable,NullWritable,Text>{
public void reduce(Text key,Iterable<ExamineWritable> values,Context context)
throws IOException,InterruptedException{
String data=null;
for(ExamineWritable value : values){
data=value.Id.toString()+","+value.Name.toString()+","
+value.Age.toString()+","+value.Gender.toString()+","
+value.Area.toString()+","+value.Approved.toString();
context.write(NullWritable.get(),new Text(data));
}
}
} public int run(String[] arg0) throws Exception {
// TODO 自动生成的方法存根
// TODO Auto-generated method stub
//建立任务Job
Job job=Job.getInstance(getConf());
job.setJarByClass(MrToHBaseExample.class);
//初始化TableMapper,把Examine表中符合条件的数据发送到Mapper
Scan scan=new Scan();
scan.addFamily(Bytes.toBytes("family1"));
Filter filter=new SingleColumnValueFilter(Bytes.toBytes("family1"),Bytes.toBytes("Area"),CompareOp.EQUAL,Bytes.toBytes("GuangDong"));
scan.setFilter(filter);
TableMapReduceUtil.initTableMapperJob("Examine",scan,MyMapper.class,Text.class,ExamineWritable.class,job);
//注册Key/Value类型为Text
job.setOutputKeyClass(NullWritable.class);
job.setOutputValueClass(Text.class);
//若Map的转出Key/Value不相同是需要分别注册
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(ExamineWritable.class);
//注册Mapper及Reducer处理类
job.setMapperClass(MyMapper.class);
job.setReducerClass(MyReducer.class);
//输入输出数据格式化类型为TextInputFormat/TableOutputFormat
job.setInputFormatClass(TableInputFormat.class);
job.setOutputFormatClass(TextOutputFormat.class);
//获取命令参数
String[] args=new GenericOptionsParser(getConf(),arg0).getRemainingArgs();
FileOutputFormat.setOutputPath(job,new Path(args[0]));
boolean status=job.waitForCompletion(true);
if(status)
return 0;
else
return 1;
} public static void main(String[] args) throws Exception{
Configuration conf=new Configuration();
ToolRunner.run(new MrToHBaseExample(), args);
}
}
运行结果
5.3.2 通过TableMapper把HBase中多个表的数据汇总到 MR
若输入的数据源来源于HBase的不同表格,TableMapReduceUtil 类还提供static void initTableMapperJob(List<Scan> scans,Class<? extends TableMapper> mapper,Class<?> outputKeyClass,Class<?> outputValueClass, Job job) 方法来实现此功能。用户可在通过List<Scan>参数绑定不同的数据源,利用 Scan.setAttribute(Scan.SCAN_ATTRIBUTES_TABLE_NAME, byte[] tableName) 方法绑定Scan所要查找的表名。然后通过 Job.setInputFormatClass(MultiTableInputFormat.class) 设置为多数据源采集模式。若要在TableMapper中区分不同的数据源表格,还可以在上下文中通过TableSplit.getTableName()方法获取输入的表格名称。
假如在HBase中Examine表格保存了审核结果表,当中包含了申请编号id,申请人姓名 name,年龄 age,性别 gender,所在地 area,审核结果 approved等信息
Assess 表格保存了审核流程记录表,当中包含申请编号id,申请时间 filingDate,审核时间approvedDate,审核人 assessor,审核商品 goods等信息
系统会通过TableMapReduceUtil 类所提供的 static void initTableMapperJob(List<Scan> scans,Class<? extends TableMapper> mapper,Class<?> outputKeyClass,Class<?> outputValueClass, Job job) 方法绑定两个不同的 Table。重写 Mapper 的 setup() 方法,通过上下文context 获取TableSplit对象,然后利用TableSplit.getTableName()分辨所输入数据表的来源。然后把ExamineDetailKey 作为 TableMapper 的 OutputKey, 以 ExamineDetailValue作为TableMapper的OutputValue,通过自定义的ExamineDetailSortComparator,ExamineDetailGroupComparator 对数据进行排序与分组,把 Id 相同的一组数据分派到同一个reduce方法进行处理,最后把同一组数据的审核结果与流程记录保存到MR中。
由于本文目的主要是讲述 HBase 对表数据的处理方式,在此对排序SortComparator/分组GroupComparator不作细说,如果对 MR 的数据排序组合操作有兴趣的朋友可以参考本人的另一篇文章
Hadoop 综合揭秘——MapReduce 基础编程(介绍 Combine、Partitioner、WritableComparable、WritableComparator 使用方式)
里面会有更详细的介绍
public class ExamineDetailKey implements WritableComparable<ExamineDetailKey> {
//当TableType为0时是审核结果表数据,为1是审核流程记录表数据
public static final IntWritable IsExamine=new IntWritable(0);
public static final IntWritable IsAssess=new IntWritable(1);
public IntWritable TableType=new IntWritable();
public Text Id=new Text(); @Override
public void readFields(DataInput in) throws IOException {
// TODO 自动生成的方法存根
this.TableType.readFields(in);
this.Id.readFields(in);
} @Override
public void write(DataOutput out) throws IOException {
// TODO 自动生成的方法存根
this.TableType.write(out);
this.Id.write(out);
} @Override
public int compareTo(ExamineDetailKey o) {
// TODO 自动生成的方法存根
if(this.Id.equals(o.Id))
return this.TableType.compareTo(o.TableType);
else
return this.Id.compareTo(o.Id);
} @Override
public boolean equals(Object o){
if(!(o instanceof ExamineDetailKey))
return false; ExamineDetailKey writable=(ExamineDetailKey)o;
if(this.Id.equals(writable.Id)&&this.TableType.equals(writable.TableType))
return true;
else
return false;
} @Override
public int hashCode(){
return (this.Id.toString()+this.TableType.toString()).hashCode();
}
} public class ExamineDetailValue implements Writable{
public Text Id=new Text();
public Text Name=new Text();
public IntWritable Age=new IntWritable();
public Text Gender=new Text();
public Text Area=new Text();
public IntWritable Approved=new IntWritable();
public Text Assessor=new Text();
public Text ApprovedDate=new Text();
public Text FilingDate=new Text();
public Text Goods=new Text(); @Override
public void readFields(DataInput in) throws IOException {
// TODO 自动生成的方法存根
this.Id.readFields(in);
this.Name.readFields(in);
this.Age.readFields(in);
this.Gender.readFields(in);
this.Area.readFields(in);
this.Approved.readFields(in);
this.Assessor.readFields(in);
this.ApprovedDate.readFields(in);
this.FilingDate.readFields(in);
this.Goods.readFields(in);
} @Override
public void write(DataOutput out) throws IOException {
// TODO 自动生成的方法存根
this.Id.write(out);
this.Name.write(out);
this.Age.write(out);
this.Gender.write(out);
this.Area.write(out);
this.Approved.write(out);
this.Assessor.write(out);
this.ApprovedDate.write(out);
this.FilingDate.write(out);
this.Goods.write(out);
}
} public class ExamineDetailSortComparator extends WritableComparator {
public ExamineDetailSortComparator(){
super(ExamineDetailKey.class,true);
}
} public class ExamineDetailGroupComparator extends WritableComparator {
public ExamineDetailGroupComparator(){
super(ExamineDetailKey.class,true);
} @Override
public int compare(WritableComparable a,WritableComparable b){
ExamineDetailKey key1=(ExamineDetailKey) a;
ExamineDetailKey key2=(ExamineDetailKey) b;
return key1.Id.compareTo(key2.Id);
}
} public class MrToHBaseExample extends Configured implements Tool{ public static class MyMapper extends TableMapper<ExamineDetailKey,ExamineDetailValue>{
private String tablename; @Override
public void setup(Context context) throws IOException,InterruptedException{
TableSplit split=(TableSplit)context.getInputSplit();
tablename=new String(split.getTableName());
} public void map(ImmutableBytesWritable writable,Result result,Context context)
throws IOException,InterruptedException{
ExamineDetailKey key=new ExamineDetailKey();
ExamineDetailValue value=new ExamineDetailValue(); for(Cell cell:result.listCells()){
String data=Bytes.toString(CellUtil.cloneValue(cell));
if(tablename.equals("Examine")){
//设置表类型辨识符TableType
key.TableType=ExamineDetailKey.IsExamine;
//获取行数据
if((Bytes.toString(CellUtil.cloneQualifier(cell))).equals("id")){
key.Id=new Text(data);
value.Id=new Text(data);
}
if((Bytes.toString(CellUtil.cloneQualifier(cell))).equals("name"))
value.Name=new Text(data);
if((Bytes.toString(CellUtil.cloneQualifier(cell))).equals("age"))
value.Age=new IntWritable(new Integer(data));
if((Bytes.toString(CellUtil.cloneQualifier(cell))).equals("gender"))
value.Gender=new Text(data);
if((Bytes.toString(CellUtil.cloneQualifier(cell))).equals("area"))
value.Area=new Text(data);
if((Bytes.toString(CellUtil.cloneQualifier(cell))).equals("approved"))
value.Approved=new IntWritable(new Integer(data));
}else if(tablename.equals("Assess")){
//设置表类型辨识符TableType
key.TableType=ExamineDetailKey.IsAssess;
//获取行数据
if((Bytes.toString(CellUtil.cloneQualifier(cell))).equals("id")){
key.Id=new Text(data);
value.Id=new Text(data);
}
if((Bytes.toString(CellUtil.cloneQualifier(cell))).equals("assessor"))
value.Assessor=new Text(data);
if((Bytes.toString(CellUtil.cloneQualifier(cell))).equals("approvedDate"))
value.ApprovedDate=new Text(data);
if((Bytes.toString(CellUtil.cloneQualifier(cell))).equals("filingDate"))
value.FilingDate=new Text(data);
if((Bytes.toString(CellUtil.cloneQualifier(cell))).equals("goods"))
value.Goods=new Text(data);
}
} context.write(key,value);
}
} public static class MyReducer extends Reducer<ExamineDetailKey,ExamineDetailValue,NullWritable,Text>{
public void reduce(ExamineDetailKey key,Iterable<ExamineDetailValue> values,Context context)
throws IOException,InterruptedException{
String data=key.Id.toString(); for(ExamineDetailValue value : values){
if(key.TableType.equals(ExamineDetailKey.IsExamine)){
data+=","+value.Name.toString()+","+value.Age.toString()+","
+value.Gender.toString()+","+value.Area.toString()+","
+value.Approved.toString();
}else if(key.TableType.equals(ExamineDetailKey.IsAssess)){
data+=","+value.Assessor.toString()+","
+value.ApprovedDate.toString()+","+value.FilingDate.toString()+","
+value.Goods.toString();
context.write(NullWritable.get(),new Text(data));
}
}
}
} public int run(String[] arg0) throws Exception {
// TODO 自动生成的方法存根
// TODO Auto-generated method stub
//生成表Examine,列簇family1
//HBaseUtils.createTable("Examine", "family1");
//建立任务Job
Job job=Job.getInstance(getConf());
job.setJarByClass(MrToHBaseExample.class);
//初始化TableMapper,把Examine表中的数据与Assess中的数据发送到Mapper
Scan scan1=new Scan();
scan1.addFamily(Bytes.toBytes("family1"));
scan1.setAttribute(Scan.SCAN_ATTRIBUTES_TABLE_NAME, "Examine".getBytes());
Scan scan2=new Scan();
scan2.addFamily(Bytes.toBytes("family1"));
scan2.setAttribute(Scan.SCAN_ATTRIBUTES_TABLE_NAME, "Assess".getBytes());
List<Scan> list=new ArrayList<Scan>();
list.add(scan1);
list.add(scan2);
TableMapReduceUtil.initTableMapperJob(list,MyMapper.class,ExamineDetailKey.class,ExamineDetailValue.class,job);
//注册Key/Value类型为Text
job.setOutputKeyClass(NullWritable.class);
job.setOutputValueClass(Text.class);
//TableMapReduceUtil.initTableMapperJob方法中已经绑定Mapper输出类型,下面方法可忽略
//job.setMapOutputKeyClass(ExamineDetailKey.class);
//job.setMapOutputValueClass(ExamineDetailValue.class);
//注册Mapper及Reducer处理类
job.setMapperClass(MyMapper.class);
job.setReducerClass(MyReducer.class);
//设置SortComparator和GroupComarartor
job.setSortComparatorClass(ExamineDetailSortComparator.class);
job.setGroupingComparatorClass(ExamineDetailGroupComparator.class);
//输入输出数据格式化类型为TextInputFormat/TableOutputFormat
job.setInputFormatClass(MultiTableInputFormat.class);
job.setOutputFormatClass(TextOutputFormat.class);
//伪分布式情况下不设置时默认为1
//job.setNumReduceTasks(1);
//注册自定义Partitional类
//job.setPartitionerClass(MyPatitional.class);
//获取命令参数
String[] args=new GenericOptionsParser(getConf(),arg0).getRemainingArgs();
FileOutputFormat.setOutputPath(job,new Path(args[0]));
boolean status=job.waitForCompletion(true);
if(status)
return 0;
else
return 1;
} public static void main(String[] args) throws Exception{
Configuration conf=new Configuration();
ToolRunner.run(new MrToHBaseExample(), args);
}
}
运行结果
本章小结
本文的主要目的是介绍 HBase 的运行原理,介绍 HBase 的常用 Java API 开发实例,讲解 HBase 与 MR 之间的关系。在现今大数据年代,了解NoSQL的开发可以说是技术人员入门的必修课程,希望本文对各位的工作学习有所帮助。
由于本人时间紧迫,文章中有所缺漏的地方敬请点评。
对 JAVA 开发有兴趣的朋友欢迎加入QQ群: 共同探讨!
对 .NET 开发有兴趣的朋友欢迎加入QQ群:230564952 共同探讨 !
Hadoop 综合揭秘
MapReduce 基础编程(介绍 Combine、Partitioner、WritableComparable、WritableComparator 使用方式)
作者:风尘浪子
https://www.cnblogs.com/leslies2/p/9530378.html
原创作品,转载时请注明作者及出处
Hadoop 综合揭秘——HBase的原理与应用的更多相关文章
- Hadoop 综合揭秘——MapReduce 基础编程(介绍 Combine、Partitioner、WritableComparable、WritableComparator 使用方式)
前言 本文主要介绍 MapReduce 的原理及开发,讲解如何利用 Combine.Partitioner.WritableComparator等组件对数据进行排序筛选聚合分组的功能.由于文章是针对开 ...
- hadoop学习第七天-HBase的原理、安装、shell命令
一. hbase的原理知识 1. hbase介绍 hbase是hadoop的一个重要成员,主要用于存储结构化数据,构建在hdfs之上的分布式存储系统,它主要通过横向扩展,通用不断增加廉价服务器增加计算 ...
- Hadoop生态圈-Zookeeper的工作原理分析
Hadoop生态圈-Zookeeper的工作原理分析 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 无论是是Kafka集群,还是producer和consumer都依赖于Zoo ...
- 分享知识-快乐自己:揭秘HBase
揭秘HBase: 一):大数据(hadoop)初始化环境搭建 二):大数据(hadoop)环境搭建 三):运行wordcount案例 四):揭秘HDFS 五):揭秘MapReduce 六):揭秘HBa ...
- 大数据应用期末总评Hadoop综合大作业
作业要求来源于:https://edu.cnblogs.com/campus/gzcc/GZCC-16SE2/homework/3339 1.将爬虫大作业产生的csv文件上传到HDFS 此次作业选取的 ...
- 【HBase】二、HBase实现原理及系统架构
整个Hadoop生态中大量使用了master-slave的主从式架构,如同HDFS中的namenode和datanode,MapReduce中的JobTracker和TaskTracker,YAR ...
- Hbase概念原理扫盲
一.Hbase简介 1.什么是Hbase Hbase的原型是google的BigTable论文,收到了该论文思想的启发,目前作为hadoop的子项目来开发维护,用于支持结构化的数据存储. Hbase是 ...
- HBase 底层原理详解(深度好文,建议收藏)
HBase简介 HBase 是一个分布式的.面向列的开源数据库.建立在 HDFS 之上.Hbase的名字的来源是 Hadoop database,即 Hadoop 数据库.HBase 的计算和存储能力 ...
- C#综合揭秘——通过修改注册表建立Windows自定义协议
引言 本文主要介绍注册表的概念与其相关根项的功能,以及浏览器如何通过连接调用自定义协议并与客户端进行数据通信.文中讲及如何通过C#程序.手动修改.安装项目等不同方式对注册表进行修改.其中通过安装项目对 ...
随机推荐
- 学习excel的使用技巧三快捷键和思路
快捷键 CRTL+回车 是多行执行 思路 关于公式 在空白出 写= 即开始写公式 excel第一行 就是行标 比如 A1 就是excel 表格中第一个 比如来个乘法 =A1*12+b1*13 求和更简 ...
- Oracle,cast函数
cast(要转换的值 AS 转换的类型): 问题:' ' as FSubBillNo 若用此法 oracle 默认字段类型为char型 且字段长度度为输入空格的长度,会导致字符串 ...
- 【FZSZ2017暑假提高组Day1】华容道游戏
[问题描述] 华容道是一种有趣的滑块游戏,大概是下面这个样子的. 游戏局面由一个2*2的曹操滑块,五个2*1的蜀将滑块(横竖是不定的).四个1*1的小兵滑块以及两个空的位置构成,玩家需要利用空的位子移 ...
- 微信小程序开发之保留小数(toFixed) 四舍五入 获取整数 string转int
https://blog.csdn.net/qq_31383345/article/details/52961767
- cleos
[cleos] 1.在.bashrc中加入以下代码,方便直接使用 cleos,7777是nodeos端口,5555是keosd端口. alias cleos='docker exec -it eosi ...
- zabbix3.4 监控ESXI6.7
一.ESXI WEB界面 管理--高级配置启用 键 Config.HostAgent.plugins.solo.enableMob 访问:https://10.81.1.219/mob/?moid=h ...
- 通过启动函数定位main()函数
如下,通过vc6.0编写一个hello world程序.尝试结合汇编代码.分析启动函数找到main函数. 在printf(xxx)插入断点,调试执行.如下,在堆栈窗口中可见main()下的一个 ...
- GitHub下载子目录
背景 整个Github目录太大,国内网速不好,且其他部分也不需要. 方法 把 /tree/master 改成 trunk. svn checkout https://github.com/lodash ...
- Go学习笔记:Win7+LiteIDE+Go+Beego 环境搭建
安装过程比较简单 1.安装go语言环境: 2.安装git: 3.git bash 安装beego,输入“go get github.com/astaxie/beego”,等待一会儿,在D盘的 ...
- springboot + @KafkaListener 手动提交及消费能力优化
转载 https://blog.csdn.net/asd5629626/article/details/82776450 https://blog.csdn.net/asd5629626/artic ...