DistRaidNode的实现
hdfs-raid是添加了冗余编码的hadoop分支模块,本篇研究分布式raid模式的实现过程
RaidNode是raid模块的核心类,有Local和Distributed两种实现。
分布式RaidNode的实现
当TriggerMonitor发现有文件需要编码时将调用RaidNode子类对象的raidFiles函数。DistRaidNode类中raidFiles函数的重写如下:
DistRaid dr = new DistRaid(conf);
dr.addRaidPaths(info, paths);
boolean started = dr.startDistRaid();
if (started) {
jobMonitor.monitorJob(info.getName(), dr);
}
DistRaid是分布式raid任务的模型类,startDistRaid()函数将会向集群提交mapreduce任务,执行分布式raid操作。jobMonitor对象负责周期性的监视DistRaid任务的执行状态。
raid任务的创建与提交
框架通过创建和提交mapreduce任务来发布分布式的raid任务。整次任务由DistRaid类负责定义和创建,简化的DistRaid类示意如下(‘c’代表内部类):
由于该类负责实现任务的提交,所以编写了分别继承InputFormat
和Mapper
的内部类。这两个类是描述mapreduce过程所必要的类。以下列举了DistRaid对象创建过程中发进行的操作:
//DistRaid对象的构造过程
public DistRaid(Configuration conf) {
setConf(createJobConf(conf));
}
private static JobConf createJobConf(Configuration conf) {
JobConf jobconf = new JobConf(conf, DistRaid.class);
jobName = NAME + " " + dateForm.format(new Date(RaidNode.now()));
jobconf.setUser(RaidNode.JOBUSER);
jobconf.setJobName(jobName);
jobconf.setMapSpeculativeExecution(false);
RaidUtils.parseAndSetOptions(jobconf, SCHEDULER_OPTION_LABEL);
jobconf.setJarByClass(DistRaid.class);
jobconf.setInputFormat(DistRaidInputFormat.class);
jobconf.setOutputKeyClass(Text.class);
jobconf.setOutputValueClass(Text.class);
jobconf.setMapperClass(DistRaidMapper.class);
jobconf.setNumReduceTasks(0);
return jobconf;
}
public void setConf(Configuration conf) {
if (jobconf != conf) {
jobconf = conf instanceof JobConf ? (JobConf) conf : new JobConf(conf);
}
}
简而言之,创建DistRaid对象的过程中创建了jobConf对象。jobConf是用来描述一个mapreduce任务的对象,他指定了一次分布式job的名称、ID、输入格式类、map类等等最终要的信息。再来研究一下startDistRaid()
函数:
public boolean startDistRaid() throws IOException {
assert(raidPolicyPathPairList.size() > 0);
if (setup()) {
this.jobClient = new JobClient(jobconf);
this.runningJob = this.jobClient.submitJob(jobconf);
LOG.info("Job Started: " + runningJob.getID());
this.startTime = System.currentTimeMillis();
return true;
}
return false;
}
//setup()函数核心核心片段
SequenceFile.Writer opWriter = null;
try {
opWriter = SequenceFile.createWriter(fs, jobconf, opList, Text.class,
PolicyInfo.class, SequenceFile.CompressionType.NONE);
for (RaidPolicyPathPair p : raidPolicyPathPairList) {
java.util.Collections.shuffle(p.srcPaths);
for (FileStatus st : p.srcPaths) {
opWriter.append(new Text(st.getPath().toString()), p.policy);
opCount++;
if (++synCount > SYNC_FILE_MAX) {
opWriter.sync();
synCount = 0;
}
}
}
}...
创建好了DistRaid对象之后,startDistRaid()调用了setup()函数对任务相关的参数进行初始化,指定raid的文件路径和policy列表。setup()函数对raidPolicyPathPairList列表遍历,将每一个需要raid的文件的路径和其raid策略一一对应的存储到一个sequenceFile中,供后续mapreduce框架进行任务分割,简而言之就是创建了任务列表。接着创建了用来提交任务的jobClient对象提交了jobConf所描述的任务并更新日志。
raid任务的分割
一次raid的‘大任务’是如何分割成一个个的‘小任务’并在map过程中一一解决的呢?任务分割或者说任务分配的工作由DistRaidInputFormat类来完成,其中最重要的就是getSplits()函数:
//DistRaidInputFormat.getSplits()核心片段
...
List<FileSplit> splits = new ArrayList<FileSplit>(numSplits);
SequenceFile.Reader in = null;
for (in = new SequenceFile.Reader(fs, srcs, job); in.next(key, value);) {
long curr = in.getPosition();
long delta = curr - prev;
if (++count > targetcount) {
count = 0;
splits.add(new FileSplit(srcs, prev, delta, (String[]) null));
prev = curr;
}
}...
//map函数的参数
public void map(Text key, PolicyInfo policy,OutputCollector<WritableComparable, Text> out, Reporter reporter)
通过遍历在setup()过程中提交的sequenceFile文件,该函数将个文件的路径及其raid策略封装为一个FileSplit,最终产生FileSplit列表作为函数的返回值。mapreduce框架将把每一个‘切片’split分配给map,作为map的输入源。在本例中,FileSplit中存储的Text类型的文件路径和raid策略policy对象将会被mapreduce自动拆卸并传入map函数。
raid任务的执行
分布式raid任务的流程相对来说比较简单,只通过map阶段就完成了。所以源代码中并没有指定Reducer的继承类。阅读map函数,除去输出提示信息和更新统计数据的代码后剩下了最关键的两行:
Codec.initializeCodecs(jobconf);
RaidNode.doRaid(jobconf, policy, fs, st, reporter);
它们分别完成slave节点上codec的初始化和slave节点本地编码的任务,具体编码过程与RaidNode本地的编码过程并无区别。