大數據IMF傳奇行動絕密課程第91課:SparkStreaming基於Kafka Direct案例實戰和內幕源碼解密

SparkStreaming基於Kafka Direct案例實戰和內幕源碼解密

1、sparkStreaming on Kafka Direct工作原理機制
2、sparkStreaming on Kafka Direct案例實戰
3、sparkStreaming on Kafka Direct源碼解析

可以避免重複消費,RDD的Partition與Kafka的Partition對應

package com.tom.spark.SparkApps.sparkstreaming;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import kafka.serializer.StringDecoder;

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.Function2;
import org.apache.spark.api.java.function.PairFunction;
import org.apache.spark.api.java.function.VoidFunction;
import org.apache.spark.streaming.Durations;
import org.apache.spark.streaming.api.java.JavaDStream;
import org.apache.spark.streaming.api.java.JavaPairDStream;
import org.apache.spark.streaming.api.java.JavaPairInputDStream;
import org.apache.spark.streaming.api.java.JavaStreamingContext;
import org.apache.spark.streaming.api.java.JavaStreamingContextFactory;
import org.apache.spark.streaming.kafka.KafkaUtils;

import scala.Tuple2;

/**
 * 在線處理廣告點擊流
 */
public class SparkStreamingOnKafkaDirect {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //好處:1、checkpoint 2、工廠
        final SparkConf conf = new SparkConf().setAppName("SparkStreamingOnKafkaDirect").setMaster("hdfs://Master:7077/");
        final String checkpointDirectory = "hdfs://Master:9000/library/SparkStreaming/CheckPoint_Data";

        JavaStreamingContextFactory factory = new JavaStreamingContextFactory() {

            public JavaStreamingContext create() {
                // TODO Auto-generated method stub
                return createContext(checkpointDirectory, conf);
            }   
        };

        /**
         * 可以從失敗中恢復Driver,不過還需要指定Driver這個進程運行在Cluster,並且在提交應用程序的時候制定--supervise;
         */
        JavaStreamingContext javassc = JavaStreamingContext.getOrCreate(checkpointDirectory, factory);
        /**
         * 第三步:創建Spark Streaming輸入數據來源input Stream:
         * 1、數據輸入來源可以基於File、HDFS、Flume、Kafka、Socket等
         * 2、在這裏我們指定數據來源於網絡Socket端口,Spark Streaming連接上該端口並在運行的時候一直監聽該端口的數據
         *      (當然該端口服務首先必須存在),並且在後續會根據業務需要不斷有數據產生(當然對於Spark Streaming
         *      應用程序的運行而言,有無數據其處理流程都是一樣的)
         * 3、如果經常在每間隔5秒鐘沒有數據的話不斷啓動空的Job其實會造成調度資源的浪費,因爲並沒有數據需要發生計算;所以
         *      實際的企業級生成環境的代碼在具體提交Job前會判斷是否有數據,如果沒有的話就不再提交Job;
         */

        //創建Kafka元數據來讓Spark Streaming這個Kafka Consumer利用

        Map<String, String> kafkaParameters = new HashMap<String, String>();
        kafkaParameters.put("metadata.broker.list", "Master:9092,Worker1:9092,Worker2:9092");

        Set<String> topics = new HashSet<String>();
        topics.add("AdClicked");

        JavaPairInputDStream<String, String> adClickedStreaming = KafkaUtils.createDirectStream(javassc, 
                String.class, String.class, 
                StringDecoder.class, StringDecoder.class,
                kafkaParameters, 
                topics);
        /**
         * 廣告點擊的基本數據格式:timestamp、ip、userID、adID、province、city
         * 
         */

        JavaPairDStream<String, Long> pairs = adClickedStreaming.mapToPair(new PairFunction<Tuple2<String,String>, String, Long>() {

            public Tuple2<String, Long> call(Tuple2<String, String> t)
                    throws Exception {
                // TODO Auto-generated method stub
                String[] splited = t._2.split("\t");
                String timestamp = splited[0]; //yyyy-MM-dd
                String ip = splited[1];
                String userID = splited[2];
                String adID = splited[3];
                String province = splited[4];
                String city = splited[5];
                String clickedRecord = timestamp + "_" + ip + "_" + userID + "_" + adID + "_" + province + "_" + city;
                return new Tuple2<String, Long>(clickedRecord, 1L);
            }
        });


     /**
     * 計算每個Batch Duration中每個User的廣告點擊量
     */
        JavaPairDStream<String, Long> adClickedUsers = pairs.reduceByKey(new Function2<Long, Long, Long>(){
            //對相同的key,進行Value的累加(包括Local和Reducer級別同時Reduce)
            public Long call(Long v1, Long v2) throws Exception {
                // TODO Auto-generated method stub
                return v1 + v2;
            }           
        });

