大数据平台技术:Spark实践

在IDEA中开发基于scala的wordcount程序(Scala+Maven)

参考林子雨教程和尚硅谷大数据视频
按照步骤来基本可以顺利搞定

运行jar包:

cd ~
/usr/local/spark/bin/spark-submit --class WordCount /home/hadoop/WordCount.jar

最后得到的结果截图:

基于Spark实现两个mp任务串联

使用的仍然是上一篇博客中的代码,两个mapreduce的job串联运行,第一个job进行分词和词频统计,第二个job统计共有多少词,job2的map把每行都输出成<sum,1>,reduce任务不做修改,结果即为所求单词总数。

import java.io.IOException;
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.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.util.GenericOptionsParser;

//启动mr的driver类
public class WordCount2 {

    //JOB1的map类,实现map函数
    public static class TokenizerMapper extends
            Mapper<Object, Text, Text, IntWritable> {
        //暂存每个传过来的词频计数,均为1,省掉重复申请空间
        private final static IntWritable one = new IntWritable(1);
        //暂存每个传过来的词的值,省掉重复申请空间
        private Text word = new Text();

        //核心map方法的具体实现,逐个<key,value>对去处理. 在词频统计操作中,value是文本中的一行,key是文本中的行号
        public void map(Object key, Text value, Context context)
                throws IOException, InterruptedException {
            //用每行的字符串值初始化StringTokenizer
            StringTokenizer itr = new StringTokenizer(value.toString());
            //循环取得每个空白符分隔出来的每个元素(单词)
            while (itr.hasMoreTokens()) {
                //将取得出的每个元素放到word Text对象中
                word.set(itr.nextToken());
                //通过context对象,将map的输出逐个输出
                context.write(word, one);
            }
        }
    }

    //JOB1,JOB2可以公用的reduce类,实现reduce函数
    public static class IntSumReducer extends
            Reducer<Text, IntWritable, Text, IntWritable> {
        private IntWritable result = new IntWritable();

        //核心reduce方法的具体实现,逐个<key,List(v1,v2)>去处理
        public void reduce(Text key, Iterable<IntWritable> values,
                           Context context) throws IOException, InterruptedException {
            //暂存每个key组中计算总和
            int sum = 0;
            //加强型for,依次获取迭代器中的每个元素值,即为一个一个的词频数值
            for (IntWritable val : values) {
                //将key组中的每个词频数值sum到一起
                sum += val.get();
            }
            //将该key组sum完成的值放到result IntWritable中,使可以序列化输出
            result.set(sum);
            //将计算结果逐条输出
            context.write(key, result);
        }
    }

    //JOB2的map类,实现map函数
    public static class TokenizerMapper2 extends
            Mapper<Object, Text, Text, IntWritable> {
        //暂存每个传过来的词频计数,均为1,省掉重复申请空间
        private final static IntWritable one = new IntWritable(1);
        //暂存每个传过来的词的值,省掉重复申请空间
        private Text word = new Text();

        //核心map方法的具体实现,逐个<key,value>对去处理. 在词频统计操作中,value是文本中的一行,key是文本中的行号
        public void map(Object key, Text value, Context context)
                throws IOException, InterruptedException {
            //用每行的字符串值初始化StringTokenizer
            //主要是更改了这个地方,把分隔符唯一指定成换行符
                word.set("sum");
                //通过context对象,将map的输出逐个输出
                context.write(word, one);
        }
    }

