HDFS常用shell命令+MapReduce java编程+HBase常用shell命令+Spark python编程(RDD+df)

本文包含详细的HDFS常用shell命令+MapReduce java编程+HBase常用shell命令+Spark python编程(RDD+df),
本文档纯属个人整理,为了应对大数据期末考试的20分程序填空和20分手撕代码题(一道mapreduce
java,一道spark python)

HDFS Shell 指令说明

文件系统操作命令

-ls 命令

hdfs dfs -ls [选项] <路径>
  • 功能:查看指定路径的当前目录结构
  • 常用选项:
    • -R:递归显示所有子目录
    • -d:仅列出目录本身,不列出内容
    • -h:人类可读格式显示文件大小
    • -t:按修改时间排序,最近的在前
  • 示例:hdfs dfs -ls /user/hadoop
  • 输出:显示目录下的文件和子目录(非递归)

-du 命令

hdfs dfs -du [选项] <路径>
  • 功能:统计目录下各文件/子目录的大小(单位:字节)
  • 常用选项:
    • -s:显示汇总值
    • -h:以人类可读格式显示大小
    • -r:递归统计子目录
    • -t:按大小排序输出
  • 输出格式:大小 副本大小 路径
  • 示例:hdfs dfs -du /data

-count 命令

hdfs dfs -count [选项] <路径>
  • 功能:统计路径下的文件/目录数量
  • 常用选项:
    • -q:显示配额信息(配额/剩余/空间)
    • -h:以人类可读格式显示大小
    • -t:显示文件类型信息
    • -u:显示每个用户拥有的文件/目录数
  • 输出格式:目录数 文件数 总大小 路径
  • 示例:hdfs dfs -count /user

文件操作命令

-mv 命令

hdfs dfs -mv [选项] <源路径> <目的路径>
  • 功能:移动文件/目录(HDFS内部)
  • 常用选项:
    • -f:强制覆盖已存在的目标文件
    • -p:保留文件元数据(时间戳、权限等)
    • -t:指定目标目录
    • -d:允许迁移非空目录
  • 示例:hdfs dfs -mv /data/old.log /backup/

-cp 命令

hdfs dfs -cp [选项] <源路径> <目的路径>
  • 功能:复制文件/目录(HDFS内部)
  • 常用选项:
    • -f:强制覆盖目标文件
    • -p:保留文件属性(时间戳、所有权、权限)
    • -r:递归复制目录
    • -d:跳过创建临时文件
    • -t:指定目标目录
  • 示例:hdfs dfs -cp -p /data/file1 /backup/

-rm 命令

hdfs dfs -rm [选项] <路径>
  • 功能:删除文件/空白目录
  • 常用选项:
    • -f:强制删除,不提示
    • -r:递归删除
    • -skipTrash:直接删除(不进入回收站)
    • -t:指定删除线程数
    • -d:仅删除空目录
  • 示例:hdfs dfs -rm /tmp/expired.log

本地与HDFS交互命令

-put 命令

hdfs dfs -put [选项] <本地文件1> <本地文件2> ... 
  • 功能:上传本地文件到HDFS
  • 常用选项:
    • -f:覆盖目标文件
    • -p:保留访问和修改时间、所有权和权限
    • -l:允许源为符号链接
    • -d:跳过创建临时文件
    • -t:指定线程数
  • 示例:hdfs dfs -put -f log1.log log2.log /data/

-copyFromLocal 命令

hdfs dfs -copyFromLocal [选项] <本地文件> 
  • 功能:与 -put 相同(语义化别名)
  • 常用选项:
    • -f:覆盖目标文件
    • -p:保留文件属性
    • -l:允许源为符号链接
    • -d:跳过创建临时文件
  • 示例:hdfs dfs -copyFromLocal -p data.csv /input/

-moveFromLocal 命令

hdfs dfs -moveFromLocal [选项] <本地文件> 
  • 功能:上传后删除本地文件
  • 常用选项:
    • -f:覆盖目标文件
    • -p:保留元数据
    • -t:指定线程数
    • -d:跳过创建临时文件
  • 示例:hdfs dfs -moveFromLocal -f temp.dat /storage/

-getmerge 命令

hdfs dfs -getmerge [选项]  <本地合并文件>
  • 功能:合并HDFS目录下所有文件到本地单个文件
  • 常用选项:
    • -nl:在每个文件末尾添加换行符
    • -skip-empty-file:跳过空文件
    • -r:递归处理子目录
    • -t:指定临时文件目录
  • 示例:hdfs dfs -getmerge -nl /logs/all_logs/ merged.log

-cat 命令

hdfs dfs -cat [选项] 
  • 功能:查看文件内容
  • 常用选项:
    • -ignoreCrc:忽略CRC校验
    • -t:指定最大显示行数
    • -h:显示头部信息
    • -f:包含文件名
  • 示例:hdfs dfs -cat /output/result.txt

-text 命令

hdfs dfs -text [选项] 
  • 功能:自动解压并查看文本/压缩文件
  • 常用选项:
    • -a:显示所有内容,包括非文本字节
    • -t:限制显示的最大行数
    • -d:显示详细解压信息
    • -r:递归处理目录
  • 支持格式:gzip、snappy等
  • 示例:hdfs dfs -text -t 100 /data/compressed.gz

-copyToLocal 命令

hdfs dfs -copyToLocal [选项]  <本地路径>
  • 功能:下载HDFS文件到本地
  • 常用选项:
    • -p:保留访问和修改时间、所有权、权限
    • -ignoreCrc:跳过校验
    • -crc:附带CRC校验文件
    • -f:覆盖目标文件
    • -d:创建父目录
    • -t:指定线程数
  • 示例:hdfs dfs -copyToLocal -p /output/results ./

