解密SparkStreaming另类实验及SparkStreaming本质解析(第一篇)

来源:互联网 发布:女流和陈一发 知乎 编辑:程序博客网 时间:2024/06/06 05:51
  1. 本期亮点:
    通过SparkStreaming在线另类实验瞬间理解SparkStreaming运行本质
  2. SparkStreaming背景介绍
    当今社会处于一个大数据的时代,而SparkStreaming是Spark Code之上的一个流式计算子框架,数据的流式处理对大数据业务公司重要性是不言而喻的,应用场景如:通过大数据分析得到网上最新最热的热点词汇、电商网站给用户推荐目前最热卖的商品等等

下面我们通过一段在线黑名单过滤的代码实验剖析SparkStreaming的运行本质

import java.util.Arrays;import java.util.List;import org.apache.spark.SparkConf;import org.apache.spark.api.java.JavaPairRDD;import org.apache.spark.api.java.JavaRDD;import org.apache.spark.api.java.function.Function;import org.apache.spark.api.java.function.PairFunction;import org.apache.spark.streaming.Durations;import org.apache.spark.streaming.api.java.JavaPairDStream;import org.apache.spark.streaming.api.java.JavaReceiverInputDStream;import org.apache.spark.streaming.api.java.JavaStreamingContext;import com.google.common.base.Optional;import scala.Tuple2;/** * 背景描述:在广告点击计费系统中,我们在线过滤掉黑名单的点击,进而保护广告商的利益,只进行有效的广告点击计费 *  或者在防刷评分(或者流量)系统,过滤掉无效的投票或者评分或者流量; * 实现技术:使用transform Api直接基于RDD编程,进行join操作 *新浪微博:http://weibo.com/ilovepains/ */public class OnlineBlackListFilter {    public static void main(String[] args) {        /*         * 第一步:配置SparkConf,SparkConf主要用于配置运行时的配置信息,         * 比如说:通过setAppName来设置appName,通过setMaster来设置Spark集群的Master的URL         */        SparkConf conf = new SparkConf().setMaster("spark://Master:7077,Worker1:7077").setAppName("OnlineBlackListFilter");        /*         * 第二步:创建SparkStreamingContext:         * SparkStreaming应用程序所有功能的起始点和程序调度的核心         */        JavaStreamingContext jsc = new JavaStreamingContext(conf, Durations.seconds(300));        /*         * 第三步:创建Spark Streaming输入数据来源,此处我们采用socket网络通信为输入来源         */        JavaReceiverInputDStream<String> adsClickStream = jsc.socketTextStream("Master",9999);             /**       * 黑名单数据准备,实际上黑名单一般都是动态的,例如在Redis或者数据库中,黑名单的生成往往有复杂的业务       * 逻辑,具体情况算法不同,但是在Spark Streaming进行处理的时候每次都能工访问完整的信息       */        List<Tuple2<String,Boolean>> blackList =  Arrays.asList(new Tuple2<String,Boolean>("hadoop",true),new Tuple2<String,Boolean>("mahout",true));        final JavaPairRDD<String,Boolean> blackListRDD = jsc.sparkContext().parallelizePairs(blackList);          /**           * 此处模拟的广告点击的每条数据的格式为:time、name           * 此处map操作的结果是name、(time,name)的格式           */        JavaPairDStream<String, String> adsClickStreamFormatted = adsClickStream.mapToPair(new PairFunction<String, String, String>() {            @Override            public Tuple2<String, String> call(String line) throws Exception {                // TODO Auto-generated method stub                String[] words = line.split(" ");                return new Tuple2(words[1],line);            }        });        adsClickStreamFormatted.transform(new Function<JavaPairRDD<String,String>, JavaRDD<String>>() {            @Override            public JavaRDD<String> call(JavaPairRDD<String, String> userClickRDD) throws Exception {                // TODO Auto-generated method stub                //通过leftOuterJoin操作既保留了左侧用户广告点击内容的RDD的所有内容,又获得了相应点击内容是否在黑名单中                JavaPairRDD<String, Tuple2<String, Optional<Boolean>>> joinedBlackListRDD = userClickRDD.leftOuterJoin(blackListRDD);                /**                 * 进行filter过滤的时候,其输入元素是一个Tuple:(name,((time,name), boolean))                 * 其中第一个元素是黑名单的名称,第二元素的第二个元素是进行leftOuterJoin的时候是否存在在值                 * 如果存在的话,表面当前广告点击是黑名单,需要过滤掉,否则的话则是有效点击内容;                 */                JavaPairRDD<String, Tuple2<String, Optional<Boolean>>> validClicked = joinedBlackListRDD.filter(new Function<Tuple2<String,Tuple2<String,Optional<Boolean>>>, Boolean>() {                    @Override                    public Boolean call(Tuple2<String, Tuple2<String, Optional<Boolean>>> v1) throws Exception {                        // TODO Auto-generated method stub                        if(v1._2._2.isPresent()){                            return false;                        }                        return true;                    }                });                return validClicked.map(new Function<Tuple2<String,Tuple2<String,Optional<Boolean>>>, String>() {                    @Override                    public String call(Tuple2<String, Tuple2<String, Optional<Boolean>>> validClick) throws Exception {                        // TODO Auto-generated method stub                        return validClick._2._1;                    }                });            }        }).print();        jsc.start();        jsc.awaitTermination();        jsc.close();    }}

将代码编写完成之后,接下来我们需要做的是将程序打成jar的方式放到我们的spark集群上,当然我们是要先启动我们的spark集群的,在我们这边的实验中为了更好的查看Job的运行情况,我们还启动了history-server
由于我们的程序是通过socket网络通信作为数据的输入来源,下面我们在shell界面上先执行nc

[oracle@Master ~]$ nc -lk 9999

接下来我们通过spark-submit提交我们的程序

/home/oracle/soft/spark-1.6.0-bin-hadoop2.6/bin/spark-submit --class com.xxj.spark.SparkApps.sparkstreaming.OnlineBlackListFilter --master spark://Master:7077,Worker1:7077 /home/oracle/data/SparkApps-0.0.1-SNAPSHOT.jar

下面我们在打开netcat,填一些数据,如下:

[oracle@Master ~]$ nc -lk 9999hello sparkhello hadoopadsa adashello mahoutgg asderte 3453yuyu ert

运行完程序之后,我们可以在history-server的web页面上,看到如下信息
这里写图片描述

我们点击App ID进入查看job的运行情况:
这里写图片描述
从上面的图片我们可以看到个程序在运行期间,启动了4个Job。

下面我们先看一下 Job Id 0,点击进入详细页面
这里写图片描述
从Job Id 0的DAG图中执行了reduceBykey的算子操作,但我们在实际的代码中并没有reduceBykey的代码操作,由此可见在程序执行jsc.start()的时候框架本身帮我们额外的执行了一个job

我们再看看Job Id 1的页面
这里写图片描述
从上图我们可以看出这个的运行时间长达3.5 min,我们整个程序的执行才3.9min,从job的运行情况图上start at OnlineBlackListFilter.java:99中我们可以看出在StreamingContext start的时候,这个job也启动了,这个Job主要做什么呢?
是这样的:StreamingContext start的时候会通过启动一个Job的方式来启动Receiver,而且Receiver只会在一个Worker节点中Executor进程中执行,且以一个Task线程去接受我们的数据
下图是Job1的具体详细页面:
这里写图片描述
从上图可以看出Receiver在接收的数据的时候,默认是存放在内存当中的,当内存放不下时才会放在磁盘当中,当然对于这点我们从以下源码当中也可以出

  def socketTextStream(      hostname: String,      port: Int,      storageLevel: StorageLevel = StorageLevel.MEMORY_AND_DISK_SER_2    ): ReceiverInputDStream[String] = withNamedScope("socket text stream") {    socketStream[String](hostname, port, SocketReceiver.bytesToLines, storageLevel)  }

接下来我们再看看job2详细
这里写图片描述
从Job 2的DAG图我们可以我们程序的主要业务逻辑,在Stage 3、Stage 4、Stage 5得到了充分的体现,stage3、stage4接受到了两种不同的数据来源然后在stage5中进行transform操作
接下面我们再看看job3的详细
这里写图片描述
从Job3中我们也可以看到,其实Job2、Job3对于的DAG图是一样的,并且也都体现了我们程序的业务逻辑,不同的是在Job3中stage6、stage7是skipped的而已

从这4个Job我们可以看出在Spark应用程序中往往可以启动多个Job,不同的Job之间可以相互配合共同完成一个作业

探讨Spark Streaming本质
这里写图片描述
1、从官网的这张图我们可以看出Spark Streaming可以接受不同的数据来源,且处理之后也可以保存到多种介质中去
2、Spark Streaming提供了用于表示连续数据流的、高度抽象的被称为离散流的DStream。Dsream其本质上表示RDD的序列,也就是在以一个固定的时间间隔切割的一段RDD的数据流,所以对DStream的操作最终都会转变为对Spark Code RDD的操作,这样也就意味着DStream实际上是一种逻辑级别上的概念,相当于我们人为的把实时流进来的数据切为一段一段的(以固定的时间间隔),从空间的角度来看实际上这一段一段的数据实际上是固定不变的,这样的话也就是满足了Spark Code RDD只能对静态数据操作的要求,但时间的维度来看其实数据是流动的,这也是Spark Straming的精髓所在

0 0
原创粉丝点击