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;
}
}