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

  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 adsClickStream = jsc.socketTextStream("Master",9999);      

       /**
       * 黑名单数据准备,实际上黑名单一般都是动态的,例如在Redis或者数据库中,黑名单的生成往往有复杂的业务
       * 逻辑,具体情况算法不同,但是在Spark Streaming进行处理的时候每次都能工访问完整的信息
       */
        List> blackList =  Arrays.asList(new Tuple2("hadoop",true),new Tuple2("mahout",true));

        final JavaPairRDD blackListRDD = jsc.sparkContext().parallelizePairs(blackList);
          /**
           * 此处模拟的广告点击的每条数据的格式为:time、name
           * 此处map操作的结果是name、(time,name)的格式
           */
        JavaPairDStream adsClickStreamFormatted = adsClickStream.mapToPair(new PairFunction() {

            @Override
            public Tuple2 call(String line) throws Exception {
                // TODO Auto-generated method stub
                String[] words = line.split(" ");
                return new Tuple2(words[1],line);
            }
        });

        adsClickStreamFormatted.transform(new Function, JavaRDD>() {

            @Override
            public JavaRDD call(JavaPairRDD userClickRDD) throws Exception {
                // TODO Auto-generated method stub
                //通过leftOuterJoin操作既保留了左侧用户广告点击内容的RDD的所有内容,又获得了相应点击内容是否在黑名单中
                JavaPairRDD>> joinedBlackListRDD = userClickRDD.leftOuterJoin(blackListRDD);

                /**
                 * 进行filter过滤的时候,其输入元素是一个Tuple:(name,((time,name), boolean))
                 * 其中第一个元素是黑名单的名称,第二元素的第二个元素是进行leftOuterJoin的时候是否存在在值
                 * 如果存在的话,表面当前广告点击是黑名单,需要过滤掉,否则的话则是有效点击内容;
                 */
                JavaPairRDD>> validClicked = joinedBlackListRDD.filter(new Function>>, Boolean>() {

                    @Override
                    public Boolean call(Tuple2>> v1) throws Exception {
                        // TODO Auto-generated method stub
                        if(v1._2._2.isPresent()){
                            return false;
                        }
                        return true;
                    }
                });
                return validClicked.map(new Function>>, String>() {
                    @Override
                    public String call(Tuple2>> 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 9999
hello spark
hello hadoop
adsa adas
hello mahout
gg asd
erte 3453
yuyu ert

运行完程序之后,我们可以在history-server的web页面上,看到如下信息
解密SparkStreaming另类实验及SparkStreaming本质解析(第一篇)_第1张图片

我们点击App ID进入查看job的运行情况:
解密SparkStreaming另类实验及SparkStreaming本质解析(第一篇)_第2张图片
从上面的图片我们可以看到个程序在运行期间,启动了4个Job。

下面我们先看一下 Job Id 0,点击进入详细页面
解密SparkStreaming另类实验及SparkStreaming本质解析(第一篇)_第3张图片
从Job Id 0的DAG图中执行了reduceBykey的算子操作,但我们在实际的代码中并没有reduceBykey的代码操作,由此可见在程序执行jsc.start()的时候框架本身帮我们额外的执行了一个job

我们再看看Job Id 1的页面
解密SparkStreaming另类实验及SparkStreaming本质解析(第一篇)_第4张图片
从上图我们可以看出这个的运行时间长达3.5 min,我们整个程序的执行才3.9min,从job的运行情况图上start at OnlineBlackListFilter.java:99中我们可以看出在StreamingContext start的时候,这个job也启动了,这个Job主要做什么呢?
是这样的:StreamingContext start的时候会通过启动一个Job的方式来启动Receiver,而且Receiver只会在一个Worker节点中Executor进程中执行,且以一个Task线程去接受我们的数据
下图是Job1的具体详细页面:
解密SparkStreaming另类实验及SparkStreaming本质解析(第一篇)_第5张图片
从上图可以看出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详细
解密SparkStreaming另类实验及SparkStreaming本质解析(第一篇)_第6张图片
从Job 2的DAG图我们可以我们程序的主要业务逻辑,在Stage 3、Stage 4、Stage 5得到了充分的体现,stage3、stage4接受到了两种不同的数据来源然后在stage5中进行transform操作
接下面我们再看看job3的详细
解密SparkStreaming另类实验及SparkStreaming本质解析(第一篇)_第7张图片
从Job3中我们也可以看到,其实Job2、Job3对于的DAG图是一样的,并且也都体现了我们程序的业务逻辑,不同的是在Job3中stage6、stage7是skipped的而已

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

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

你可能感兴趣的:(spark大数据)