آموزش MapReduce به زبان ساده – بخش دوم

در بخش اول به آموزش مفهوم MapReduce در هدوپ پرداختیم. حال در بخش دوم این مقاله می خواهیم به بررسی عملی و پیاده سازی مثالی که در بخش اول مطرح شد یعنی شمارش تعداد کلمات بپردازیم.

نکته: به دلیل اینکه زبان برنامه نویسی پایه هدوپ جاوا می باشد ، می بایست با زبان برنامه نویسی جاوا و چگونگی کار با خط فرمان سیستم عامل آشنایی حداقلی داشته باشید.

همانطور که می دانید MapReduce یک مدل برنامه نویسی است که با هر زبان برنامه نویسی می توان آنرا پیاده سازی کرد ولی اگر بخواهید آنرا توسط هدوپ اجرا کنید یا باید برنامه خودتان به زبان جاوا باشد و یا اینکه باید از Hadoop Streaming API  برای حرکت ورودی ها و خروجی ها بین دو متد Map و Reduce از طریق stdin و stdout استفاده کنید. در این آموزش ما گزینه اول را انتخاب می کنیم.

برای شروع کد نویسی پیشنهاد می کنیم یکی از واسط های نرم افزاری مخصوص جاوا مثل NetBeans و Eclipse را دانلود و نصب نمایید. به دلیل اینکه آموزش کار با این محیط و چگونگی ایجاد پروژه در آن خارج از حوصله این مقاله است از این موارد گذر می کنیم و به موضوع اصلی یعنی پیاده سازی مثال شمارش تعداد کلمات با استفاده از MapReduce در هدوپ می پردازیم.

در اولین مرحله یک پروژه جاوا ایجاد می کنیم و کتابخانه های زیر را به پروژه اضافه می کنیم:

  • Hadoop-common.jar
  • Hadoop-mapreduce-client-core.jar
  • Hadoop-mapreduce-client-jobclient.jar

این کتابخانه ها را می توانید از پوشه share/hadoop از بسته فشرده هدوپ که دانلود کرده اید پیدا کنید.

پس از ایجاد پروژه و افزودن کتابخانه های موردنیاز به پروژه ، می بایست با استفاده از دستور import کلاس های موردنیاز را به کد مان اضافه کنیم:

import java.io.IOException;
import java.util.*;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.*;
import org.apache.hadoop.mapred.*;

در مرحله بعد ما به دو زیرکلاس یا subclass به نام های Map وReduce که این زیرکلاس ها شامل دو تابع Map و Reduce می شوند نیاز داریم که از قدرت هدوپ برای انجام کار موردنظرمان استفاده کند(به دلیل اینکه نمی خواهیم این دو تابع را در کلاس های جداگانه قرار دهیم آنها را در فایل اصلی کد مان به عنوان زیرکلاس تعریف کردیم) . برای اینکار می بایست زیر کلاس ها و توابع را به صورت زیر تعریف کنیم:

public static class Map extends MapReduceBase implements Mapper<LongWritable, Text, Text, IntWritable> {