-moveToLocal 命令

hdfs dfs -moveToLocal [选项]  <本地路径>
  • 功能:下载后删除HDFS文件(实验性功能)
  • 常用选项:
    • -crc:附带CRC校验文件
    • -p:保留元数据
    • -f:覆盖目标文件
    • -d:创建父目录
  • 注意:实际可能不可用
  • 示例:hdfs dfs -moveToLocal -f /tmp/file.txt ./

目录管理命令

-mkdir 命令

hdfs dfs -mkdir [选项] 
  • 功能:创建目录
  • 常用选项:
    • -p:递归创建父目录
    • -m:设置目录权限模式
    • -t:指定线程数
    • -d:设置目录配额
  • 示例:hdfs dfs -mkdir -p -m 755 /user/newdir/subdir

高级操作命令

-setrep 命令

hdfs dfs -setrep [选项] <副本数> <路径>
  • 功能:修改文件/目录的副本数
  • 常用选项:
    • -R:递归操作
    • -w:等待副本调整完成
    • -t:指定超时时间(毫秒)
    • -m:仅修改内存中的元数据
    • -d:在下一次写操作时异步更新
  • 示例:hdfs dfs -setrep -R -w 3 /critical_data

-touchz 命令

hdfs dfs -touchz [选项] <文件路径>
  • 功能:创建0字节空白文件(类似Linux touch)
  • 常用选项:
    • -a:仅更新文件的访问时间(atime)
    • -m:仅更新文件的修改时间(mtime)
    • -t:指定时间戳,格式为 [[CC]YY]MMDDhhmm[.ss]
    • -r:将指定文件的时间戳复制到目标文件
    • -d:指定日期和时间,格式为 YYYY-MM-DD HH:MM:SS
  • 示例:
    • 基本用法:hdfs dfs -touchz /tmp/lock.file
    • 设置特定时间:hdfs dfs -touchz -d "2023-12-01 08:30:00" /tmp/dated.file
    • 仅更新访问时间:hdfs dfs -touchz -a /data/logs/current.log

-stat 命令

hdfs dfs -stat [选项] [format] <路径>
  • 功能:显示文件统计信息
  • 常用选项:
    • -t:以可读时间格式显示
    • -r:递归显示目录内容
    • -a:显示所有属性
    • -m:仅显示修改时间
  • 格式占位符:
    • %b:文件大小(块大小)
    • %y:修改时间
    • %r:副本数
    • %o:块大小
    • %n:文件名
    • %a:访问时间
    • %F:文件类型
  • 示例:hdfs dfs -stat -t "%y %n %r" /data/file

-tail 命令

hdfs dfs -tail [选项] 
  • 功能:查看文件尾部内容
  • 常用选项:
    • -f:实时追踪(类似Linux tail -f)
    • -s:间隔时间(以秒为单位,用于-f模式)
    • -n:显示的行数
    • -d:显示详细信息
    • -t:指定超时时间
  • 示例:
    • 基本用法:hdfs dfs -tail /logs/app.log
    • 实时跟踪:hdfs dfs -tail -f -s 5 /logs/app.log
    • 显示末尾20行:hdfs dfs -tail -n 20 /logs/app.log

权限管理命令

-chmod 命令

hdfs dfs -chmod [选项] <权限模式> <路径>
  • 功能:修改文件/目录权限(八进制或符号模式)
  • 常用选项:
    • -R:递归操作
    • -f:静默模式,不显示错误信息
    • -t:指定超时时间
    • -d:仅修改目录权限,不修改文件
  • 示例:
    • 基本用法:hdfs dfs -chmod 755 /public
    • 递归修改:hdfs dfs -chmod -R 644 /data/logs
    • 符号模式:hdfs dfs -chmod -R u+x /scripts

-chown 命令

hdfs dfs -chown [选项] [用户][:组] <路径>
  • 功能:修改属主和属组
  • 常用选项:
    • -R:递归操作
    • -f:静默模式,不显示错误信息
    • -t:指定超时时间
    • -d:仅修改目录权限,不修改文件
  • 示例:
    • 修改用户:hdfs dfs -chown hadoop /data/file.txt
    • 递归修改用户和组:hdfs dfs -chown -R hadoop:analysts /data/reports

-chgrp 命令

hdfs dfs -chgrp [选项] <组名> <路径>
  • 功能:修改文件/目录的所属组
  • 常用选项:
    • -R:递归操作
    • -f:静默模式,不显示错误信息
    • -t:指定超时时间
    • -d:仅修改目录所属组,不修改文件
  • 示例:
    • 基本用法:hdfs dfs -chgrp datagroup /data/file.txt
    • 递归修改:hdfs dfs -chgrp -R admin /secure

帮助命令

-help 命令

hdfs dfs -help [选项] [具体命令]
  • 功能:显示命令帮助信息
  • 常用选项:
    • -a:显示所有可用命令
    • -d:显示详细帮助信息
    • -r:显示相关命令
    • -t:以简洁格式显示
  • 示例:
    • 查看特定命令:hdfs dfs -help rm
    • 查看所有命令:hdfs dfs -help -a