    //启动mr的driver方法
    public static void main(String[] args) throws Exception {
        //得到集群配置参数
        Configuration conf = new Configuration();
        String[] otherArgs = (new GenericOptionsParser(conf,args)).getRemainingArgs();
        if(otherArgs.length<2){
            System.out.println("缺少变量:wordcount<in>[<in>...]<out>");
            System.exit(2);
        }
        ///输入格式要求为:n个输入,1个job1的输出,1个job2的输出(最终输出)
        第一个job
        //设置到本次的job实例中
        Job job1 = Job.getInstance(conf, "张驰のWordCount");
        //指定本次执行的主类是WordCount
        job1.setJarByClass(WordCount.class);
        //指定map类
        job1.setMapperClass(TokenizerMapper.class);
        //指定combiner类,要么不指定,如果指定,一般与reducer类相同
        job1.setCombinerClass(IntSumReducer.class);
        //指定reducer类
        job1.setReducerClass(IntSumReducer.class);
//        //指定job输出的key和value的类型,如果map和reduce输出类型不完全相同,需要重新设置map的output的key和value的class类型
        job1.setOutputKeyClass(Text.class);
        job1.setOutputValueClass(IntWritable.class);
        //指定输入数据的路径
        for(int i=0;i<otherArgs.length-2;++i){
            FileInputFormat.addInputPath(job1,new Path(otherArgs[i]));
        }
        //指定输出路径,并要求该输出路径一定是不存在的
        FileOutputFormat.setOutputPath(job1, new Path(otherArgs[otherArgs.length-1]));


        //第二个job,用来统计有多少个单词
        Job job2 = Job.getInstance(conf, "张驰のWordCount~part2");
        //指定本次执行的主类是WordCount
        job2.setJarByClass(WordCount.class);
        //指定map类
        job2.setMapperClass(TokenizerMapper2.class);
        //指定combiner类,要么不指定,如果指定,一般与reducer类相同
        job2.setCombinerClass(IntSumReducer.class);
        //指定reducer类
        job2.setReducerClass(IntSumReducer.class);
            //指定job输出的key和value的类型,如果map和reduce输出类型不完全相同,需要重新设置map的output的key和value的class类型
        job2.setOutputKeyClass(Text.class);
        job2.setOutputValueClass(IntWritable.class);
        //指定输入数据的路径
        FileInputFormat.addInputPath(job2,new Path(otherArgs[otherArgs.length-1]));
        //指定输出路径,并要求该输出路径一定是不存在的
        FileOutputFormat.setOutputPath(job2, new Path(otherArgs[otherArgs.length-2]));



        //指定job执行模式,等待任务执行完成后,提交任务的客户端才会退出!
          if (job1.waitForCompletion(true)) {



              System.exit(job2.waitForCompletion(true) ? 0 : 1);
          }
    }
}

第一个MR输出到output文件夹,第二个输出到final文件夹,提交jar作业到spark上运行,命令为:

spark-submit --master local --name MyWordCount --class com.river.WordCountDemon ~/Downloads/spark-demon-1.0-SNAPSHOT.jar ~/hadoop/spark/wordcount/text.txt

在此之前需要保证jps:
在这里插入图片描述
最终的实现结果为:
在这里插入图片描述

Spark的共享变量

Spark创建RDD的方式

Spark 的计算过程就是创建 RDD 以及对 RDD 进行转换的过程。在 WordCount 的例子中,我们通过了 textFile 方法来从一个文本文件中的数据创建一个 RDD:

val lines = sc.textFile("hdfs://localhost:9000/dataset/example.txt")//从 HDFS 中读取数据
val lines = sc.textFile("file:///home/hadoop/example.txt")//从本地读取数据

其他从本地文件以及 HDFS 创建 RDD 的常见方法有:

  1. 使用SparkContext的parallelize()方法序列化本地数据集合创建RDD。
  2. 使用外界的数据源创建RDD,比如说本地文件系统,分布式文件系统HDFS等等。
  3. 通过将已有RDD使用transform算子操作产生新的RDD。
/**
 * 三种方法创建RDD演示Java版本
 */
public class CreateRDDJava {
 
    public static void main(String[] args) {
        SparkConf conf = new SparkConf()
                .setAppName("CreateRDDJava")
                .setMaster("local")
    
        // 创建SparkContext
        JavaSparkContext sc = new JavaSparkContext(conf);
        /**
         * 方式一:使用JavaSparkContext的parallelize方法
         * 将集合序列化,本地创建RDD
         */
        List<String> lineWord = new ArrayList<String>();
        lineWord.add("hello hadoop hello spark");
        // 创建RDD
        JavaRDD<String> lineWordRDD = sc.parallelize(lineWord);
        // 打印RDD里的内容
        lineWordRDD.foreach(new VoidFunction<String>() {
            public void call(String word) throws Exception {
                System.out.println(word);
            }
        });
 
        /**
         * 方式二:使用外部数源创建RDD
         */
        // 一般而言这里的外部数据源地址是外部共享文件系统如HDFS
        // 这里因为只是在本地演示创建RDD不打包代码上集群因而是本地的路径
        JavaRDD<String> textRDD = sc.textFile("file:///C:\\Users\\XJH\\Desktop\\test\\22.txt");
        // 打印RDD内容
        textRDD.foreach(new VoidFunction<String>() {
            public void call(String text) throws Exception {
                System.out.println(text);
            }
        });
 
        /**
         * 方式三:通过其他已有RDD通过高阶变换而来
         */
        // 这里使用lineWordRDD进行演示
        // 使用flatMap算子对其进行变换,按空格分割字符串得到含有若干个单词的新RDD
        JavaRDD<String> wordsRDD = lineWordRDD.flatMap(
            new FlatMapFunction<String, String>() {
                public Iterator<String> call(String lineWord) {
                    return Arrays.asList(lineWord.split(" ")).iterator();
                }
        });
        // 将切割后的RDD内容进行打印
        wordsRDD.foreach(new VoidFunction<String>() {
            public void call(String word) throws Exception {
                System.out.println(word);
            }
        });
    }
}
/**
  * 三种方式创建RDD演示Scala版本
  */
