Hình 1.7. Quy trình xử lý của MapReduce [9]
MapReduce được xây dựng từ mô hình lập trình hàm và lập trình song song. Tăng tốc độ thực thi xử lý dữ liệu là mục đích quan trọng nhất của MapReduce. Quy trình này gồm 2 phần:
- Map: Bắt đầu từ nút chủ (master node), công việc được chia nhỏ nó ra thành các công việc với phạm vi nhỏ hơn, gọi là các split 0, split 1, split 2 …
- Reduce: Từ các đầu ra trung gian sẽ tổng hợp lại để đưa ra các kết quả cuối cùng.
(1): Thư viện MapReduce mà chương trình người dùng (User Program) sử dụng chia các tập tin đầu vào (dữ liệu cần xử lý) thành các phần nhỏ. Dung lượng mỗi phần từ 16 MB đến 64 MB. Sau đó sao chép chương trình thành các tiến trình song song chạy trên các máy tính phân tán chứa dữ liệu.
(2): Chương trình điều khiển Master sẽ gán mỗi phần dữ liệu cho một hàm Map và một hàm Reduce.
(3) – (4): worker là phần được gán một hàm Map và Reduce để xử lý, nó sẽ đọc dữ liệu, phân tích cặp key/value ở đầu vào và phân tích thành các cặp trung gian khác được lưu tại vùng nhớ đệm.
(5): Định kỳ, các cặp dữ liệu trung gian sẽ được đẩy đến các worker tương ứng (do master điều khiển) để hàm reduce xử lý. Các thuật toán sắp xếp, so sánh, phân vùng dữ liệu sẽ được sử dụng tại giai đoạn này. Các tập dữ liệu trung gian có cùng key sẽ được sắp xếp cùng một nhóm.
(6): Khi tất cả các tác vụ Map và Reduce đã hoàn tất thì sẽ cho ra kết quả cuối cùng của quy trình MapReduce.
Ví dụ minh họa cho bài đếm từ, đầu vào và đầu ra, tương tự như phần trên nhưng mô tả cụ thể nội dung của từng hàm:
Hàm Map:
void map (String name, String document): // name: document name
// document: document contents
for each word w in document: EmitIntermediate (w, "1");
HàmReduce:
void reduces (String word, Iterator partialCounts): // word: a word
// partialCounts: a list of aggregated partial counts int result = 0;
for each pc in partialCounts: result += ParseInt (pc); Emit (AsString (result));
Grep phân tán: cho phép người dùng nhập vào một mẫu (pattern), hệ thống tìm trong tập các văn bản và trả về các dòng (line) khớp với mẫu đã cho. Hàm map nhận đầu vào là một cặp mẫu/dòng, đầu ra là một cặp mẫu/dòng nếu dòng khớp với mẫu. Hàm reduce đơn giản chỉ cần trả về danh sách các dòng mà nó nhận được.
Đếm số lượt truy cập trên một URL: khi người dùng truy cập vào một trang nào đó, URL của trang đó sẽ được ghi vào một file log. Hàm map nhận đầu vào là các file log và trả về <URL, 1>. Hàm reduce tính tổng các value của cùng một URL và trả về cặp <URL, total count>.
Sắp xếp phân tán: hàm map trích từ khóa từ mỗi bản ghi và trả về các cặp (key/record). Hàm reduce trả về những cặp không đổi.
1.3.4 Hadoop – Nền tảng lập trình mô hình MapReduce
Hadoop là một nền tảng nguồn mở, được Dough Cutting tạo ra khi ông nghiên cứu về Nutch - một ứng dụng tìm kiếm. Hadoop được viết bằng Java, dùng hỗ trợ xây dựng, thực thi các ứng dụng tính toán phân tán theo mô hình MapReduce. Hadoop cluster là hệ thống máy tính đã được triển khai nền tảng Hadoop, một Hadoop cluster bao gồm hai thành phần cơ bản là kiến trúc MapReduce và hệ thống tập tin phân tán HDFS, trong đó:
Kiến trúc MapReducer gồm hai phần: TaskTracker - trực tiếp thực thi các tác vụ xử lý dữ liệu, JobTracker - quản lý và phân chia công việc cho các TaskTracker.
Hệ thống HDFS gồm hai phần: DataNode - nơi trực tiếp lưu trữ dữ liệu, mỗi DataNode chịu trách nhiệm lưu trữ một phần dữ liệu của hệ thống, NameNode - quản lý các DataNode, dẫn đường cho các yêu cầu truy xuất dữ liệu.
Kiến trúc của Hadoop cluster là kiến trúc Master-Slave, và cả hai thành phần MapReduce và HDFS đều tuân theo kiến trúc này:
Hình 1.8. Các thành phần của Hadoop cluster
Hadoop Distributed File System (HDFS):
HDFS là một hệ thống quản lý tập tin với kích thước rất lớn bằng mô hình phân tán. Tương tự các hệ thống trước nó, HDFS cũng đáp ứng được yêu cầu như độ tin cậy, khả năng mở rộng và hiệu suất hoạt động… Tuy nhiên, HDFS ra đời để đáp ứng các đòi hỏi về lưu trữ và xử lý một lượng lớn dữ liệu với các đặc thù riêng.
Các khái niệm trong HDFS:
Khối (Block): Tượng tự như cách thức quản lý ổ cứng của các hệ điều hành, block là cơ sở của HDFS, tập tin lưu trữ trong HDFS được chia nhỏ thành các block, kích thước mặc định là 64 MB. HDFS thiết kế block lớn, người dùng có quyền thay đổi giá trị để giảm thời gian duyệt tập tin nhưng vẫn đảm bảo tốc độ duyệt tối thiểu một block.
NameNode và DataNode: Một cụm HDFS có hai loại nút:
- NameNode: chịu trách nhiệm quản lý những thông tin về cấu trúc thư mục, file các metadata hệ thống. Lưu trữ vị trí của các block của tập tin trên DataNode.
- Các DataNode: chịu trách nhiệm lưu trữ và cập nhật vị trí của block định kỳ cho NameNode.
Kiến trúc HDFS: HDFS sử dụng cấu trúc cây để phân cấp file, thư mục. Các file là các node lá, mỗi file được chia thành các block có block ID để nhận diện. Mỗi block được lưu trữ thành nhiều bản (replica) nhằm đảm bảo an toàn dữ liệu. Trong một cụm, chỉ có một NameNode chịu trách nhiệm quản lý, nó cũng có các bản sao ở các cụm khác để đảm bảo cả cụm vẫn hoạt động nếu NameNode bị lỗi. Khi có yêu cầu lấy tập tin từ máy khách gửi đến NameNode, nó sẽ gửi danh sách block id và nơi lưu trữ chúng cho máy đó, từ danh sách đó máy khách gửi các yêu cầu đến các DataNode chứa các block đó để lấy chúng về, sau đó tổng hợp lại thành tập tin hoàn chỉnh.
Hoạt động của MapReduce trên Hadoop:
MapReduce trên Hadoop gồm hai thành phần: JobTracker là nơi quản lý, lập lịch các công việc cho hệ thống và phân phối cho các TaskTracker. Mỗi khi có yêu cầu thực thi một ứng dụng MapReduce, JobTracker sẽ tạo ra một JobClient và chép toàn bộ code thực thi cần thiết của job đó lên hệ thống tập tin phân tán HDFS, mỗi JobClient sẽ được gán một JobID duy nhất. Tiếp theo JobClient sẽ gửi một yêu cầu thực thi job lên JobTracker, JobTracker dựa theo yêu cầu của JobClient, sẽ gửi yêu cầu khởi tạo task kèm theo các thông tin phân công công việc đến các TaskTracker. Mỗi TaskTracker sẽ dựa vào thông tin phân công lần lượt thực hiện: Khởi tạo map task hoặc reduce task, chép toàn bộ code thực thi trên HDFS về, thực hiện công việc được phân công. Sau khi thực hiện xong, TaskTracker sẽ thông báo cho JobTracker và tự giải phóng.
1.4 Lập trình với mô hình ứng dụng MapReduce
1.4.1 Phương pháp viết một MapReduce job
MapReduce job là một đơn vị của công việc (job) mà khách hàng muốn được thực hiện: nó bao gồm dữ liệu đầu vào, chương trình MapReduce, và thông tin cấu hình. Hadoop chạy các công việc này bằng cách chia nó thành các nhiệm vụ (task), trong đó có hai kiểu chính là: các nhiệm vụ map (map task) và các nhiệm vụ reduce (reduce task) [3].
Trong phần này sẽ nói chi tiết hơn về từng giai đoạn trong chương trình MapReduce điển hình. Hình 1.11 biểu diễn biểu đồ phân cấp của toàn bộ quá trình, và xem xét cụ thể từng phần:
1.4.2 Các kiểu dữ liệu Hadoop hỗ trợ
MapReduce framework có một cách định nghĩa cặp khóa key/value tuần tự để có thể gửi qua mạng, và chỉ các lớp hỗ trợ kiểu tuần tự mới được dùng làm giá trị của key và value.
Cụ thể hơn, các lớp implement giao diện Writable có thể làm value, và các lớp implement giao diện WritableComparable<T> có thể làm cả key và value. Lưu ý rằng giao diện WritableComparable<T> là một sự kết hợp của Writable và giao diện java.lang.Comparable<T> dùng cho việc so sánh các khóa bởi vì chúng sẽ được sắp xếp ở giai đoạn reduce, các giá trị đơn thì không cần.
Hadoop đi cung cấp một số lớp được định nghĩa trước đã implement
WritableComparable, bao gồm các lớp bao cho tất cả các loại dữ liệu cơ bản như trong Bảng 1.1.
Bảng 1.1 Danh sách các kiểu dữ liệu cơ bản của Hadoop
Tên lớp Miêu tả
BooleanWritable Lớp bao (wapper) của biến kiểu Boolean chuẩn
ByteWritable Lớp bao của biến kiểu byte
DoubleWritable Lớp bao của biến kiểu double
FloatWritable Lớp bao của biến kiểu float
IntWritable Lớp bao của biến kiểu int
LongWritable Lớp bao của biến kiểu long
Text Lớp bao của biến kiểu văn bản định dạng UTF-8
NullWritable Lớp để giữ chỗ khi mà key hoặc value không cần thiết
Chúng ta cũng có thể tùy chỉnh một kiểu dữ liệu bằng cách implement
Writable (hay WritableComparable<T>). Ví dụ sau minh họa lớp biểu diễn các cạnh (Edge) trong đồ thị:
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.io.WritableComparable;
public class Edge implements WritableComparable<Edge> { private String departureNode; //Node khoi hanh
private String arrivalNode; //Node den
public String getDepartureNode(){
return departureNode; }
public void readFields(DataInput in) throws IOException { departureNode = in.readUTF();
arrivalNode = in.readUTF(); } @Override
public void write(DataOutput out) throws IOException { out.writeUTF(departureNode);
out.writeUTF(arrivalNode); }
@Override
public int compareTo(Edge o) {
return (departureNode.compareTo (o.departureNode)!= 0)?
departureNode.compareTo(departureNode):
arrivalNode.compareTo(o.arrivalNode);
} }
Lớp Edge thực hiện hai phương thức readFields() và write() của giao diện Writeable. Chúng làm việc với lớp DataInput và DataOutput
để tuần tự nội dung của các lớp. Thực hiện phương pháp compareTo() cho interface Comparable.
Với kiểu dữ liệu được định nghĩa tại giao diện, chúng ta có thể tiến hành giai đoạn đầu tiên của xử lý luồng dữ liệu như trong Hình 1.11–mapper.
1.4.3 Mapper
Để tạo ra một Mapper, thì lớp này implements từ interface Mapper và kế thừa từ lớp MapReduceBase [2]. Lớp MapReduceBase, đóng vai trò là lớp cơ sở cho cả mapper và reducer. Nó bao gồm hai phương thức hoạt động hiệu quả như là hàm khởi tạo và hàm hủy của lớp:
void configure(JobConf job) – hàm này có thể trích xuất các thông số cài đặt bằng các file XML cấu hình hoặc trong các lớp chính của ứng dụng của bạn. Hàm này được gọi trước khi xử lý dữ liệu.
void close() – phương thức đóng dòng dữ liệu. Như hành động cuối trước khi chấm dứt nhiệm vụ map, thường được gọi khi kết thúc để đóng kết nối cơ sở dữ liệu, các file đang mở.
Giao diện Mapper chịu trách nhiệm cho bước xử lý dữ liệu. Nó sử dụng mẫu Mapper<K1,V1,K2,V2>, implements từ WriteableComparable và
Writable. Phương thức duy nhất của nó để xử lý các cặp (key/value) như sau:
void map(K1 key,V1 value,OutputCollector<K2, V2> output,Reporter reporter) throws IOException
Phương thức này tạo ra một danh sách (có thể rỗng) các cặp (K2,V2)
từ một cặp đầu vào (K1,V1). OuputCollector nhận kết quả từ đầu ra của quá trình map, và Reporter cung cấp các tùy chọn để ghi lại thông tin thêm về mapper như tiến triển công việc.
Hadoop cung cấp một vài cài đặt Mapper hữu dụng như trong Bảng 1.2.
Bảng 1.2. Một vài lớp thực hiện Mapper được định nghĩa trước bởi Hadoop
Tên lớp Miêu tả
IdentityMapper<K,V> Với cài đặt Mapper <K,V,K,V> và ánh xạ
trực tiếp từ đầu vào đến đầu ra
InverseMapper<K,V> Với cài đặt Mapper<K,V,V,K> và đảo ngược cặp (K/V)
RegexMapper<K>
Với cài đặt Mapper<K,Text,Text,
LongWritable> và sinh ra cặp (match, 1) cho mỗi ánh xạ biểu thức phù hợp
TokenCountMapper<K>
Với cài đặt Mapper<K,Text,Text,
LongWritable> sinh ra một cặp (token,
1.4.4 Reducer
Với bất cứ cài đặt Mapper, một Reducer đầu tiên phải kế thừa từ lớp MapReduceBase để cho phép cấu hình và dọn dẹp sau khi hoàn thành. Ngoài ra, nó cũng phải implement giao diện Reducer với một phương thức duy nhất:
void reducer(K2 key,Iterator<V2>values,
OutputCollector<K3,V3> output,Reporter reporter)throws IOException
Khi nhận được các task từ đầu ra của các Mapper khác nhau, nó sắp xếp các dữ liệu đến theo các khóa của các cặp (key/value) và nhóm lại các giá trị cùng khóa.
Hàm reduce() được gọi sau đó, nó sinh ra một danh sách (có thể rỗng) các cặp (K3,V3) bằng cách lặp lại trên các giá trị được liên kết với khóa đã cho. OutputCollector nhận từ đầu ra của quá trình reduce và ghi nó ra đầu ra file. Reporter cung cấp tùy chọn ghi lại thông tin thêm về reducer như là một tiến triển công việc.
Bảng 1.3. Danh sách một số reduce cơ bản được cung cấp bởi Hadoop
Tên lớp Miêu tả
IdentityReducer<K,V> Với cài đặt Reducer <K,V,K,V> ghi lại tất
cả các giá trị từ đầu vào đến đầu ra
LongSumReducer<K>
Với cài đặt Reducer <K,LongWritable, K,LongWritable> và tập hợp giá trị có cùng key lại với nhau.
1.4.5 Chương trình Mapreduce bằng Java
Một chương trình theo mô hình MapReduce cần 3 class: một map function, một reduce function và một đoạn code để chạy job.
Bài toán Wordcount (WC) là bài toán đơn giản trong Hadoop nhằm minh họa cho quá trình MapReduce. Dưới đây sẽ mô tả bài toán theo từng hàm của mô hình MapReduce.
Map function
Hàm map sẽ implementation Mapper interface, để thực thi hàm map(). Source code như sau:
package com.javatpoint; import java.io.IOException; import java.util.StringTokenizer; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapred.MapReduceBase; import org.apache.hadoop.mapred.Mapper; import org.apache.hadoop.mapred.OutputCollector; import org.apache.hadoop.mapred.Reporter;
public class WC_Mapper extends MapReduceBase implements Mapper< LongWritable,Text,Text,IntWritable>
{
private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
public void map(LongWritable key, Text value,OutputCollect or<Text,IntWritable> output, Reporter reporter)
throws IOException
{
String line = value.toString();
StringTokenizer tokenizer = new StringTokenizer(line);
while (tokenizer.hasMoreTokens())
{
word.set(tokenizer.nextToken());
output.collect(word, one);
Trong Mapper interface ta cần chỉ định các kiểu của input key, input value, output key và output value. Trong ví dụ bài toán WC ở trên:
Input key là kiểu long integer Input value: kiểu Text
Output key: là một tokenized Output value: là kiểu integer
Hadoop cung cấp một số kiểu cơ bản, nó được định nghĩa trong gói org.apache.hadoop.io
Hàm map() được đưa vào một key/value và được cung cấp một cài đặt của OutputCollector để ghi output vào đó.
Reduce function: được định nghĩa sử dụng reducer. package com.javatpoint; import java.io.IOException; import java.util.Iterator; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapred.MapReduceBase; import org.apache.hadoop.mapred.OutputCollector; import org.apache.hadoop.mapred.Reducer; import org.apache.hadoop.mapred.Reporter;
public class WC_Reducer extends MapReduceBase implements Reducer<Text,IntWritable,Text,IntWritable>
{
public void reduce(Text key, Iterator<IntWritable> value s,OutputCollec-tor<Text,IntWritable> output,
Reporter reporter) throws IOException { int sum=0; while (values.hasNext()) { sum+=values.next().get(); } output.collect(key,new IntWritable(sum)); }}
Tương tự như hàm map, có 4 kiểu tham số được dùng để chỉ định cho các kiểu input và output. Trong đó các kiểu input của hàm reduce phải khớp tuyệt đối với kiểu output của hàm map.
MapReduce Job package com.javatpoint; import java.io.IOException; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapred.FileInputFormat; import org.apache.hadoop.mapred.FileOutputFormat; import org.apache.hadoop.mapred.JobClient; import org.apache.hadoop.mapred.JobConf; import org.apache.hadoop.mapred.TextInputFormat; import org.apache.hadoop.mapred.TextOutputFormat; public class WC_Runner {
public static void main(String[] args)throws IOException{JobC
onf conf = new JobConf(WC_Runner.class);
conf.setJobName("WordCount"); conf.setOutputKeyClass(Text.class); conf.setOutputValueClass(IntWritable.class); conf.setMapperClass(WC_Mapper.class); conf.setCombinerClass(WC_Reducer.class); conf.setReducerClass(WC_Reducer.class); conf.setInputFormat(TextInputFormat.class); conf.setOutputFormat(TextOutputFormat.class); FileInputFormat.setInputPaths(conf,new Path(args[0])); FileOutputFormat.setOutputPath(conf,newPath(args[1])); JobClient.runJob(conf); } }
JobConf: chỉ định chi tiết job, nó cho phép điều khiển cách mà job làm việc.
Trong JobConf ta cần chỉ định inputpath và outputpath.
Một input path được chỉ định bằng cách gọi phương thức
addInputPath() trong FileInputFormat, nó có thể là một file đơn, một thư mục hay một pattern.
Output path được chỉ định bởi phương thức setOutPutPath() của FileOutputFormat. Nó chỉ định một thư mục nơi mà các output files từ reducer functions được viết ra.
Tiếp theo ta cần chỉ định các kiểu map và reducer thông qua
setMapperClass() và setReduceClass()
Phương thức setCombinerClass() giúp giảm tải lượng dữ liệu truyền từ Map về Reduce.
Dùng lệnh FileOutputFormat.setOutputPath() để chỉ định thư mục xuất ra và JobConf.setOutputFormat() để chỉ định đối tượng
OutputFormat.
1.5 Kết luận chương 1
Trong chương này đã giới thiệu tổng quan các khái niệm đồ thị, đồ thị con thường xuyên, Apache Hadoop và mô hình lập trình MapReduce. Các khái niệm đã chỉ ra những vấn đề cốt lõi trong quá trình nghiên cứu về khai phá đồ thị con thường xuyên theo mô hình lập trình MapReduce. Với các nghiên cứu mới nhất của các thuật toán khai phá đồ thị cùng với các ngôn ngữ lập trình mới, công cụ mới có khả năng đơn giản hóa quá trình viết mã, tận dụng các thư viện có sẵn từ cộng đồng và các nền tảng mới tăng khả năng xử