注意:所有命令中的<路径>默认为HDFS路径,本地路径需显式指定协议(如file:///)

并非所有选项在所有HDFS版本中都可用,请根据您的具体版本参考官方文档确认支持的选项。

MapReduce (Java)

1. MapReduce 介绍

MapReduce思想在生活中处处可见。或多或少都曾接触过这种思想。MapReduce的思想核心是"分而治之",适用于大量复杂的任务处理场景(大规模数据处理场景)。

  • Map负责"分",即把复杂的任务分解为若干个"简单的任务"来并行处理。可以进行拆分的前提是这些小任务可以并行计算,彼此间几乎没有依赖关系。
  • Reduce负责"合",即对map阶段的结果进行全局汇总。
  • MapReduce运行在yarn集群
    1. ResourceManager
    2. NodeManager

这两个阶段合起来正是MapReduce思想的体现。

还有一个比较形象的语言解释MapReduce:

我们要数图书馆中的所有书。你数1号书架,我数2号书架。这就是"Map"。我们人越多,数书就更快。

现在我们到一起,把所有人的统计数加在一起。这就是"Reduce"。

1.1. MapReduce 设计构思

MapReduce是一个分布式运算程序的编程框架,核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在Hadoop集群上。

MapReduce设计并提供了统一的计算框架,为程序员隐藏了绝大多数系统层面的处理细节。为程序员提供一个抽象和高层的编程接口和框架。程序员仅需要关心其应用层的具体计算问题,仅需编写少量的处理应用本身计算问题的程序代码。如何具体完成这个并行计算任务所相关的诸多系统层细节被隐藏起来,交给计算框架去处理:

Map和Reduce为程序员提供了一个清晰的操作接口抽象描述。MapReduce中定义了如下的Map和Reduce两个抽象的编程接口,由用户去编程实现.Map和Reduce,MapReduce处理的数据类型是键值对。

  • Map: (k1; v1) → [(k2; v2)]

  • Reduce: (k2; [v2]) → [(k3; v3)]

一个完整的mapreduce程序在分布式运行时有三类实例进程:

  1. MRAppMaster 负责整个程序的过程调度及状态协调
  2. MapTask 负责map阶段的整个数据处理流程
  3. ReduceTask 负责reduce阶段的整个数据处理流程

2. MapReduce 编程规范

MapReduce 的开发一共有八个步骤, 其中 Map 阶段分为 2 个步骤,Shuffle 阶段 4 个步骤,Reduce 阶段分为 2 个步骤

Map 阶段 2 个步骤

  1. 设置 InputFormat 类, 将数据切分为 Key-Value**(K1和V1)** 对, 输入到第二步
  2. 自定义 Map 逻辑, 将第一步的结果转换成另外的 Key-Value(K2和V2) 对, 输出结果

Shuffle 阶段 4 个步骤

  1. 对输出的 Key-Value 对进行分区
  2. 对不同分区的数据按照相同的 Key 排序
  3. (可选) 对分组过的数据初步规约, 降低数据的网络拷贝
  4. 对数据进行分组, 相同 Key 的 Value 放入一个集合中

Reduce 阶段 2 个步骤

  1. 对多个 Map 任务的结果进行排序以及合并, 编写 Reduce 函数实现自己的逻辑, 对输入的 Key-Value 进行处理, 转为新的 Key-Value(K3和V3)输出
  2. 设置 OutputFormat 处理并保存 Reduce 输出的 Key-Value 数据

3. MapReduce 代码示例

例子1:WordMerger - 单词合并去重

package com.wordmerger;

import java.io.IOException;
import java.util.Properties;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class WordMerger {
    // Map类 - 将输入转为键值对,用于后续去重
    // Step 2: 自定义Map逻辑,将输入的每行文本作为键,空字符串作为值输出
    public static class Map extends Mapper<Object, Text, Text, Text> {
        private static Text text = new Text();

        public void map(Object key, Text value, Context context) throws IOException,
                InterruptedException {
            text = value; // 直接使用输入的Text值作为输出键
            context.write(text, new Text("")); // 输出键值对,值为空Text对象
        }
    }

    // Step 7: Reduce阶段 - 实现去重逻辑
    public static class Reduce extends Reducer<Text, Text, Text, Text> {
        public void reduce(Text key, Iterable<Text> values, Context context) throws IOException,
                InterruptedException {
            // Shuffle阶段已将相同键的值分组,这里只需输出每个键一次即可实现去重
            context.write(key, new Text("")); // 对于每个唯一的键,只输出一次
        }
    }

    public static void main(String[] args) throws Exception {
        Properties properties = System.getProperties();
        properties.setProperty("HADOOP_USER_NAME", "bduser");

        Configuration conf = new Configuration();
        conf.set("fs.defaultFS", "hdfs://10.40.2.147:9000");
        
        String[] otherArgs = new String[] { 
            "/hadoop2/wyh/input2",
            "/hadoop2/wyh/output" 
        };

        if (otherArgs.length != 2) {
            System.err.println("Usage: wordmerger and duplicate removal  ");
            System.exit(2);
        }

        // 配置Job
        Job job = Job.getInstance(conf, "wordmerger and duplicate removal");
        job.setJarByClass(WordMerger.class);
        
        // Step 2: 设置Mapper类
        job.setMapperClass(Map.class);
        
        // Step 7: 设置Reducer类
        job.setReducerClass(Reduce.class);
        
        // 设置输出类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(Text.class);

        // Step 1: 设置输入路径和InputFormat (默认为TextInputFormat)
        FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
        
        // Step 8: 设置输出路径和OutputFormat (默认为TextOutputFormat)
        FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));

        // 提交作业并等待完成
        System.exit(job.waitForCompletion(true) ? 0 : 1);
    }
}

例子2:WordCount - 单词计数和句子统计

package com.wordcount;

import java.io.IOException;
import java.util.Properties;

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.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;

public class WordCount {
    // 用于区分字符和句子的标识
    public static final String CHAR_PREFIX = "CHAR:";
    public static final String SENTENCE_PREFIX = "SENT:";