 @Override
     public void map(LongWritable key, Text value, OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException {

// OUR CODE WILL BE HERE

}
}

public static class Reduce extends MapReduceBase implements Reducer<Text, IntWritable, Text, IntWritable> {
     @Override
     public void reduce(Text key, Iterator<IntWritable> values, OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException {

// OUR CODE WILL BE HERE

}
}

دو کلاس بالا از Interface جاوایی به نام Mapper و Reducer از کلاس MapReduceBase ارث بری میکند که از طریق دستورات extends و implements انجام می شود. واسط یا همان Interface های بالا ۴ مقدار یا متغیر اصلی می پذیرند که از قبل می بایست نوع این ۴ متغیر به این واسط اعلام شود(دقت کنید که متغیر اول در تابع Mapper در این مثال مورد استفاده قرار نمی گیرد ولی می بایست معرفی شود). دو متغیر از این ۴ متغیر به عنوان ورودی و دو متغیر دیگر به عنوان خروجی در نظر گرفته می شوند.

شاید بپرسید متغیرهای Text و LongWritable و IntWritable از چه نوعی هستند و چرا از متغیرهای عادی جاوا مثل String ، Int و Long استفاده نشده است. پاسخ این است که Writable ها می توانند اشیاء را با استفاده از هدوپ با سرعت بسیار بیشتر از متغیرهای معمولی جاوا سریالی کنند و به دلیل اینکه عملیات سریالی کردن یا Serialization در جاوا بسیار کند و سنگین است ، این وظیفه را هدوپ بر عهده می گیرد تا از سرعت برنامه کم نشود. پس نوع متغیرهای IntWritable و LongWritable از نظر کلی همان Int و Long هستند و نوع Text همان String است ولی با اندکی تغییر که در بالا توضیح داده شد.

در دو تابع بالا دو نوع متغیر ناآشنا به نام های  OutputCollector و Reporter وجود دارد. OutputCollector در واقع یک لیست برای جمع آوری مقادیر کلید-مقدار در توابع Map و Reduce است و Reporter وظیفه اطلاع رسانی درصد پیشرفت برنامه و گزارش گیری را بر عهده دارد.

پس از تعریف دو زیرکلاس و تابع بالا سراغ کار اصلی که برنامه می بایست انجام دهد یعنی شمارش تعداد کلمات می رویم.

وظیفه کلی تابع Map در اینجا این است که فایل ورودی ما که یک فایل متنی شامل کلمات مختلف است را دریافت کند و آنها را به زوج های کلید-مقدار تبدیل کند تا بتوان آنها را توسط تابع Reduce بر روی گره های دیگر شبکه به صورت موازی پردازش کرد.

دو متغیر زیر را به صورت سراسری می بایست تعریف کنیم:

private final static IntWritable one = new IntWritable(1);
private final Text word = new Text();

در بالا متغیر one با مقدار اولیه ۱ و متغیری از نوع Text به نام word برای نگه داری هر کلمه تعریف شده است. 

متغیر value در تابع Map حاوی تمامی اطلاعات فایل ورودی ما می باشد. پس باید با داده ای که در این متغیر قرار گرفته است کار کنیم. 

کد زیر در تابع Map قرار میگیرد:

String line = value.toString();
StringTokenizer tokenizer = new StringTokenizer(line);
while (tokenizer.hasMoreTokens()) {
word.set(tokenizer.nextToken());
output.collect(word, one);
}

در کد بالا مقدار متغیر value را باید تبدیل به String کنیم و سپس با کلاس StringTokenizer تمامی کلمات را از هم جدا کنیم(البته اگر برنامه نویس باشید میدانید که این کار را می توان با Split کردن متن با خط فاصله هم انجام داد که ما برای راحتی کار به از StringTokenizer استفاده کردیم).

کد بالا تمامی کلمات ما را تبدیل به کلید-مقدار <word,1> می کند و به لیست OutputCollector اضافه می کند. با اینکار همه کلماتی که در فایل ورودی ما موجود بود عدد ۱ به آنها انتصاب داده می شود و تبدیل به کلید-مقدار می شوند.

حال به سراغ تابع Reduce می رویم تا تمامی این کلید-مقدار ها را ادغام و شمارش کند.

کد زیر این کار را برای ما انجام می دهد:

int sum = 0;
while (values.hasNext()) {
sum += values.next().get();
}
output.collect(key, new IntWritable(sum));

 

در واقع کد بالا تمامی کلید-مقدار های تکراری از لیست ما را با هم جمع می کند و به متغیرهای خروجی می فرستد.

در مرحله بعدی سراغ تابع main برنامه می رویم که توابعی که ساختیم را به هدوپ معرفی کنیم. کد main به صورت زیر است:

JobConf conf = new JobConf(MapReduceSample.class);
conf.setJobName("wordcount");
conf.setOutputKeyClass(Text.class);
conf.setOutputValueClass(IntWritable.class);
conf.setMapperClass(Map.class);
conf.setCombinerClass(Reduce.class);
conf.setReducerClass(Reduce.class);
conf.setInputFormat(TextInputFormat.class);
conf.setOutputFormat(TextOutputFormat.class);

FileInputFormat.setInputPaths(conf, new Path(args[0]));
FileOutputFormat.setOutputPath(conf, new Path(args[1]));

JobClient.runJob(conf);

در کد بالا ما یک نمونه از کلاس JobConf ساختیم که مقادیر مربوط به برنامه خودمان را به هدوپ معرفی کنیم و در نهایت براساس این تنظیمات برنامه ما اجرا شود. توضیحات متدهای بالا در زیر آمده است:

  • setJobName = معرفی نام برنامه
  • setOutputKeyClass = معرفی نوع کلیدهای خروجی
  • setOutputValueClass = معرفی نوع مقادیر خروجی
  • setMapperClass = معرفی کلاس Map
  • setCombinerClass = معرفی کلاس Combiner (در این برنامه از مرحله Combiner صرف نظر کردیم و  و تابع Reduce همان کار را برای ما انجام می دهد)
  • setReducerClass = معرفی کلاس Reducer
  • setInputFormat = معرفی نوع فایل ورودی
  • setOutputFormat = معرفی نوع فایل خروجی
  • setInputPaths = معرفی مسیر فایل ورودی
  • setOutputPath = معرفی مسیر فایل خروجی

در نهایت متد  runJob برنامه ما را بر روی هدوپ راه اندازی می کند.

در اینجا برنامه ما آماده است ولی برای انتقال آن به هدوپ می بایست پروژه را Build کنید تا فایل jar ساخته شود. پس از اینکار با دستور زیر می توانید برنامه MapReduce ای که نوشتیم را بر روی هدوپ اجرا کنیم:

hadoop jar MapReduceSample.jar /path/to/input /path/to/output

با نوشتن دستور بالا به هدوپ می گوییم که فایل اجرایی برنامه ما MapReduceSample.jar است که بر روی پوشه /path/to/input به عنوان ورودی و /path/to/output به عنوان خروجی کار می کند. دقت کنید که پوشه اصلی خروجی نباید از قبل وجود داشته باشد. این پوشه ها بسته به نوع پیکربندی هدوپ می توانند هم به صورت محلی و هم به صورت توزیع شده بر روی فایل سیستم هدوپ موجود باشند.

نتیجه کار برنامه را می توانید در پوشه خروجی در فایل part-0000 مشاهده کنید.

دانلود کد برنامه

نویسنده: مبین رنجبر

از سرورهای ما برای اجرای پروژه های داده های کلان خود بدون اینکه درگیر پیچیدگی نصب شوید استفاده کنید. برای سفارش اینجا را کلیک کنید.
ما حتی می توانیم کلاستر و سرورهای هدوپ را برای شما ایجاد کنیم. برای درخواست راه اندازی اینجا را کلیک کنید.
اگر نیاز به مشاوره و یا کلاس آموزشی داشتید به اینجا مراجعه کنید.

لیست نظرات

پاسخ دهید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *