[英]Hadoop - constructor args for mapper
有没有办法在Hadoop中为Mapper提供构造函数args? 可能通过一些包装创造就业的图书馆?
这是我的情景:
public class HadoopTest {
// Extractor turns a line into a "feature"
public static interface Extractor {
public String extract(String s);
}
// A concrete Extractor, configurable with a constructor parameter
public static class PrefixExtractor implements Extractor {
private int endIndex;
public PrefixExtractor(int endIndex) { this.endIndex = endIndex; }
public String extract(String s) { return s.substring(0, this.endIndex); }
}
public static class Map extends Mapper<Object, Text, Text, Text> {
private Extractor extractor;
// Constructor configures the extractor
public Map(Extractor extractor) { this.extractor = extractor; }
public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
String feature = extractor.extract(value.toString());
context.write(new Text(feature), new Text(value.toString()));
}
}
public static class Reduce extends Reducer<Text, Text, Text, Text> {
public void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
for (Text val : values) context.write(key, val);
}
}
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = new Job(conf, "test");
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
job.setMapperClass(Map.class);
job.setReducerClass(Reduce.class);
job.setInputFormatClass(TextInputFormat.class);
job.setOutputFormatClass(TextOutputFormat.class);
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
job.waitForCompletion(true);
}
}
应该清楚,因为Mapper仅作为类引用( Map.class
)提供给Configuration
,所以Hadoop无法传递构造函数参数并配置特定的Extractor。
有一些Hadoop包装框架就像Scoobi,Crunch,Scrunch(可能还有更多我不知道的)似乎有这种能力,但我不知道他们是如何实现的。 编辑:在与Scoobi合作之后,我发现我对此有些不对劲。 如果在“映射器”中使用外部定义的对象,则Scoobi要求它是可序列化的,并且如果不是,则会在运行时进行投诉。 所以也许正确的方法就是让我的Extractor
可以在Mapper的设置方法中进行序列化和反序列化......
此外,我实际上在Scala工作,所以非常欢迎基于Scala的解决方案(如果不鼓励!)
我建议通过您正在创建的Configuration
对象告诉您的映射器使用哪个提取器。 映射器在其setup
方法( context.getConfiguration()
)中接收配置。 看起来您不能将对象放在配置中,因为它通常是从XML文件或命令行构造的,但您可以设置枚举值并让映射器自己构造其提取器。 在创建映射器之后定制映射器并不是很漂亮,但这就是我对API的解释。
在提交作业时设置实现类名
Configuration conf = new Configuration();
conf.set("PrefixExtractorClass", "com.my.class.ThreePrefixExtractor");
或者使用命令行中的-D选项设置PrefixExtractorClass选项。
下面是mapper中的实现
Extractor extractor = null;
protected void setup(Context context) throws IOException,
InterruptedException
{
try {
Configuration conf = context.getConfiguration();
String className = conf.get("PrefixExtractorClass");
extractor = Class.forName(className);
} Catch (ClassNotFoundException e) {
//handle the exception
}
}
现在使用map函数中所需的extractor
对象。
包含com.my.class.ThreePrefixExtractor
类的jar应该分发给所有节点。 以下是来自Cloudera的一篇文章 ,介绍了不同的方法。
在上面的例子中, com.my.class.ThreePrefixExtractor
应该扩展Extractor
类。
使用这种方法可以使映射器实现成为通用的。 这是大多数框架采用的方法(使用Class.forName)来实现可实现特定接口的可插入组件。
我仍在寻找好的答案,但我提出的一个(非理想的)解决方案是使用继承而不是组合,将Map转换为Extractor抽象类。 然后它可以一直子类化以包含所有构造函数args(如下所示)。
public static abstract class Extractor extends Mapper<Object, Text, Text, Text> {
public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
String feature = extract(value.toString());
context.write(new Text(feature), new Text(value.toString()));
}
public abstract String extract(String s);
}
public static abstract class PrefixExtractor extends Extractor {
public String extract(String s) { return s.substring(0, getEndIndex()); }
public abstract int getEndIndex();
}
public static class ThreePrefixExtractor extends PrefixExtractor {
public int getEndIndex() { return 3; }
}
然而,这并不是那么好,我真的觉得必须有一种方法以正确的方式做到这一点。
(我把它从原来的问题中解决了,使事情变得不那么混乱。)
到目前为止,我提出的最佳解决方案是将我想要的对象的序列化版本传递给Mapper,并使用反射在运行时构造对象。
所以,主要方法会说:
conf.set("ExtractorConstructor", "dicta03.hw4.PrefixExtractor(3)");
然后,在Mapper中我们使用辅助函数construct
(在下面定义)并且可以说:
public void setup(Context context) {
try {
String constructor = context.getConfiguration().get("ExtractorConstructor");
this.extractor = (Extractor) construct(constructor);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
使用反射在运行时从String递归构造对象的construct
定义:
public static Object construct(String s) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
if (s.matches("^[A-Za-z0-9.#]+\\(.*\\)$")) {
Class cls = null;
List<Object> argList = new ArrayList<Object>();
int parenCount = 0;
boolean quoted = false;
boolean escaped = false;
int argStart = -1;
for (int i = 0; i < s.length(); i++) {
if (escaped) {
escaped = false;
} else if (s.charAt(i) == '\\') {
escaped = true;
} else if (s.charAt(i) == '"') {
quoted = true;
} else if (!quoted) {
if (s.charAt(i) == '(') {
if (cls == null)
cls = Class.forName(s.substring(0, i));
parenCount++;
argStart = i + 1;
} else if (s.charAt(i) == ')') {
if (parenCount == 1)
argList.add(construct(s.substring(argStart, i)));
parenCount--;
} else if (s.charAt(i) == ',') {
if (parenCount == 1) {
argList.add(construct(s.substring(argStart, i)));
argStart = i + 1;
}
}
}
}
Object[] args = new Object[argList.size()];
Class[] argTypes = new Class[argList.size()];
for (int i = 0; i < argList.size(); i++) {
argTypes[i] = argList.get(i).getClass();
args[i] = argList.get(i);
}
Constructor constructor = cls.getConstructor(argTypes);
return constructor.newInstance(args);
} else if (s.matches("^\".*\"$")) {
return s.substring(1, s.length() - 1);
} else if (s.matches("^\\d+$")) {
return Integer.parseInt(s);
} else {
throw new RuntimeException("Cannot construct " + s);
}
}
(这可能不是最强大的解析器,但可以轻松扩展以覆盖更多类型的对象。)
对于另一个类似的解决方案,请看看:
我们如何做到这一点。 它使用反射来构建一些java-source-code,这些代码在运行时会创建一个相同的对象图。 然后我们编译该源(使用javassist)并包含在发送到集群的jar中。
它非常强大,如果你想要它,它处理循环对象图和所有特殊情况(有一些很少)的东西。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.