    // Step 2: 自定义Map逻辑,处理输入并输出带前缀的键值对
    public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable> {
        private final static IntWritable one = new IntWritable(1);
        private Text outKey = new Text();

        public void map(Object key, Text value, Context context) throws IOException,
                InterruptedException {
            String line = value.toString();

            // 处理每行作为一个句子
            outKey.set(SENTENCE_PREFIX + line);
            context.write(outKey, one); // 输出(句子前缀+行内容, 1)键值对

            // 处理每个字符
            char[] chars = line.toCharArray();
            for (char c : chars) {
                if (!Character.isWhitespace(c)) {
                    outKey.set(CHAR_PREFIX + c);
                    context.write(outKey, one); // 输出(字符前缀+字符, 1)键值对
                }
            }
        }
    }

    // Step 3-6: Shuffle阶段会自动处理分区、排序、规约和分组
    
    // Step 7: Reduce阶段 - 汇总统计结果
    public static class IntSumReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
        private IntWritable result = new IntWritable();
        private Text outputKey = new Text();

        public void reduce(Text key, Iterable<IntWritable> values, Context context)
                throws IOException, InterruptedException {
            // 对每个键对应的所有值进行求和
            int sum = 0;
            for (IntWritable val : values) {
                sum += val.get();
            }

            String keyStr = key.toString();

            // 对于句子,只输出出现次数大于1的
            if (keyStr.startsWith(SENTENCE_PREFIX)) {
                if (sum > 1) {
                    outputKey.set(keyStr.substring(SENTENCE_PREFIX.length()));
                    result.set(sum);
                    context.write(outputKey, result);
                }
            }
            // 对于字符,正常输出
            else if (keyStr.startsWith(CHAR_PREFIX)) {
                outputKey.set(keyStr.substring(CHAR_PREFIX.length()));
                result.set(sum);
                context.write(outputKey, result);
            }
        }
    }

    public static void main(String[] args) throws Exception {
        Properties properties = System.getProperties();
        properties.setProperty("HADOOP_USER_NAME", "bduser");

        Configuration conf = new Configuration();
        conf.set("fs.defaultFS", "hdfs://10.40.2.147:9000");
        
        String[] otherArgs = new String[] { 
            "/hadoop2/wyh/input3",
            "/hadoop2/wyh/output4" 
        };

        if (otherArgs.length < 2) {
            System.err.println("Usage: wordcount  [...] ");
            System.exit(2);
        }

        // 配置Job
        Job job = Job.getInstance(conf, "word count");
        job.setJarByClass(WordCount.class);
        
        // Step 2: 设置Mapper类
        job.setMapperClass(TokenizerMapper.class);
        
        // Step 7: 设置Reducer类
        job.setReducerClass(IntSumReducer.class);

        // Step 1: 设置InputFormat
        job.setInputFormatClass(TextInputFormat.class);
        
        // 设置输出类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        // Step 1: 添加输入路径
        for (int i = 0; i < otherArgs.length - 1; ++i) {
            FileInputFormat.addInputPath(job, new Path(otherArgs[i]));
        }

        // Step 8: 设置输出路径和OutputFormat(默认TextOutputFormat)
        FileOutputFormat.setOutputPath(job, new Path(otherArgs[otherArgs.length - 1]));
        
        // 提交作业并等待完成
        System.exit(job.waitForCompletion(true) ? 0 : 1);
    }
}

例子3:SecondarySort - 二次排序

package com.secondarysort;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Properties;
import java.util.StringTokenizer;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.RawComparator;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Partitioner;
import org.apache.hadoop.mapreduce.Reducer;

/**
 * 这是一个Hadoop Map/Reduce应用示例。它读取包含每行两个整数的文本输入文件。
 * 输出按第一个数字和第二个数字排序,并按第一个数字分组。
 */
public class SecondarySort {

    // 自定义WritableComparable类型,实现二次排序的关键
    public static class IntPair implements WritableComparable<IntPair> {
        private int first = 0;
        private int second = 0;

        // 设置整数对的值
        public void set(int left, int right) {
            first = left;
            second = right;
        }

        // 获取第一个元素
        public int getFirst() {
            return first;
        }

        // 获取第二个元素
        public int getSecond() {
            return second;
        }

        // 实现从DataInput读取字段的方法
        @Override
        public void readFields(DataInput in) throws IOException {
            first = in.readInt() + Integer.MIN_VALUE;
            second = in.readInt() + Integer.MIN_VALUE;
        }

        // 实现向DataOutput写入字段的方法
        @Override
        public void write(DataOutput out) throws IOException {
            out.writeInt(first - Integer.MIN_VALUE);
            out.writeInt(second - Integer.MIN_VALUE);
        }

        // 计算哈希值
        @Override
        public int hashCode() {
            return first * 157 + second;
        }

        // 实现equals方法,判断两个IntPair是否相等
        @Override
        public boolean equals(Object right) {
            if (right instanceof IntPair) {
                IntPair r = (IntPair) right;
                return r.first == first && r.second == second;
            } else {
                return false;
            }
        }

        // Step 4: 自定义比较器,用于排序
        public static class Comparator extends WritableComparator {
            public Comparator() {
                super(IntPair.class);
            }

            // 比较两个序列化的IntPair对象
            public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {
                return compareBytes(b1, s1, l1, b2, s2, l2);
            }
        }

        // 注册IntPair的比较器
        static {
            WritableComparator.define(IntPair.class, new Comparator());
        }

        // 实现compareTo方法,定义排序规则
        @Override
        public int compareTo(IntPair o) {
            // 先按first排序,相同时按second排序
            if (first != o.first) {
                return first < o.first ? -1 : 1;
            } else if (second != o.second) {
                return second < o.second ? -1 : 1;
            } else {
                return 0;
            }
        }
    }

