默认情况下,部署在 MapReduce 集群中的 MapReduce jobs 没有权限访问$HBASE_CONF_DIR路径下的 HBase 配置 或者 HBase classes.

通过以下方式可以为 MapReduce jobs 配置权限.

增加 hbase-site.xml$HADOOP_HOME/conf
然后将 HBase jars 添加到 $HADOOP_HOME/lib 目录下
最后需要将这些变更拷贝到 Hadoop 集群中所有服务上.

或者

编辑 $HADOOP_HOME/conf/hadoop-env.sh 将 HBase 依赖添加到 HADOOP_CLASSPATH.

以上配置均不推荐,因为它会让 Hadoop 安装 HBase 的依赖,并且需要重启 Hadoop 集群才能使用 HBase 中的数据.

推荐的方式是 HBase 使用HADOOP_CLASSPATH or -libjars添加其依赖的 jar 包.

从 HBase 0.90.x,HBase 添加依赖 jar 包到任务自身配置中. 依赖项只需要在本地CLASSPATH可用,然后被打包部署到 MapReduce 集群的 fat job jar 中.一种取巧的方式是传递全量的 HBase classpath(即 hbase,独立的 jars 还有配置)到 mapreduce job 运行器中令 hbase 工具从全量的 classpath 挑选依赖最终配置到 MapReduce job 的配置中(可以查看源码实现TableMapReduceUtil#addDependencyJars(org.apache.hadoop.mapreduce.Job)).

下面的例子是在表usertable上运行的 HBase 的 MapReduce 任务: 表行数统计任务RowCounter.设置在 MapReduce 上下文运行需要的 hbase jars 以及配置文件如 hbase-site.xml 到 HADOOP_CLASSPATH. 一定要确保使用了与你的系统相对应的 HBase Jar.替换以下命令中的 VERSION 字段为本地 HBASE 版本. 反引号(`)使 shell 执行子命令,将hbase classpath的输出设置为HADOOP_CLASSPATH. 这个例子需要在 Bash-compatible 执行.

$ HADOOP_CLASSPATH=`${HBASE_HOME}/bin/hbase classpath` \
${HADOOP_HOME}/bin/hadoop jar ${HBASE_HOME}/lib/hbase-mapreduce-VERSION.jar \
org.apache.hadoop.hbase.mapreduce.RowCounter usertable

以上命令将启动一个运行在本地配置指定的 hbase 集群的 mapreduce 作业,用来统计表行数.这个集群也是 Hadoop 配置指定的.

hbase-mapreduce.jar 核心是一个驱动,罗列了 HBASE 装载的一些基础的 MapReduce 任务.例如,假设你安装的是2.0.0-SNAPSHOT版本:

$ HADOOP_CLASSPATH=`${HBASE_HOME}/bin/hbase classpath` \
  ${HADOOP_HOME}/bin/hadoop jar ${HBASE_HOME}/lib/hbase-mapreduce-2.0.0-SNAPSHOT.jar
An example program must be given as the first argument.
Valid program names are:
  CellCounter: Count cells in HBase table.
  WALPlayer: Replay WAL files.
  completebulkload: Complete a bulk data load.
  copytable: Export a table from local cluster to peer cluster.
  export: Write table data to HDFS.
  exportsnapshot: Export the specific snapshot to a given FileSystem.
  import: Import data written by Export.
  importtsv: Import data in TSV format.
  rowcounter: Count rows in HBase table.
  verifyrep: Compare the data from tables in two different clusters. WARNING: It doesn't work for incrementColumnValues'd cells since the timestamp is changed after being appended to the log.

您可以使用上面列出的 MapReduce 任务的简写采用以下命令重新执行表行数统计任务(同样,假设安装的 HBASE 是2.0.0-SNAPSHOT版本):

$ HADOOP_CLASSPATH=`${HBASE_HOME}/bin/hbase classpath` \
  ${HADOOP_HOME}/bin/hadoop jar ${HBASE_HOME}/lib/hbase-mapreduce-2.0.0-SNAPSHOT.jar \
  rowcounter usertable

您可能发现了hbase mapredcp工具的输出; 它列出了在 hbase 运行基础 mapreduce 作业所需的最小 jar 文件集合(不包括配置,如果希望 MapReduce 作业能准确找到目标集群,则可能需要添加些配置)。 一旦你开始做任何实质性的事情,你还需要添加额外依赖,这些依赖需在运行hbase mapredcp时通过传递系统属性-Dtmpjars来指定。

对于那些没有打包依赖的 jobs 或者直接调用TableMapReduceUtil#addDependencyJars,则下面的命令格式就非常必要了:

$ HADOOP_CLASSPATH=`${HBASE_HOME}/bin/hbase mapredcp`:${HBASE_HOME}/conf hadoop jar MyApp.jar MyJobMainClass -libjars $(${HBASE_HOME}/bin/hbase mapredcp | tr ':' ',') ...

如果您是在 HBase 的构建地址而不是安装地址执行以上示例,您会遇到如下错误:

java.lang.RuntimeException: java.lang.ClassNotFoundException: org.apache.hadoop.hbase.mapreduce.RowCounter$RowCounterMapper

如果出现了以上问题,请参照以下命令修改,它从构建环境的 target/ 目录下使用 HBASE jars

$ HADOOP_CLASSPATH=${HBASE_BUILD_HOME}/hbase-mapreduce/target/hbase-mapreduce-VERSION-SNAPSHOT.jar:`${HBASE_BUILD_HOME}/bin/hbase classpath` ${HADOOP_HOME}/bin/hadoop jar ${HBASE_BUILD_HOME}/hbase-mapreduce/target/hbase-mapreduce-VERSION-SNAPSHOT.jar rowcounter usertable

Notice to MapReduce users of HBase between 0.96.1 and 0.98.4 一些 HBase MapReduce 任务启动失败,会出现以下类似的异常:

Exception in thread "main" java.lang.IllegalAccessError: class
    com.google.protobuf.ZeroCopyLiteralByteString cannot access its superclass
    com.google.protobuf.LiteralByteString
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:792)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:449)
    at java.net.URLClassLoader.access$100(URLClassLoader.java:71)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at
    org.apache.hadoop.hbase.protobuf.ProtobufUtil.toScan(ProtobufUtil.java:818)
    at
    org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil.convertScanToString(TableMapReduceUtil.java:433)
    at
    org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil.initTableMapperJob(TableMapReduceUtil.java:186)
    at
    org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil.initTableMapperJob(TableMapReduceUtil.java:147)
    at
    org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil.initTableMapperJob(TableMapReduceUtil.java:270)
    at
    org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil.initTableMapperJob(TableMapReduceUtil.java:100)
...

这是HBASE-9867无意间增加了一个类加载器依赖引入的优化.

这个影响使用-libjars 和 'fat jar '的任务,他们将运行时依赖放在在lib路径下.

为了满足新类加载器需要,hbase-protocol.jar必须包含在 Hadoop 的 环境变量下.可通过HBase, MapReduce, and the CLASSPATH查阅解决 一些 classpath 错误的推荐解决方法. The following is included for historical purposes.

在 Hadoop 的 lib 目录里通过系统连接或者直接拷贝方式引入hbase-protocol.jar,可以系统范围内解决 classpath 问题.

这也可以在每个作业启动的基础上实现,方法是在作业提交时将其(hbase-protocol.jar)包含在HADOOP_CLASSPATH环境变量中。启动时打包其依赖项,以下所有三个作业启动命令都满足此要求

$ HADOOP_CLASSPATH=/path/to/hbase-protocol.jar:/path/to/hbase/conf hadoop jar MyJob.jar MyJobMainClass
$ HADOOP_CLASSPATH=$(hbase mapredcp):/path/to/hbase/conf hadoop jar MyJob.jar MyJobMainClass
$ HADOOP_CLASSPATH=$(hbase classpath) hadoop jar MyJob.jar MyJobMainClass

下面的命令对于那些不打包自己依赖的 Jar 文件很有必要:

$ HADOOP_CLASSPATH=$(hbase mapredcp):/etc/hbase/conf hadoop jar MyApp.jar MyJobMainClass -libjars $(hbase mapredcp | tr ':' ',') ...

可以查阅 HBASE-10304进行更深入的讨论.