MapReduce / Hive / Pig :从底层批处理到 SQL/脚本落地

最近整理大数据离线计算这块内容时,我最大的感受是: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:常见坑与建议

  1. Combiner 用对场景:sum/min/max 这类可叠加才稳。

  2. 自定义 Partitioner 记得配 Reduce 数量:不然分区没意义。

  3. 推测执行别乱开:任务有外部写入时,容易出现重复副作用。

  4. Hive 分区一定要用:不然日志表越大越慢,扫全表很痛。

  5. 先小数据验证再上大数据:先跑通流程,再扩到全量,排错效率高很多。


总结

MapReduce 提供了稳定的离线批处理能力;Hive 用 SQL 把常见统计变得更简单;Pig 用数据流脚本把 ETL 流水线写得更顺手。掌握它们的核心思想后,很多大数据实验题基本轻松完成。

相关推荐
V1ncent Chen3 小时前
从零学SQL 02 MySQL架构介绍
数据库·sql·mysql·架构·数据分析
大母猴啃编程3 小时前
MySQL内置函数
数据库·sql·mysql·adb
@atweiwei3 小时前
MySQL vs MongoDB 深度对比(底层存储数据结构与并发控制篇)
数据结构·数据库·后端·sql·mysql·mongodb·个人开发
nbsaas-boot3 小时前
SQL JOIN 图解说明
android·数据库·sql
小鸡脚来咯3 小时前
SQL 语法面试考点
数据库·sql·oracle
V1ncent Chen3 小时前
从零学SQL 04 MySQL Workbench用法简介
数据库·sql·mysql·数据分析
IT从业者张某某3 小时前
Docker部署Hadoop-01-Docker安装
hadoop·docker·eureka
Z1eaf_complete4 小时前
SQL注入绕过详解与防御机制
数据库·sql
CN.LG4 小时前
SQLiteStudio 介绍
sql·sqlite·c#