    // Step 3: 自定义分区器,决定数据发送到哪个Reducer
    public static class FirstPartitioner extends Partitioner<IntPair, IntWritable> {
        @Override
        public int getPartition(IntPair key, IntWritable value, int numPartitions) {
            // 只根据第一个整数决定分区
            return Math.abs(key.getFirst() * 127) % numPartitions;
        }
    }

    // Step 6: 自定义分组比较器,决定哪些键会被分到同一组传给Reducer
    public static class FirstGroupingComparator implements RawComparator<IntPair> {
        // 比较序列化格式
        @Override
        public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {
            // 只比较第一个整数部分(4字节)
            return WritableComparator.compareBytes(b1, s1, Integer.SIZE / 8, b2, s2, Integer.SIZE / 8);
        }

        // 比较对象格式
        @Override
        public int compare(IntPair o1, IntPair o2) {
            // 只比较第一个值
            int l = o1.getFirst();
            int r = o2.getFirst();
            return l == r ? 0 : (l < r ? -1 : 1);
        }
    }

    // Step 2: 自定义Map逻辑
    public static class MapClass extends Mapper<LongWritable, Text, IntPair, IntWritable> {
        private final IntPair key = new IntPair();
        private final IntWritable value = new IntWritable();

        @Override
        public void map(LongWritable inKey, Text inValue, Context context) throws IOException,
                InterruptedException {
            // 从每行文本中解析出两个整数
            StringTokenizer itr = new StringTokenizer(inValue.toString());
            int left = 0;
            int right = 0;
            if (itr.hasMoreTokens()) {
                left = Integer.parseInt(itr.nextToken());
                if (itr.hasMoreTokens()) {
                    right = Integer.parseInt(itr.nextToken());
                }
                // 创建键值对: ((left, right), right)
                key.set(left, right);
                value.set(right);
                context.write(key, value);
            }
        }
    }

    // Step 7: 自定义Reduce逻辑
    public static class Reduce extends Reducer<IntPair, IntWritable, Text, IntWritable> {
        private static final Text SEPARATOR = new Text("-----------------------------------------------");
        private final Text first = new Text();

        @Override
        public void reduce(IntPair key, Iterable<IntWritable> values, Context context)
                throws IOException, InterruptedException {
            // 输出分隔符
            context.write(SEPARATOR, null);
            // 将第一个整数转为文本
            first.set(Integer.toString(key.getFirst()));
            // 遍历所有值并输出
            for (IntWritable value : values) {
                context.write(first, value);
            }
        }
    }

    public static void main(String[] args) throws Exception {
        Properties properties = System.getProperties();
        properties.setProperty("HADOOP_USER_NAME", "bduser");

        Configuration conf = new Configuration();
        conf.set("fs.defaultFS", "hdfs://10.40.2.147:9000");
        
        String[] otherArgs = new String[] { 
            "/hadoop2/wyh/input2",
            "/hadoop2/wyh/output2" 
        };

        if (otherArgs.length != 2) {
            System.err.println("Usage: secondarysort  ");
            System.exit(2);
        }

        // 配置Job
        Job job = Job.getInstance(conf, "secondary sort");
        job.setJarByClass(SecondarySort.class);
        
        // Step 2: 设置Mapper类
        job.setMapperClass(MapClass.class);
        
        // Step 7: 设置Reducer类
        job.setReducerClass(Reduce.class);

        // Step 3: 设置分区器
        job.setPartitionerClass(FirstPartitioner.class);
        
        // Step 6: 设置分组比较器
        job.setGroupingComparatorClass(FirstGroupingComparator.class);

        // 设置Map输出类型
        job.setMapOutputKeyClass(IntPair.class);
        job.setMapOutputValueClass(IntWritable.class);

        // 设置Reduce输出类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        // Step 1: 设置输入路径和InputFormat(默认TextInputFormat)
        FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
        
        // Step 8: 设置输出路径和OutputFormat(默认TextOutputFormat)
        FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));

        // 提交作业并等待完成
        System.exit(job.waitForCompletion(true) ? 0 : 1);
    }
}

Hbase (Shell)

HBase 数据组织结构

数据模型层级

层级 说明
表(Table) 存储数据的逻辑单元,由多行组成。
行(Row) 由 行键(Row Key) 唯一标识,按字典序排序。
列族(Column Family) 列的集合,必须在创建表时定义(如 cf1, cf2)。列族存储在同一个 HFile 中,影响物理存储。
列(Column) 动态添加,格式为 列族:列族限定符(如 cf1:name, cf1:age)。
单元格(Cell) 由 行键 + 列族:列名 + 时间戳 唯一确定,存储实际数据(如 value)。
时间戳(Timestamp) 数据版本控制,默认由系统自动生成,也可手动指定。

示例数据存储结构

在HBase中,数据按列族物理存储,每个单元格由(行键, 列族:列名, 时间戳)唯一确定:

Row Key Column Family & Column Timestamp Value
row1 cf1:name 1 “Alice”
row1 cf1:city 3 “Beijing”
row1 cf2:age 2 “25”
row2 cf1:name 4 “Bob”
row2 cf2:age 5 “30”

逻辑视图(用户视角):

Row Key cf1:name cf1:city cf2:age
row1 “Alice” “Beijing” “25”
row2 “Bob” - “30”

注意:

  • HBase是稀疏存储的,不存在的列不占用存储空间
  • 每个单元格可以有多个版本(不同时间戳)
  • 数据按行键字典序存储,按列族物理隔离

HBase 常用指令整理

表操作命令

功能 命令表达式 说明
创建表 create '表名', '列族1', '列族2', ... 可指定多个列族
禁用表 disable '表名' 删除表前必需操作
删除表 drop '表名' 必须先禁用表
查看表是否存在 exists '表名'
启用表 enable '表名' 重新启用已禁用的表

数据操作命令

