最近整理大数据离线计算这块内容时,我最大的感受是:MapReduce 是底层发动机 ,而 Hive / Pig 是"让我们少写 Java"的上层工具。理解它们的定位和边界,做实验和写作业会顺很多,也能少踩坑。
目录
1. MapReduce 到底解决什么问题
MapReduce 的核心目标很明确:在大量普通机器上,稳定处理海量数据。它的优势一般体现在:
-
任务逻辑清晰:只要写 Mapper / Reducer
-
数据量大也能扩:加机器、加并行度
-
容错强:任务失败自动重试
-
适合离线批处理:吞吐优先
一句话记忆:大批量、可容错、可横向扩展的离线计算模型。
2. MapReduce 的限制:哪些场景别硬上
MapReduce 不适合:
-
实时计算:要求秒级/毫秒级响应
-
流式计算:数据不停进来不停算
-
复杂 DAG:多阶段依赖链(多作业串起来)但效率不高
如果你发现自己要"频繁多轮迭代""强交互""实时监控",大概率要换方案(Spark/流式框架等)。
3. WordCount 拆透:Map → Shuffle/Sort → Reduce
WordCount 是最经典的例子:把输入文本切成词,统计每个词出现次数。
3.1 最小可运行版本
Mapper: 输出 (word, 1)
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class WCMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
private final IntWritable ONE = new IntWritable(1);
private final Text outKey = new Text();
@Override
protected void map(LongWritable key, Text value, Context context)
throws java.io.IOException, InterruptedException {
// 简单按空格切词
String[] words = value.toString().trim().split("\\s+");
for (String w : words) {
if (!w.isEmpty()) {
outKey.set(w);
context.write(outKey, ONE);
}
}
}
}
Reducer: 聚合相同 key 的 values
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class WCReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
private final IntWritable outVal = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context)
throws java.io.IOException, InterruptedException {
int sum = 0;
for (IntWritable v : values) sum += v.get();
outVal.set(sum);
context.write(key, outVal);
}
}
Driver: 组装 Job
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
public class WCDriver {
public static void main(String[] args) throws Exception {
if (args.length != 2) {
System.err.println("Usage: WCDriver <input> <output>");
System.exit(1);
}
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "wordcount");
job.setJarByClass(WCDriver.class);
job.setInputFormatClass(TextInputFormat.class);
TextInputFormat.addInputPath(job, new Path(args[0]));
job.setMapperClass(WCMapper.class);
job.setReducerClass(WCReducer.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
job.setOutputFormatClass(TextOutputFormat.class);
TextOutputFormat.setOutputPath(job, new Path(args[1]));
System.exit(job.waitForCompletion(true) ? 0 : 2);
}
}
3.2 运行命令
hadoop jar wc.jar WCDriver /input/words.txt /output/wc_out
hdfs dfs -cat /output/wc_out/part-r-00000 | head
4. 关键组件:InputFormat / Split / Combiner / Partitioner
4.1 InputFormat:切片 + 解析 KV
-
负责把输入数据分成多个 Split
-
并把每条记录解析成 key/value
默认 TextInputFormat:key 是偏移量,value 是一行文本。
4.2 Split vs Block
-
Block:存储单位(HDFS)
-
Split :计算单位(MapReduce)
很多情况下 Split ≈ Block,但它们不是一个概念。
4.3 Combiner:本地先合并,减少网络 IO
WordCount 可以直接复用 Reducer 作为 Combiner:
job.setCombinerClass(WCReducer.class);
注意:只有"可叠加"的聚合适合(sum/min/max 这类),平均值这种要小心。
4.4 Partitioner:决定 key 去哪个 Reducer
默认 hash(key) % numReduceTasks。
示例:按首字母分区
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;
public class FirstCharPartitioner extends Partitioner<Text, IntWritable> {
@Override
public int getPartition(Text key, IntWritable value, int numPartitions) {
if (numPartitions <= 1) return 0;
char c = Character.toLowerCase(key.toString().charAt(0));
if (c >= 'a' && c <= 'i') return 0;
if (c >= 'j' && c <= 'r') return 1 % numPartitions;
return 2 % numPartitions;
}
}
启用方式:
job.setPartitionerClass(FirstCharPartitioner.class);
job.setNumReduceTasks(3);
5. MR 在 YARN 上怎么跑:容错与重试
运行时一般会有:
-
负责提交与查询的 Client
-
负责切分任务、申请资源、监控状态的 ApplicationMaster
-
Task 失败会触发重试(可配置次数)
思路上记住:失败不是结束,而是重跑,这就是大规模集群能稳定跑作业的关键。
6. 推测执行:提速,但也可能出问题
推测执行的思路是:
如果某个 Task 明显慢于平均水平,会启动"备份 Task",谁先完成就用谁结果。
不建议开启的场景(常见翻车点):
-
任务有明显数据倾斜(慢是正常现象)
-
Reducer 做外部写入(可能导致重复写)
7. Hive:用 SQL 做离线分析
Hive 的核心体验:写 HQL(类 SQL)做离线统计 。
适合:
-
大量结构化/半结构化日志
-
典型的离线报表:PV、UV、TopN、分组聚合
-
多维分析(配合分区、分桶)
8. Hive 常用落地 SQL:分区、统计、词频
8.1 建表(TSV 示例)
CREATE DATABASE IF NOT EXISTS ods;
USE ods;
CREATE TABLE IF NOT EXISTS ods_access_log (
ip STRING,
ts STRING,
method STRING,
url STRING,
status INT,
ua STRING
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
STORED AS TEXTFILE;
8.2 分区表(按天)
CREATE TABLE IF NOT EXISTS dwd_access_log (
ip STRING,
method STRING,
url STRING,
status INT,
ua STRING
)
PARTITIONED BY (dt STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
STORED AS TEXTFILE;
LOAD DATA INPATH '/data/logs/2023-11-01.log'
INTO TABLE dwd_access_log
PARTITION (dt='2023-11-01');
8.3 PV / UV
-- PV
SELECT dt, COUNT(*) AS pv
FROM dwd_access_log
WHERE dt='2023-11-01'
GROUP BY dt;
-- UV(以 ip 为例)
SELECT dt, COUNT(DISTINCT ip) AS uv
FROM dwd_access_log
WHERE dt='2023-11-01'
GROUP BY dt;
8.4 词频(explode + split)
SELECT word, COUNT(*) AS cnt
FROM doc
LATERAL VIEW explode(split(text, ' ')) t AS word
GROUP BY word
ORDER BY cnt DESC;
9. Pig:数据流脚本做 ETL
Pig 更像"数据处理流水线脚本"。你描述"先读 → 再过滤 → 再分组 → 再输出",它会翻译成底层作业执行。
9.1 Pig Latin 示例:统计 URL PV
raw = LOAD '/data/logs/2023-11-01.log'
USING PigStorage('\t')
AS (ip:chararray, ts:chararray, method:chararray, url:chararray, status:int, ua:chararray);
ok = FILTER raw BY status == 200;
g = GROUP ok BY url;
pv_by_url = FOREACH g GENERATE group AS url, COUNT(ok) AS pv;
STORE pv_by_url INTO '/out/pv_by_url' USING PigStorage('\t');
10. Hive vs Pig:怎么选
| 对比项 | Hive | Pig |
|---|---|---|
| 思维方式 | "我要什么结果"(SQL) | "我怎么处理数据"(数据流) |
| 上手门槛 | 会 SQL 就能写 | 更像脚本/流程 |
| 典型用途 | 离线统计、报表、多维分析 | ETL、清洗、复杂流程拼接 |
| 适合人群 | 分析/数仓更常用 | 处理链路更常用 |
简单建议:
-
统计/报表:优先 Hive
-
清洗/流程:优先 Pig
Tips:常见坑与建议
-
Combiner 用对场景:sum/min/max 这类可叠加才稳。
-
自定义 Partitioner 记得配 Reduce 数量:不然分区没意义。
-
推测执行别乱开:任务有外部写入时,容易出现重复副作用。
-
Hive 分区一定要用:不然日志表越大越慢,扫全表很痛。
-
先小数据验证再上大数据:先跑通流程,再扩到全量,排错效率高很多。
总结
MapReduce 提供了稳定的离线批处理能力;Hive 用 SQL 把常见统计变得更简单;Pig 用数据流脚本把 ETL 流水线写得更顺手。掌握它们的核心思想后,很多大数据实验题基本轻松完成。