【HBase】20-HBase与MapReduce集成

当用户使用 MapReduce作业从表中读取数据时,实质上是使用 TableInputFormat取得数据。它符合 MapReduce框架,并重载了公有方法 getSplits()和 createRecordreader()。
在一个作业被执行前,框架调用 netSplit(来决定如何划分块(chunk),从而由块数目决定映射任务的数目。
对于 HBase来说, TableInputFormat基于用户提供的Scan实例取得所需要的表信息,并且按 region来划分边界。由于它不能直接地预测到可选过滤器的执行效果,所以它简单地使用起止键来确定 region。拆分块的数目与起止键之间的 region数目相等。如果用户没有设置这两个键,则所有 region都被包含在其中。
作业启动时,框架会按拆分的数目调用 createRecordReader(),并返回与当前块对应的TableRecordReader实例。换句话说,每个 TableRecordReader实例处理一个对应的regoin,读取并遍历一个 region的所有行每一个拆分也包含对应 region所在服务器的信息。正是这个信息能使 HBase上的MapReduce作业本地化执行:框架会比较 region对应的服务器名,如果有任务 tracker在对应服务器上运行,它会挑选这个服务器来执行作业。因为 region服务器与 Data Node运行在一个节点,所以扫描会从本地磁盘读取文件。
当在 HBase上运行 MapReduce时,推荐用户关闭预测执行模式。这样会增、加region和服务器的负载,同时与本地化运行相违背:预测作业在一个不同的机器上运行,因此没有本地 region服务器,这会增加网络带宽开销。

在 HBase之上的 MapReduce

1、准备

当运行 MapReduce业所需库中的类不是绑定在 Hadoop或 MapReduce框架中时,用户就必须确保这些库在作业执行之前已经可用。用户一般有两个选择:在所有的任务节点上准备静态的库,或直接提供作业所需的所有库。

1.1、静态配置

对于经常使用的库来说,最好将这些JAR文件安装在 MapReduce任务 tracker的本地,可以通过以下步骤来完成
1.1.1、把JAR文件复制到所有节点的常用路径中
1.1.2、将这些JAR文件的完整路径写入 hadoop-enνsh配置文件中,按照如下方式编辑 HADOOP_CLASSPATH变量:

1.1.3、重启所有的任务 tracker使变更生效。
显然这个技术是纯静态的,并且每次变更(即增加新库)都需要重启任务 tracker。如果需要添加 HBase的支持,至少需要增加 HBase和 ZooKeeper的JAR文件。按照如下方式编辑 hadoop-env.sh:

这里的前提是假设用户已经定义了两个$XYZ_HOME环境变量,并在环境变量中指出软件的安装路径。

1.2、动态配置

在一些情况下,用户希望每个他们运行的作业可以使用不同的库,或者在作业运行时与MapReduce集成能够更新库,这种情况下动态配置就非常有用了。
针对这种情况, Hadoop有一个特殊的功能:它可以读取操作目录中/lib目录下包含的所有库的JAR文件。用户可以使用此功能生成所谓的“胖”JAR文件,因为它们传输的不仅仅是实际的作业代码,还有所有需要的库。这意味着作业JAR文件更大,但从另一方面来说,其中已经自已包含了处理作业的完整的代码。
另一种动态提供必备库的方式是 Hadoop MapReduce框架的 libjars功能。当用户使用GenericoptionsParser创建一个 MapReduce作业时,可以得到libjar参数提供的支持。
HADOOP_CLASSPATH需要使用命令行。 Driver类在启动时需要访问 HBase和 Zookeeper类,需要按照如下方式添加libjars参数:

最终, HBase辅助类 TableMapReduceUti1提供了可以帮助用户在作业中通过代码动态添加依赖的JAR和配置文件的方法:
static void addDependencyJars(Job job) throws IOException;
static void addDependencyJars(Configuration conf, Class.. classes) throws IOException
用户过去都使用后一种功能来添加所需的 HBase、 ZooKeeper和作业类:
addDependencyJars (job.getConfiguration(),org.apache.zookeeper.ZooKeeper.class,
job.getMapoutputKeyClass(),
job.getMapoutput ValueClass(),
job.getInput Formatclass(),
job.getoutputKeyclass(),
job.getoutputValueClass(),
job.getoutputFormatclass(),
job.getPartitionerclass(),
job.getcombinerClass())

这里首先调用 addDependencyJars()方法添加作业和必要的类,包括输入输出格式和键值的类型等。第二个调用添加了Google Guava JAR,这个JAR需求程度在其他库之上。需要注意的是,这个方法并不需要用户指定实际的JAR文件,它直接使用Java的类加载器进行加载但有可能会找到相同的JAR文件,不过在这里这是无关紧要的,重要的是你可以在Java CLASSPATH中访问该类;否则,这些调用最终会失败,并返回一个 ClassNotFoundException错误,类似于之前你看到过的信息。用户在一个没有准备好的 Hadoop起动中,至少需要填加 HADOOP_CLASSPATH到命令行中。
你将选择哪种方法?胖JAR的优势在于,其包含 HBase启动过程所有作业所需的库,而另一种方式则至少需要准备 classpath。

2、数据流向

随后,我们将使用 MapReduce作业作为过程的一部分,该作业可以使用 HBase进行读取或写入。第一个例子是使用 HBase作为数据流向,这个例子是在 TableoutputFormat:类的帮助下实现的。
 MapReduce作业从一个文件中读取数据井写入 HBase表

  1. 为后续的使用定义一个作业名。
  2. 定义mapr类,继承自 Hadoop已有的类。
  3. map()函数将 Input Fort提供的键值对转化为了 OutputFormat需要的类型。
  4. 行键是经过MD5散列之后随机生成的键值。
  5. 存储原始数据到给定的表中的一列
  6. 使用 Apache Commons CLI类解析命令行参数。这些已经是HBae的一部分,因此用户可以很方便地处理作业的参数
  7. 传递命令行参数到解析器中,并优先解析“-Dxyz”属性。
  8. 使用特定的类定义作业。
  9. 这是一个只包含map阶段的作业,框架会直接跳过 reduce阶段。

用户在代码的main()函数中进行了设置,在运行 MapReduce作业之前,首先解析命令行以获取目标表名、目标列和输入文件的名称。虽然这里可以硬编码,但是最好还是写一段支持可配置的代码。
下一步是构建作业实例,通过命令行和已确定的参数(如类名)指定变量的细节。其中的一个细节是指定 mapper类,将其设置为ImportMapper。这个类与main类在同一个源代码文件中,并定义了作业在map阶段要完成的逻辑。
Main()函数的代码指定了输出格式的类,即前面描述过的 TableoutputFormat类。这个类由HBae提供,并能够方便地向一张表中写入数据,键的类型被该类隐式地设置为 ImmutableBytesWritable类,值被隐式地设置为 Writable的类。
用户在执行作业前,最好先创建一个目标表,例如,使用 HBase shel来创建目标表:

一旦准备好了表,用户就可以运行该作业了:

第一个命令是hadoop dfs-put,该命令将样本数据集合存储到HDFS的用户目录中。第二个命令则可以运行这个作业,作业的完成需要一段时间。使用默认的 TextinputFormat类读取数据,这个类是由 Hadoop.及其 MapReduce框架提供的。这个输入格式能够读取用换行符换行的文本文件。每读取一行都会调用一次指定的 mapper类的map()方法,这将触发 ImportMapper.map()函数。
ImportMapper类定义了两个方法,这两个方法重载了父类 Mapper,方法名均相同。
ImportMapper类的重载的 setup()方法只会在框架初始化该类时调用一次。在这个例子中,它主要被用于将指定的列信息解析为列族和列限定符。
这个类中的map()才是执行实际工作的实体,它会被输入的文本文件中的每一行调用,每一行都包含了一个JSON格式的记录。这段代码会使用一行数据的MD5散列值来创建一个 HBase行键,然后使用 HBase提供的名为data:json的列存储一行的内容。
这个例子通过 TableoutputFormat类使用了隐式的写缓冲区。调用 context.write()方法时,该方法内部会传入给定的Put实例并调用 table.put()。在作业结束前, TableOutputFormat会主动调用 flushCommits()以保存仍就驻留在写缓冲区的数据。
map()方法可以通过写P吐t实例来存储输入数据,用户也可以通过写 Delete实例来删除目标表中的数据,这就是设置作业输出键的类型为 Writable接口,而并非显式的Put类的原因TableoutputFormat当前仅能处理put与 Delete实例,将消息设置为除Put或 Delete之外的实例都会导致抛出一个 IOException异常。
最后,请注意作业在没有 reduce阶段的情况下,map阶段是怎样工作的。这是相当典型的 HBase与 MapReduce作业结合:由于数据是存储在排序表中的,并且每行数据都拥有唯一的行键,用户可以在流程中避免更消耗的sort、 shuffle和 reduce阶段。

3、数据源

将数据导入到表中之后,我们可以使用其中包含的数据来解析JSON记录,并从中提取信息。这里完全使用了 TableInputFormat类,恰好对应了TableoutputFormat。在 MapReduce处理过程中,它以一张表为输入。
MapReduce作业读取已导入的数据并解析

  1. 继承已提供的 TableMapper类,设置用户自己的输出键值类型
  2. 解析JSON数据,提取编辑用户,并统计发生次数。
  3. 拓展一个 Hadoop Reducer类,并指定适当的类型。
  4. 统计发生次数,并计算其总和。
  5. 创建并配置一个scan实例。
  6. 使用提供的库来设置表的map阶段。
  7. 使用常规的 Hadoop语法来配置 reduce阶段。

这个作业运行了一个完整的 MapReduce过程,map阶段主要负责从源表中读取JSON数据, reduce阶段主要负责对每个用户做聚合统计。这个例子与 Hadoop提供的 Wordcount例子非常相似, mapper负责记录统计值ONE, reducer负责对每个键做聚合操作,操作
的键是编辑用户(Author)。在命令行上按照如下方式执行作业:

这个例子的结果是包含每个编辑用户的统计列表,并且可以通过命令行方式进行访问,例如, hadoop dfs -text命令:

hadoop dfs -text analyzel/part-r-00000

这个例子展示了怎样使用 TableMapReduceUtil类,通过使用 TableMapReduceUtil的静态方法可以快速配置一个作业,并附带必要的类。由于作业需要 reduce阶段,main()函数代码添加了必要的 Reducer类,并在没有其他特殊指定(本例中是TextoutputFormat类)的情况下隐式地使用默认值。
显然,这是一个非常简单的例子,实际上用户不得不执行更多的分析处理。但即便如此,例子所展现的模式与之前仍类似:用户从表中读取数据,提取必要信息,最终将结果输出到一个特定目标。

4、数据源与数据流向

一个 MapReduce作业中的源和目标都可以是 HBase表,但也有可能在个作业中同时使用 HBase作为输入和输出。换句话说,第三类的 MapReduce模板同时使用HBase表作为输入与输出。这里需要将 TableInputFormat和TableOutputFormat类设置到各自的作业配置项中。
MapReduce作业解析每行数据为若干列

  1. 将顶层的 JJSON记录的键存储为列,其值的集合存储为列值
  2. 在 mapper的配置实例中存储列族以备后用。
  3. 使用通用方法设置map阶段的细节
  4. 配置一个 reducer实例用于存储解析后的数据。

这个例子使用通用方法配置map阶段和 reduce阶段,并用到了 ParseMapper类,这个类用于从一行原始sON数据中提取信息,还使用了 IdentityTableReducer类将数据存储到目标表。请注意,源表与目标表可以是相同的。用户可以通过以下命令行提交作业:

这个百分比表示map阶段与 reduce阶段已经完成,并且整个作业也已经完成。使用IdentityTableReducer类存储数据并不是必需的,实际上,只需要在代码中增加一行就可以让作业只拥有map阶段。
MapReduce作业解析每行数据为若干列(仅限map阶段)

用户可以从命令行展示的正在运行的作业中看出 reduce阶段已经被跳过了:

reduce阶段一直显示为0%,直到作业完成。用户可以使用 Hadoop MapReduce的UI来确认已执行的作业确实没有 reduce阶段。跳过 reduce阶段的优点是可以更快地完成作业,这是由于框架不需要增加额外的数据处理工作。
这两个不同的 ParseJson作业的作用相同,用户可以通过 HBase Shel看其结果(由于展示空间的缘故,省略重复的行键):

这个导入过程利用了 HBase支持任意列名的特点:JsON的键被转化为列限定符,并形成了新的一列。

5、自定义处理

用户并非必须使用HBae提供的类来读写表数据,实际上这些类非常轻量级并且仅起到了辅助类的作用。改变了前面的代码,以将已解析的JSON数据拆分到两张不同的目标表中,链接和它对应的值存储到了一张名为 linkable的独立的表中,而其他列则存储到了名为 infotable的表中。
MapReduce作业解析每行数据为若干列

  1. 在setup()方法中创建并配置两张目标表。
  2. 当任务完成时,刷新所有挂起的提交。
  3. 保存已解析的数据到两张不同的表中。
  4. 在mapper的配置实例中存储表名以备后用。
  5. 设置框架会忽略的输出格式。

用户需要创建两张表,请使用 HBase Shell进行以下操作:
hbase(main): 001: 0> create 'infotable','data'
hbase(main): 002: 0> create 'linktable','data'
在当前例子中,这两张表将会被用作目标存储表通过如下命令行执行作业,并忽略输出内容:

到目前为止,这类似于之前解析JSON的示例,所不同的是结果表及其内容。用户可以在作业完成后使用 HBase Shel和扫描命令罗列内容。用户可以从中看出链接(link)表只包含了链接,而信息(info)表则包含了原JSON数据的剩余字段。
用户可以通过自行编写的 MapReduce代码来控制作业的执行内容。例如,用户可以在存储组合结果的不同表中查询数据。用户从哪里读数据和向哪里写数据是不受限制的。 HBase提供的类是辅助类,或多或少地服务了大量的用例。如果用户发现了它们功能上的限制,可以简单地拓展它们,或直接通过 MapReduce的AP实现可以以任何形式访问 HBase的代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值