功能 命令表达式 说明
插入/更新数据 put '表名', '行键', '列族:列名', '值' 不存在则插入,存在则更新
查询单行 get '表名', '行键' 获取整行数据
带条件查询 get '表名', '行键', {COLUMN => '列族:列名'} 查询特定列
删除数据 delete '表名', '行键', '列族:列名' 删除指定单元格
删除整行 deleteall '表名', '行键' 删除整行所有版本

扫描统计命令

功能 命令表达式 说明
全表扫描 scan '表名' 默认显示100条,可用LIMIT参数
列族扫描 scan '表名', {COLUMNS => '列族'} 只扫描指定列族
范围扫描 scan '表名', {STARTROW => '起始行', STOPROW => '结束行'} 左闭右开区间
记录计数 count '表名' 统计行数(可能不实时)
快速计数 count '表名', INTERVAL => 100000 每10万行显示进度

高级操作命令

功能 命令表达式 说明
查看表结构 describe '表名' 显示列族配置信息
修改表结构 alter '表名', {NAME => '列族', VERSIONS => 3} 需先禁用表
批量操作 exec '命令文件.hbase' 执行脚本文件
区域信息 status '表名' 查看表分区状态
快照操作 snapshot '表名', '快照名' 需先配置快照功能

HBase 常用操作实践

1. 验证表创建情况

可以使用多种命令查看数据库是否创建成功:

list 命令

以列表形式显示所有数据表:

hbase(main):002:0> list
describe 命令

查看表的结构:

hbase(main):003:0> describe 'student'
exists 命令

查询表是否存在:

hbase(main):004:0> exists 'student'
is_enabled 命令

查询表是否可用:

hbase(main):005:0> is_enabled 'student'

2. 向表中插入数据

使用 put 命令向表中插入数据。HBase 中的列是由列族前缀和列的名字组成的,以冒号间隔。

hbase(main):006:0> put 'student', 'row1', 'score:a', 'value1'
hbase(main):007:0> put 'student', 'row2', 'score:b', 'value2'
hbase(main):008:0> put 'student', 'row3', 'score:c', 'value3'

3. 检查数据插入情况

可以使用 scanget 命令来检查插入结果:

scan 命令

扫描整个表:

hbase(main):009:0> scan 'student'
get 命令

获取特定行的数据:

hbase(main):010:0> get 'student', 'row1'

4. 删除表

删除表需要两步操作:

  1. 先使用 disable 命令使表无效:
hbase(main):011:0> disable 'student'
  1. 然后使用 drop 命令删除表:
hbase(main):012:0> drop 'student'

Spark (Python)

一、Spark RDD (弹性分布式数据集) 操作

1. RDD 基本概念

Resilient Distributed Dataset (弹性分布式数据集) 是 Spark 的核心概念,是一个不可变的、可分区的分布式数据集合,可以并行操作。RDD具有以下特点:

  • 弹性:容错性强,可以自动从节点故障中恢复
  • 分布式:数据分布在集群多个节点上
  • 数据集:可以存储各种类型的数据
  • 不可变性:创建后不能修改,只能通过转换操作生成新RDD
  • 延迟计算:转换操作不会立即执行,而是在动作操作时触发

2. 创建RDD

新函数

  • sc.parallelize():将本地集合转换为分布式RDD
  • sc.textFile():从文本文件创建RDD
2.1 从本地集合创建
# 创建包含整数的RDD
# sc.parallelize() 将本地Python列表转换为分布式数据集
intRDD = sc.parallelize([3, 1, 2, 5, 5])

# 查看RDD内容和类型
intRDD.collect()  # 输出: [3, 1, 2, 5, 5]
type(intRDD)      # 输出: pyspark.rdd.RDD

# 创建包含字符串的RDD
stringRDD = sc.parallelize(["浙江省", "天津市", "北京市", "上海市", "浙江省", "广东省"])
stringRDD.collect()  # 输出: ['浙江省', '天津市', '北京市', '上海市', '浙江省', '广东省']
2.2 从文件创建
# 从HDFS文本文件创建RDD
textFileRDD = sc.textFile("/hadoop2/zhangsan/inputEnglish/LICENSE.txt")
textFileRDD.take(2)  # 显示前两行内容

3. RDD 转换操作 (Transformations)

转换操作创建一个新的RDD,不会触发计算。这些操作都是惰性的,只有在动作操作时才会执行。

3.1 基本转换操作

新函数

  • map():对每个元素应用函数
  • filter():筛选满足条件的元素
  • distinct():去除重复元素
  • flatMap():对每个元素应用函数并扁平化结果
# map: 对每个元素应用函数
# 使用具名函数
def addOne(x): 
    return x + 1
intRDD.map(addOne).collect()  # 输出: [4, 2, 3, 6, 6]

# 使用匿名函数(lambda)
intRDD.map(lambda x: x + 1).collect()  # 输出: [4, 2, 3, 6, 6]

# 对字符串使用map
stringRDD.map(lambda x: "籍贯:" + x).collect()  # 输出: ['籍贯:浙江省', '籍贯:天津市', ...]

# filter: 筛选满足条件的元素
intRDD.filter(lambda x: x < 3).collect()  # 输出: [1, 2]
intRDD.filter(lambda x: x == 3).collect()  # 输出: [3]
intRDD.filter(lambda x: 1 < x and x < 5).collect()  # 输出: [3, 2]

# 字符串筛选
stringRDD.filter(lambda x: "市" in x).collect()  # 输出: ['天津市', '北京市', '上海市']

# distinct: 去除RDD中的重复元素
intRDD.distinct().collect()  # 输出: [1, 2, 3, 5]
stringRDD.distinct().collect()  # 输出: ['浙江省', '天津市', '北京市', '上海市', '广东省']
3.2 高级转换操作

