参考林子雨教程和尚硅谷大数据视频
按照步骤来基本可以顺利搞定
运行jar包:
cd ~
/usr/local/spark/bin/spark-submit --class WordCount /home/hadoop/WordCount.jar
使用的仍然是上一篇博客中的代码,两个mapreduce的job串联运行,第一个job进行分词和词频统计,第二个job统计共有多少词,job2的map把每行都输出成
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方法的具体实现,逐个对去处理. 在词频统计操作中,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方法的具体实现,逐个去处理
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方法的具体实现,逐个对去处理. 在词频统计操作中,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[...]" );
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
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 的常见方法有:
/**
* 三种方法创建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))
}
}
这一部分内容还没有完全明白,摘抄一段从网上搜集的资料
默认情况下,如果在一个算子的函数中使用到了某个外部的变量,那么这个变量的值会被拷贝到每个task中。此时每个task只能操作自己的那份变量副本。如果多个task想要共享某个变量,那么这种方式是做不到的。
Spark为此提供了两种共享变量,一种是Broadcast Variable(广播变量),另一种是Accumulator(累加变量)。Broadcast Variable会将使用到的变量,仅仅为每个节点拷贝一份,更大的用处是优化性能,减少网络传输以及内存消耗。Accumulator则可以让多个task共同操作一份变量,主要可以进行累加操作。
累加器
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入门教程