object CreateRDDScala {
 
  def main(args: Array[String]): Unit = {
 
    val conf = new SparkConf()
      .setAppName("CreateRDDScala")
      .setMaster("local")
      .set("spark.testing.memory", "2147480000")
    // 创建SparkContext
    val sc = new SparkContext(conf)
 
    /**
      * 方式一:使用JavaSparkContext的parallelize方法
      * 将集合序列化,本地创建RDD
      */
    val lineWord = Array("hello word hello spark")
    // 序列化创建RDD
    val lineWordRDD = sc.parallelize(lineWord)
    // 打印RDD的内容
    lineWordRDD.foreach(line => println(line))
 
    /**
      * 方式二:使用外部数据源创建RDD
      */
       // 一般而言这里的外部数据源地址是外部共享文件系统如HDFS
       // 这里因为只是在本地演示创建RDD不打包代码上集群因而是本地的路径
    val textRDD = sc.textFile("file:///C:\\Users\\XJH\\Desktop\\test\\22.txt")
    // 打印RDD的内容
    textRDD.foreach(text => println(text))
 
    /**
      * 方式三:通过其他已有RDD通过高阶变换而来
      */
    // 这里使用lineWordRDD进行演示
    // 使用flatMap算子对其进行变换,按空格分割字符串得到含有若干个单词的新RDD
    val wordsRDD = lineWordRDD.flatMap(line => line.split(" "))
    // 打印RDD的内容
    wordsRDD.foreach(word => println(word))
  }
}

Spark共享变量

这一部分内容还没有完全明白,摘抄一段从网上搜集的资料

默认情况下,如果在一个算子的函数中使用到了某个外部的变量,那么这个变量的值会被拷贝到每个task中。此时每个task只能操作自己的那份变量副本。如果多个task想要共享某个变量,那么这种方式是做不到的。
Spark为此提供了两种共享变量,一种是Broadcast Variable(广播变量),另一种是Accumulator(累加变量)。Broadcast Variable会将使用到的变量,仅仅为每个节点拷贝一份,更大的用处是优化性能,减少网络传输以及内存消耗。Accumulator则可以让多个task共同操作一份变量,主要可以进行累加操作。

累加器

  1. 累加器支持在所有不同节点之间进行累加操作。
  2. 累加器是仅仅被相关操作累加的变量,通常可以被用来实现计数器和求和。
  3. Spark原生地支持数值型(numeric)的累加器,也可以自己编写对新类型的累加器。
  4. 可以通过SparkContext.longAccumulator()或者SparkContext.doubleAccumulator()来创建累加器。
  5. 参数有两个(Int,String),第一个参数为初始累加值,默认为0,第二个参数为累加器的名字。
  6. 运行在集群中的任务,就可以使用add()方法来把数值累加到累加器上。但是任务节点执行做累加操作,不能读取累加器的值,只有任务控制节点(Driver Program)可以使用value方法来读取。
import org.apache.spark._
object MyRdd {
    def main(args:Array[String]): Unit ={
        //初始化配置:设置主机名和程序主类的名字
        val conf = new SparkConf().setMaster("local").setAppName("MyRdd");
        //通过conf来创建sparkcontext
        val sc = new SparkContext(conf);
        val accum = sc.longAccumulator("My Accumulator");//后边是计数器的名字
        val list = List(1,2,3,4,5);
        val rdd = sc.parallelize(list);
        rdd.foreach(x => accum.add(x));//调用累加器求和
        accum.value;//注意只有任务控制节点(Driver节点)才能使用value方法来获取累加器的值
    }
}

参考

在这篇文章之前还写了一篇Hbase和Scala的简单入门,没有写完,贴在这里
https://blog.csdn.net/weixin_44986776/article/details/106170682
创建RDD实践
Spark共享变量
林子雨Spark入门教程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值