新函数

  • randomSplit():随机分割RDD
  • groupBy():根据函数结果对元素分组
# randomSplit: 随机分割RDD为多个子RDD
sRDD = intRDD.randomSplit([0.4, 0.6])  # 按0.4:0.6的比例分割
sRDD[0].collect()  # 第一个RDD,约40%的数据
sRDD[1].collect()  # 第二个RDD,约60%的数据

# groupBy: 根据函数结果对元素分组
# 按奇偶性分组
gRDD = intRDD.groupBy(lambda x: "even" if (x % 2 == 0) else "odd").collect()
print(gRDD[0][0], sorted(gRDD[0][1]))  # 如: 'even' [2]
print(gRDD[1][0], sorted(gRDD[1][1]))  # 如: 'odd' [1, 3, 5, 5]

# 更复杂的groupBy示例:按除以2的余数分组
rdd = sc.parallelize([1, 1, 2, 3, 8, 6, 7, 5, 8])
result = rdd.groupBy(lambda x: x % 2).collect()
[[x, sorted(y)] for (x, y) in result]  # 输出如: [[0, [2, 6, 8, 8]], [1, [1, 1, 3, 5, 7]]]
3.3 多RDD转换操作

新函数

  • union():合并两个RDD,不去重
  • intersection():求两个RDD的交集,会去重
  • subtract():从第一个RDD中移除第二个RDD中的元素
  • cartesian():计算两个RDD的笛卡尔积
# 创建多个RDD进行集合操作
intRDD1 = sc.parallelize([3, 1, 2, 5, 5])
intRDD2 = sc.parallelize([5, 6])
intRDD3 = sc.parallelize([2, 7])

# union: 合并RDD (不去重)
intRDD1.union(intRDD2).union(intRDD3).collect()  # 输出: [3, 1, 2, 5, 5, 5, 6, 2, 7]

# intersection: 两个RDD的交集
intRDD1.intersection(intRDD2).collect()  # 输出: [5]

# subtract: 第一个RDD中存在但第二个RDD中不存在的元素
intRDD1.subtract(intRDD2).collect()  # 输出: [1, 2, 3]

# cartesian: 笛卡尔积
result = intRDD1.cartesian(intRDD2).collect()  # 输出类似: [(3,5), (3,6), (1,5), ...]

4. RDD 动作操作 (Actions)

动作操作会触发实际计算,返回值给驱动程序或写入外部存储系统。

新函数

  • collect():返回RDD中的所有元素到驱动程序
  • count():计算RDD元素数量
  • first():返回第一个元素
  • take(n):返回前n个元素
  • takeOrdered(n):返回自然顺序排序后的前n个元素
  • stats(), min(), max(), sum(), mean():统计函数
# collect: 收集所有元素到驱动程序
intRDD.collect()  # 输出: [3, 1, 2, 5, 5]

# count: 计算元素数量
intRDD.count()  # 输出: 5

# first: 获取第一个元素
intRDD.first()  # 输出: 3

# take: 获取前n个元素
intRDD.take(2)  # 输出: [3, 1]

# takeOrdered: 排序后取前n个
intRDD.takeOrdered(3)  # 输出: [1, 2, 3] (自然顺序)
intRDD.takeOrdered(3, key=lambda x: -x)  # 输出: [5, 5, 3] (降序)

# 统计函数
intRDD.stats()  # 输出统计摘要: (count: 5, mean: 3.2, stdev: 1.6, max: 5.0, min: 1.0)
intRDD.min()  # 输出: 1
intRDD.max()  # 输出: 5
intRDD.sum()  # 输出: 16
intRDD.mean()  # 输出: 3.2

5. 键值对 RDD 操作

键值对RDD是包含键值对的RDD,有特殊的操作方法。

新函数

  • keys(), values():获取所有键或值
  • mapValues():仅转换值,保持键不变
  • sortByKey():按键排序
  • reduceByKey():按键归约
  • groupByKey():按键分组
  • join(), leftOuterJoin(), rightOuterJoin():连接操作
  • countByKey():统计每个键的出现次数
  • lookup():查找指定键的所有值
# 创建键值对RDD
kvRDD = sc.parallelize([("1001", "肇事逃逸"), ("1001", "无证驾驶"), ("1003", "酒后驾驶"), ("1001", "酒驾")])

# 基本操作
kvRDD.keys().collect()  # 输出: ['1001', '1001', '1003', '1001']
kvRDD.values().collect()  # 输出: ['肇事逃逸', '无证驾驶', '酒后驾驶', '酒驾']

# 按键筛选
kvRDD.filter(lambda kv: kv[0] < "1002").collect()  # 输出: [('1001', '肇事逃逸'), ('1001', '无证驾驶'), ('1001', '酒驾')]

# 转换值
kvRDD.mapValues(lambda v: v + "情节严重").collect()  # 输出: [('1001', '肇事逃逸情节严重'), ...]

# 排序
kvRDD.sortByKey().collect()  # 按键升序
kvRDD.sortByKey(ascending=False).collect()  # 按键降序

# 按键归约,合并相同键的值
kvRDD.reduceByKey(lambda x, y: x + "," + y).collect()  # 输出: [('1003', '酒后驾驶'), ('1001', '肇事逃逸,无证驾驶,酒驾')]

# 连接操作
kvRDD2 = sc.parallelize([("1001", "于谦"), ("1002", "郭德纲")])
kvRDD.join(kvRDD2).collect()  # 内连接
kvRDD.leftOuterJoin(kvRDD2).collect()  # 左外连接
kvRDD.rightOuterJoin(kvRDD2).collect()  # 右外连接