        /**
         * 計算出什麼叫有效的點擊
         * 1、複雜化的一般都是採用機器學習訓練好模型直接在線進行過濾
         * 2、簡單的?可以通過一個Batch Duration中的點擊次數來判斷是不是非法廣告點擊,但是實際上講,非法廣告
         * 點擊程序會儘可能模擬真實的廣告點擊行爲,所以通過一個Batch來判斷是不完整的,我們需要對例如一天(也可以是每小時)的數據進行判斷
         * 3、比在線機器學習退而求其次的做法如下:
         *  例如:一段時間內,同一個IP(MAC地址)有多個用戶的賬號訪問
         *  例如:可以統計一天內一個用戶點擊廣告的次數,如果一天點擊同樣的廣告操作50次的話就列入黑名單
         *  
         *  黑名單有一個重要的特徵:動態生成!所以每次每一個Batch Duration都要考慮是否有新的黑名單加入,此時黑名單需要存儲起來
         *  具體存儲在什麼地方,存儲在DB中即可
         *  
         *  例如郵件系統的“黑名單”,可以採用Spark Streaming不斷監控每個用戶的操作,如果用戶發送郵件的頻率超過某個值,可以
         *  暫時把用戶列入黑名單,從而阻止用戶過度怕、頻繁地發送郵件
         *  
         */
        JavaPairDStream<String, Long> filteredClickedInbatch = adClickedUsers.filter(new Function<Tuple2<String,Long>, Boolean>() {

            public Boolean call(Tuple2<String, Long> v1) throws Exception {
                // 10s內<=1才正常
                if(1 < v1._2) {
                    //更新一下黑名單的數據表
                    return false;
                } else {
                    return true;
                }
            }
        });

//      filteredClickedInbatch.print();
        filteredClickedInbatch.foreachRDD(new Function<JavaPairRDD<String,Long>, Void>() {

            public Void call(JavaPairRDD<String, Long> rdd) throws Exception {
                // TODO Auto-generated method stub
                rdd.foreachPartition(new VoidFunction<Iterator<Tuple2<String,Long>>>() {

                    public void call(Iterator<Tuple2<String, Long>> partition) throws Exception {
                        // 在這裏我們使用數據庫連接池的高效讀寫數據庫的方式把數據寫入數據庫MySQL
                        // 由於傳入的參數是一個Iterator類型的集合,所以爲了更加高效地操作,我們需要批量處理
                        // 例如一次性插入1000條Record,使用insertBatch或者updateBatch類型的操作
                        // 插入的用戶信息可以包含:userID、adIDclickedCount、time
                        // 這裏有一個問題:可能出現兩條記錄的Key是一樣的,此時就需要更新累加操作

                    }
                });
                return null;
            }
        });
        JavaPairDStream<String, Long> blackListBasedOnHistory = filteredClickedInbatch.filter(new Function<Tuple2<String,Long>, Boolean>() {

            public Boolean call(Tuple2<String, Long> v1) throws Exception {
                // 廣告點擊的基本數據格式:timestamp、ip、userID、adID、province、city
                String[] splited = v1._1.split("\t");
                String dateString = splited[0];
                String userID = splited[2];
                String adId = splited[3];

                /**
                 * 接下來根據date、userID、adID等條件去查詢用戶點擊廣告的數據表,獲得總的點擊次數
                 * 這個時候基於點擊次數判斷是否屬於黑名單點擊
                 */
                int clickedCountTotalToday = 81;
                if(clickedCountTotalToday > 50)
                    return true;

                else return false;
            }
        });
        /**
         * 必須對黑名單的整個RDD進行去重操作
         */
        JavaDStream<String> blackListuserIDBasedOnHistory = blackListBasedOnHistory.map(new Function<Tuple2<String,Long>, String>() {

            public String call(Tuple2<String, Long> v1) throws Exception {
                // TODO Auto-generated method stub
                return v1._1.split("\t")[2];            }

        });


         JavaDStream<String> blackListUniqueUserID = blackListuserIDBasedOnHistory.transform(new Function<JavaRDD<String>, JavaRDD<String>>() {

            public JavaRDD<String> call(JavaRDD<String> rdd) throws Exception {
                // TODO Auto-generated method stub
                return rdd.distinct();
            }
        });

        //下一步寫入黑名單數據表中

         blackListUniqueUserID.foreachRDD(new Function<JavaRDD<String>, Void>() {

            public Void call(JavaRDD<String> rdd) throws Exception {
                rdd.foreachPartition(new VoidFunction<Iterator<String>>() {

                    public void call(Iterator<String> t) throws Exception {
                        // 在這裏我們使用數據庫連接池的高效讀寫數據庫的方式把數據寫入數據庫MySQL
                        // 由於傳入的參數是一個Iterator類型的集合,所以爲了更加高效地操作,我們需要批量處理
                        // 例如一次性插入1000條Record,使用insertBatch或者updateBatch類型的操作
                        // 插入的用戶信息可以包含:userID、adIDclickedCount、time
                        // 此時直接插入黑名單數據表即可

                    }
                });
                return null;
            }
        });

        /**
         * Spark Streaming 執行引擎也就是Driver開始運行,Driver啓動的時候是位於一條新的線程中的,當然其內部有消息循環體,用於
         * 接收應用程序本身或者Executor中的消息,
         */
        javassc.start();
        javassc.awaitTermination();
        javassc.close();
    }

    private static JavaStreamingContext createContext(String checkpointDirectory, SparkConf conf) {
        // If you do not see this printed, that means the StreamingContext has been loaded
        // from the new checkpoint
        System.out.println("Creating new context");


        // Create the context with a 5 second batch size
        JavaStreamingContext ssc = new JavaStreamingContext(conf, Durations.seconds(10));
        ssc.checkpoint(checkpointDirectory);

        return ssc;
    }
}
發佈了125 篇原創文章 · 獲贊 5 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章