FB版本RaidNode线程分析——BlockIntegerMonitor
概述
RaidNode上的BlockIntegrityMonitor线程会通过DFSck工具检查系统中corrupt或decomission的数据,通过BlockCopier和BlockFixer线程周期行对出错的数据进行修复。local模式下,修复过程在RaidNode上之行,Dist模式下修复过程通过提交Job的方式提交给集群完成。
具体代码
线程的创建和启动
RaidNode.initialize方法
// Create a block integrity monitor and start its thread(s)
this.blockIntegrityMonitor = BlockIntegrityMonitor.createBlockIntegrityMonitor(conf);
boolean useBlockFixer =
!conf.getBoolean(RAID_DISABLE_CORRUPT_BLOCK_FIXER_KEY, false);
Runnable fixer = blockIntegrityMonitor.getCorruptionMonitor();
if (useBlockFixer && (fixer != null)) {
this.blockFixerThread = new Daemon(fixer);
this.blockFixerThread.setName("Block Fixer");
this.blockFixerThread.start();
}
线程的实现代码(Dist模式)
fixer的实现类是继承了Worker.class的CorruptionWorker.class
Worker.run :
public void run() {
while (running) {
try {
updateStatus();//更新状态,可以忽略
checkAndReconstructBlocks();// important
}
try {
Thread.sleep(blockCheckInterval);
}
}
}
Worker.checkAndReconstructBlocks:
void checkAndReconstructBlocks()
throws IOException, InterruptedException, ClassNotFoundException {
checkJobs();
if (jobIndex.size() >= maxPendingJobs) {
LOG.info("Waiting for " + jobIndex.size() + " pending jobs");
return;
}
Map<String, Integer> lostFiles = getLostFiles();
FileSystem fs = new Path("/").getFileSystem(getConf());
Map<String, Priority> filePriorities =
computePriorities(fs, lostFiles);
LOG.info("Found " + filePriorities.size() + " new lost files");
startJobs(filePriorities);
}
checkJobs 方法:
1.检查Job是否完成,完成的话,从容器中进行移除
2.清楚 fileIndex容器中过期的文件,由raid.blockfix.max.time.for.file指定,默认是4hour。
3.删除不再运行job的in/out路径
getLostFiles 方法:
使用 hadoop fsck -list-corruptfileblocks命令得到 缺块的文件列表。
computePriorities 方法:
将待修复文件分为校验文件和源文件,文件的缺块大于1,Priority =High ,等于1,Priority = Low
startJobs 方法:
将之前的待修复文件按Priority进行变量,filesPerTask * TASKS_PER_JOB(默认是10*50)的待修复文件组成一个job,然后进行startJob
private void startJobs(Map<String, Priority> filePriorities)
throws IOException, InterruptedException, ClassNotFoundException {
String startTimeStr = dateFormat.format(new Date());
for (Priority pri : Priority.values()) {
Set<String> jobFiles = new HashSet<String>();
for (Map.Entry<String, Priority> entry: filePriorities.entrySet()) {
// Check if file priority matches the current round.
if (entry.getValue() != pri) {
continue;
}
jobFiles.add(entry.getKey());
// Check if we have hit the threshold for number of files in a job.
if (jobFiles.size() == filesPerTask * TASKS_PER_JOB) {
String jobName = JOB_NAME_PREFIX + "." + jobCounter +
"." + pri + "-pri" + "." + startTimeStr;
jobCounter++;
startJob(jobName, jobFiles, pri);
jobFiles.clear();
if (jobIndex.size() > maxPendingJobs) return;
}
}
if (jobFiles.size() > 0) {
String jobName = JOB_NAME_PREFIX + "." + jobCounter +
"." + pri + "-pri" + "." + startTimeStr;
jobCounter++;
startJob(jobName, jobFiles, pri);
jobFiles.clear();
if (jobIndex.size() > maxPendingJobs) return;
}
}
}
startJob 方法:
在这里构建只有Map的job,指定job的in/out路径,将待修复的文件列表写入到in路径中,ReconstructionMapper.class作为job的实现类。
BlockIntegrityMonitor和RaidShell对文件的修复最终都通过BlockReconstructor来完成。 BlockReconstructor修复文件过程主要分为三类:Har parity文件,parity文件和源数据文件。
Har parity文件
- 获取har文件的基本信息及index
- 获取har文件中的lost block,对每个block进行如下处理:
- 在本地文件系统创建该block的临时文件,
- 对该block涉及的所有parity文件,获取对应的source文件,通过Encoder重新encode,在本地生成parity数。
- 将本地生成的block数据发送到一个datanode上,datanode的选取规则是从集群中除原block所属节点外随机选取一个。发送过程同时生成block的meta文件。
parity文件
parity文件的修复处理相对简单:
- 在本次创建lost block的临时文件
- 获取parity文件的源文件,通过Encoder重新encode,在本地生成parity文件的block
- 选取一个dn(选取规则和har parity文件修复一致),将block数据发送到该dn上,并同时生成meta文件
源数据文件
源文件的恢复与parity文件的修复相反,是一个decode过程:
-
对于file中丢失的每个block执行修复操作
-
在本地创建block的临时文件
-
通过Decoder恢复block数据
-
选取一个target dn,将block数据发送给target dn,并同时生成meta文件。
Decoder的修复过程:即一个parity文件的decode过程:
- 根据文件中出错的位置,计算出错的block,该block所在的stripe,以及在stripe中的位置,计算parity文件相应block的位置。
- 通过ParallelStreamReader读取源block数据和parity数据,读取方式与编码时类似
- 通过Erasured Code将源block和parity数据的进行解码,生成丢失的block数据
private void startJob(String jobName, Set<String> lostFiles, Priority priority)
throws IOException, InterruptedException, ClassNotFoundException {
Path inDir = new Path(JOB_NAME_PREFIX + "/in/" + jobName);
Path outDir = new Path("/user/work/"+JOB_NAME_PREFIX + "/out/" + jobName);
List<String> filesInJob = createInputFile(
jobName, inDir, lostFiles);
if (filesInJob.isEmpty()) return;
Configuration jobConf = new Configuration(getConf());
if (jobpool != null){
jobConf.set("mapred.fairscheduler.pool", jobpool);
}
RaidUtils.parseAndSetOptions(jobConf, priority.configOption);
if (priority == priority.HIGH){
jobConf.set("mapred.job.priority", "HIGH");
}
Job job = new Job(jobConf, jobName);
configureJob(job, this.RECONSTRUCTOR_CLASS);
job.setJarByClass(getClass());
job.setMapperClass(ReconstructionMapper.class);
job.setNumReduceTasks(0);
job.setInputFormatClass(ReconstructionInputFormat.class);
job.setOutputFormatClass(SequenceFileOutputFormat.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
ReconstructionInputFormat.setInputPaths(job, inDir);
SequenceFileOutputFormat.setOutputPath(job, outDir);
submitJob(job, filesInJob, priority);
List<LostFileInfo> fileInfos =
updateFileIndex(jobName, filesInJob, priority);
// The implementation of submitJob() need not update jobIndex.
// So check if the job exists in jobIndex before updating jobInfos.
if (jobIndex.containsKey(job)) {/// sumbit put job null
jobIndex.put(job, fileInfos);
}
numJobsRunning++;
}