# 动作操作
kvRDD.countByKey()  # 输出: defaultdict(int, {'1001': 3, '1003': 1})
kvRDD.lookup("1001")  # 输出: ['肇事逃逸', '无证驾驶', '酒驾']
KV = kvRDD.collectAsMap()  # 转换为字典(重复键会被覆盖)

6. WordCount 示例

WordCount是大数据处理的"Hello World"。以下是使用Spark RDD实现的词频统计:

# 1. 从文件读取文本
textFile = sc.textFile("/hadoop2/zhangsan/inputEnglish/LICENSE.txt")

# 2. 将文本分割为单词
stringRDD = textFile.flatMap(lambda line: line.split(" "))

# 3. 将每个单词映射为(单词,1)的键值对
wordPairs = stringRDD.map(lambda word: (word, 1))

# 4. 按键(单词)归约,统计每个单词出现次数
counts = wordPairs.reduceByKey(lambda x, y: x + y)

# 5. 保存结果
counts.saveAsTextFile("/spark/output/")

# 查看部分结果(可选)
counts.take(5)

二、Spark DataFrame 操作

1. DataFrame 基本概念

DataFrame是一种分布式的数据集合,组织成命名列的形式,概念上等同于关系数据库中的表。DataFrame提供了比RDD更高级的抽象,支持结构化数据处理和SQL查询。

主要特点:

  • 命名列和结构化数据
  • 优化的执行计划
  • 支持多种数据源
  • 支持SQL查询
  • 与机器学习库无缝集成

2. 创建DataFrame

新函数

  • SparkSession.builder.getOrCreate():获取SparkSession
  • createDataFrame():从RDD创建DataFrame
  • Row():创建命名行对象
2.1 从RDD创建DataFrame
# 1. 读取文本文件创建RDD
RawUserRDD = sc.textFile("/spark/user")

# 2. 解析数据,分割字段
userRDD = RawUserRDD.map(lambda line: line.split("|"))

# 3. 转换为Row对象(带列名)
from pyspark.sql import Row
user_Rows = userRDD.map(lambda p: Row(
    userid=int(p[0]),
    age=int(p[1]),
    gender=p[2],
    occupation=p[3]),
    zipcode=p[4]
)

# 4. 创建DataFrame
sqlContext = SparkSession.builder.getOrCreate()
user_df = sqlContext.createDataFrame(user_Rows)

# 5. 查看DataFrame结构和内容
user_df.printSchema()  # 显示结构
user_df.show(5)  # 显示前5行

3. DataFrame 基本操作

新函数

  • select():选择列
  • filter():筛选行
  • orderBy():排序
  • withColumn():添加或替换列
  • show():显示内容
# 为DataFrame创建别名(便于引用)
df = user_df.alias("df")

# select: 选择特定列
user_df_1 = df.select("userid", "occupation", "gender", "age")
user_df_1.show(5)

# 不同的列引用方式
df.select(df.userid, df.occupation, df.gender, df.age).show(5)  # 使用列引用
df[['userid', 'occupation', 'gender', 'age']].show(5)  # 使用列表

# filter: 筛选数据
# 方式1: 使用字符串表达式
df.filter("occupation='technician' and gender='M' and age=24").show()

# 方式2: 使用列引用和条件表达式
df.filter((df.occupation == 'technician') & (df.gender == 'M') & (df.age == 24)).show()

# orderBy: 排序
df.select("userid", "occupation", "gender", "age").orderBy("age").show(5)  # 升序
df.select("userid", "occupation", "gender", "age").orderBy(df.age.desc()).show(5)  # 降序

# 多字段排序
df.orderBy(["age", "gender"], ascending=[0, 1]).show(5)  # 年龄降序,性别升序

# 添加计算列
df.select("userid", "gender", "age", (2021 - df.age).alias("birthyear")).show(5)

4. Spark SQL 操作

新函数

  • registerTempTable()/createOrReplaceTempView():注册临时表
  • sqlContext.sql():执行SQL查询
# 注册临时表
user_df.registerTempTable("user_table")

# 执行SQL查询
# 统计总数
sqlContext.sql("SELECT count(*) as user_count FROM user_table").show()

# 选择特定列
sqlContext.sql("SELECT userid, occupation, gender, age FROM user_table").show(5)

# 添加计算列
sqlContext.sql("SELECT userid, gender, age, 2021 - age as birthyear FROM user_table").show(5)

# 条件过滤
sqlContext.sql('SELECT * FROM user_table WHERE occupation="technician" AND gender="M" AND age=24').show()

# 排序
sqlContext.sql("SELECT userid, occupation, gender, age FROM user_table ORDER BY age DESC").show(5)

# 分组聚合
sqlContext.sql("SELECT gender, occupation, COUNT(*) as count FROM user_table GROUP BY gender, occupation ORDER BY count DESC").show(5)

5. DataFrames 高级操作

新函数

  • groupBy():分组聚合
  • agg():聚合函数
  • join():连接操作
  • distinct():去重
# 导入聚合函数
from pyspark.sql.functions import count, avg, max, min, sum, desc

# 分组聚合
df.groupBy("gender").count().show()
df.groupBy("gender", "occupation").agg(count("*").alias("人数"), avg("age").alias("平均年龄")).show()

# 连接操作
# 假设有第二个DataFrame
occupation_df = sqlContext.createDataFrame([
    ("technician", "技术员"),
    ("scientist", "科学家"),
    ("engineer", "工程师")
], ["occupation", "chinese_name"])

# 执行连接
df.join(occupation_df, "occupation").select("userid", "occupation", "chinese_name", "gender").show()

# 去重操作
df.select("occupation").distinct().show()

你可能感兴趣的:(hdfs,mapreduce,java)