title | shortTitle | description | category | language | tag | ||||
---|---|---|---|---|---|---|---|---|---|
MapReduce Pattern in Java |
MapReduce |
Learn the MapReduce pattern in Java with real-world examples, class diagrams, and tutorials. Understand its intent, applicability, benefits, and known uses to enhance your design pattern knowledge. |
Performance optimization |
en |
|
- Split-Apply-Combine Strategy
- Scatter-Gather Pattern
MapReduce aims to process and generate large datasets with a parallel, distributed algorithm on a cluster. It divides the workload into two main phases: Map and Reduce, allowing for efficient parallel processing of data.
Real-world example
Imagine a large e-commerce company that wants to analyze its sales data across multiple regions. They have terabytes of transaction data stored across hundreds of servers. Using MapReduce, they can efficiently process this data to calculate total sales by product category. The Map function would process individual sales records, emitting key-value pairs of (category, sale amount). The Reduce function would then sum up all sale amounts for each category, producing the final result.
In plain words
MapReduce splits a large problem into smaller parts, processes them in parallel, and then combines the results.
Wikipedia says
"MapReduce is a programming model and associated implementation for processing and generating big data sets with a parallel, distributed algorithm on a cluster". MapReduce consists of two main steps: The "Map" step: The master node takes the input, divides it into smaller sub-problems, and distributes them to worker nodes. A worker node may do this again in turn, leading to a multi-level tree structure. The worker node processes the smaller problem, and passes the answer back to its master node. The "Reduce" step: The master node then collects the answers to all the sub-problems and combines them in some way to form the output – the answer to the problem it was originally trying to solve. This approach allows for efficient processing of vast amounts of data across multiple machines, making it a fundamental technique in big data analytics and distributed computing.
- The Mapper takes an input string, splits it into words, and counts occurrences.
- Output: A map {word → count} for each input line.
public class Mapper {
public static Map<String, Integer> map(String input) {
Map<String, Integer> wordCount = new HashMap<>();
String[] words = input.split("\\s+");
for (String word : words) {
word = word.toLowerCase().replaceAll("[^a-z]", "");
if (!word.isEmpty()) {
wordCount.put(word, wordCount.getOrDefault(word, 0) + 1);
}
}
return wordCount;
}
}
Example Input: "Hello world hello"
Output: {hello=2, world=1}
- The Shuffler collects key-value pairs from multiple mappers and groups values by key.
public class Shuffler {
public static Map<String, List<Integer>> shuffleAndSort(List<Map<String, Integer>> mapped) {
Map<String, List<Integer>> grouped = new HashMap<>();
for (Map<String, Integer> map : mapped) {
for (Map.Entry<String, Integer> entry : map.entrySet()) {
grouped.putIfAbsent(entry.getKey(), new ArrayList<>());
grouped.get(entry.getKey()).add(entry.getValue());
}
}
return grouped;
}
}
Example Input:
[
{"hello": 2, "world": 1},
{"hello": 1, "java": 1}
]
Output:
{
"hello": [2, 1],
"world": [1],
"java": [1]
}
- The Reducer sums up occurrences of each word.
public class Reducer {
public static List<Map.Entry<String, Integer>> reduce(Map<String, List<Integer>> grouped) {
Map<String, Integer> reduced = new HashMap<>();
for (Map.Entry<String, List<Integer>> entry : grouped.entrySet()) {
reduced.put(entry.getKey(), entry.getValue().stream().mapToInt(Integer::intValue).sum());
}
List<Map.Entry<String, Integer>> result = new ArrayList<>(reduced.entrySet());
result.sort(Map.Entry.comparingByValue(Comparator.reverseOrder()));
return result;
}
}
Example Input:
{
"hello": [2, 1],
"world": [1],
"java": [1]
}
Output:
[
{"hello": 3},
{"world": 1},
{"java": 1}
]
- The MapReduce class coordinates the three steps.
public class MapReduce {
public static List<Map.Entry<String, Integer>> mapReduce(List<String> inputs) {
List<Map<String, Integer>> mapped = new ArrayList<>();
for (String input : inputs) {
mapped.add(Mapper.map(input));
}
Map<String, List<Integer>> grouped = Shuffler.shuffleAndSort(mapped);
return Reducer.reduce(grouped);
}
}
- The Main class executes the MapReduce pipeline and prints the final word count.
public static void main(String[] args) {
List<String> inputs = Arrays.asList(
"Hello world hello",
"MapReduce is fun",
"Hello from the other side",
"Hello world"
);
List<Map.Entry<String, Integer>> result = MapReduce.mapReduce(inputs);
for (Map.Entry<String, Integer> entry : result) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
Output:
hello: 4
world: 2
the: 1
other: 1
side: 1
mapreduce: 1
is: 1
from: 1
fun: 1
Use MapReduce when:
- Processing large datasets that don't fit into a single machine's memory
- Performing computations that can be parallelized
- Dealing with fault-tolerant and distributed computing scenarios
- Analyzing log files, web crawl data, or scientific data
Benefits:
- Scalability: Can process vast amounts of data across multiple machines
- Fault-tolerance: Handles machine failures gracefully
- Simplicity: Abstracts complex distributed computing details
Trade-offs:
- Overhead: Not efficient for small datasets due to setup and coordination costs
- Limited flexibility: Not suitable for all types of computations or algorithms
- Latency: Batch-oriented nature may not be suitable for real-time processing needs
- Google's original implementation for indexing web pages
- Hadoop MapReduce for big data processing
- Log analysis in large-scale systems
- Genomic sequence analysis in bioinformatics
- Chaining Pattern
- Master-Worker Pattern
- Pipeline Pattern