描述:实时统计每天各地区各城市各广告的点击总流量,并将其存入MySQL。
1)单个批次内对数据进行按照天维度的聚合统计;
2)结合MySQL数据跟当前批次数据更新原有的数据。
CREATE TABLE area_city_ad_count (
dt CHAR(10),
area CHAR(4),
city CHAR(4),
adid CHAR(2),
count BIGINT,
PRIMARY KEY (dt,area,city,adid)
);
object DateAreaCityAdCountHandler {
//时间格式化对象
private val sdf: SimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd")
/**
* 统计每天各大区各个城市广告点击总数并保存至MySQL中
*
* @param filterAdsLogDStream 根据黑名单过滤后的数据集
*/
def saveDateAreaCityAdCountToMysql(filterAdsLogDStream: DStream[Ads_log]): Unit = {
//1.统计每天各大区各个城市广告点击总数
val dateAreaCityAdToCount: DStream[((String, String, String, String), Long)] = filterAdsLogDStream.map(ads_log => {
//a.取出时间戳
val timestamp: Long = ads_log.timestamp
//b.格式化为日期字符串
val dt: String = sdf.format(new Date(timestamp))
//c.组合,返回
((dt, ads_log.area, ads_log.city, ads_log.adid), 1L)
}).reduceByKey(_ + _)
//2.将单个批次统计之后的数据集合MySQL数据对原有的数据更新
dateAreaCityAdToCount.foreachRDD(rdd => {
//对每个分区单独处理
rdd.foreachPartition(iter => {
//a.获取连接
val connection: Connection = JdbcUtil.getConnection
//b.写库
iter.foreach { case ((dt, area, city, adid), count) =>
JdbcUtil.executeUpdate(connection,
"""
|INSERT INTO area_city_ad_count (dt,area,city,adid,count)
|VALUES(?,?,?,?,?)
|ON DUPLICATE KEY
|UPDATE count=count+?;
""".stripMargin,
Array(dt, area, city, adid, count, count))
}
//c.释放连接
connection.close()
})
})
}
}
object RealTimeApp {
def main(args: Array[String]): Unit = {
//1.创建SparkConf
val sparkConf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RealTimeApp")
//2.创建StreamingContext
val ssc = new StreamingContext(sparkConf, Seconds(3))
//3.读取Kafka数据 1583288137305 华南 深圳 4 3
val topic: String = PropertiesUtil.load("config.properties").getProperty("kafka.topic")
val kafkaDStream: InputDStream[ConsumerRecord[String, String]] = MyKafkaUtil.getKafkaStream(topic, ssc)
//4.将每一行数据转换为样例类对象
val adsLogDStream: DStream[Ads_log] = kafkaDStream.map(record => {
//a.取出value并按照" "切分
val arr: Array[String] = record.value().split(" ")
//b.封装为样例类对象
Ads_log(arr(0).toLong, arr(1), arr(2), arr(3), arr(4))
})
//5.根据MySQL中的黑名单表进行数据过滤
val filterAdsLogDStream: DStream[Ads_log] = adsLogDStream.filter(adsLog => {
//查询MySQL,查看当前用户是否存在。
val connection: Connection = JdbcUtil.getConnection
val bool: Boolean = JdbcUtil.isExist(connection, "select * from black_list where userid=?", Array(adsLog.userid))
connection.close()
!bool
})
filterAdsLogDStream.cache()
//6.对没有被加入黑名单的用户统计当前批次单日各个用户对各个广告点击的总次数,
// 并更新至MySQL
// 之后查询更新之后的数据,判断是否超过100次。
// 如果超过则将给用户加入黑名单
BlackListHandler.saveBlackListToMysql(filterAdsLogDStream)
//7.统计每天各大区各个城市广告点击总数并保存至MySQL中
dateAreaCityAdCountHandler.saveDateAreaCityAdCountToMysql(filterAdsLogDStream)
//10.开启任务
ssc.start()
ssc.awaitTermination()
}
}
结果展示:
1:List [15:50->10,15:51->25,15:52->30]
2:List [15:50->10,15:51->25,15:52->30]
3:List [15:50->10,15:51->25,15:52->30]
1)开窗确定时间范围;
2)在窗口内将数据转换数据结构为((adid,hm),count);
3)按照广告id进行分组处理,组内按照时分排序。
object LastHourAdCountHandler {
//时间格式化对象
private val sdf: SimpleDateFormat = new SimpleDateFormat("HH:mm")
/**
* 统计最近一小时(2分钟)广告分时点击总数
*
* @param filterAdsLogDStream 过滤后的数据集
* @return
*/
def getAdHourMintToCount(filterAdsLogDStream: DStream[Ads_log]): DStream[(String, List[(String, Long)])] = {
//1.开窗 => 时间间隔为1个小时 window()
val windowAdsLogDStream: DStream[Ads_log] = filterAdsLogDStream.window(Minutes(2))
//2.转换数据结构 ads_log =>((adid,hm),1L) map()
val adHmToOneDStream: DStream[((String, String), Long)] = windowAdsLogDStream.map(adsLog => {
val timestamp: Long = adsLog.timestamp
val hm: String = sdf.format(new Date(timestamp))
((adsLog.adid, hm), 1L)
})
//3.统计总数 ((adid,hm),1L)=>((adid,hm),sum) reduceBykey(_+_)
val adHmToCountDStream: DStream[((String, String), Long)] = adHmToOneDStream.reduceByKey(_ + _)
//4.转换数据结构 ((adid,hm),sum)=>(adid,(hm,sum)) map()
val adToHmCountDStream: DStream[(String, (String, Long))] = adHmToCountDStream.map { case ((adid, hm), count) =>
(adid, (hm, count))
}
//5.按照adid分组 (adid,(hm,sum))=>(adid,Iter[(hm,sum),...]) groupByKey
adToHmCountDStream.groupByKey()
.mapValues(iter =>
iter.toList.sortWith(_._1 < _._1)
)
}
}
object RealTimeApp {
def main(args: Array[String]): Unit = {
//1.创建SparkConf
val sparkConf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("RealTimeApp")
//2.创建StreamingContext
val ssc = new StreamingContext(sparkConf, Seconds(3))
//3.读取Kafka数据 1583288137305 华南 深圳 4 3
val topic: String = PropertiesUtil.load("config.properties").getProperty("kafka.topic")
val kafkaDStream: InputDStream[ConsumerRecord[String, String]] = MyKafkaUtil.getKafkaStream(topic, ssc)
//4.将每一行数据转换为样例类对象
val adsLogDStream: DStream[Ads_log] = kafkaDStream.map(record => {
//a.取出value并按照" "切分
val arr: Array[String] = record.value().split(" ")
//b.封装为样例类对象
Ads_log(arr(0).toLong, arr(1), arr(2), arr(3), arr(4))
})
//5.根据MySQL中的黑名单表进行数据过滤
val filterAdsLogDStream: DStream[Ads_log] = adsLogDStream.filter(adsLog => {
//查询MySQL,查看当前用户是否存在。
val connection: Connection = JdbcUtil.getConnection
val bool: Boolean = JdbcUtil.isExist(connection, "select * from black_list where userid=?", Array(adsLog.userid))
connection.close()
!bool
})
filterAdsLogDStream.cache()
//6.对没有被加入黑名单的用户统计当前批次单日各个用户对各个广告点击的总次数,
// 并更新至MySQL
// 之后查询更新之后的数据,判断是否超过100次。
// 如果超过则将给用户加入黑名单
BlackListHandler.saveBlackListToMysql(filterAdsLogDStream)
//7.统计每天各大区各个城市广告点击总数并保存至MySQL中
DateAreaCityAdCountHandler.saveDateAreaCityAdCountToMysql(filterAdsLogDStream)
//8.统计最近一小时(2分钟)广告分时点击总数
val adToHmCountListDStream: DStream[(String, List[(String, Long)])] = LastHourAdCountHandler.getAdHourMintToCount(filterAdsLogDStream)
//9.打印
adToHmCountListDStream.print()
//10.开启任务
ssc.start()
ssc.awaitTermination()
}
}
Spark内核泛指Spark的核心运行机制,包括Spark核心组件的运行机制、Spark任务调度机制、Spark内存管理机制、Spark核心功能的运行原理等,熟练掌握Spark内核原理,能够更好地完成Spark代码设计,并能够帮助准确锁定项目运行过程中出现的问题的症结所在。
Spark驱动器节点,用于执行Spark任务中的main方法,负责实际代码的执行工作。Driver在Spark作业执行时主要负责:
Spark Executor节点是一个JVM进程,负责在 Spark 作业中运行具体任务,任务彼此之间相互独立。Spark 应用启动时,Executor节点被同时启动,并且始终伴随着整个 Spark 应用的生命周期而存在。如果有Executor节点发生了故障或崩溃,Spark 应用也可以继续执行,会将出错节点上的任务调度到其他Executor节点上继续运行。
Executor有两个核心功能:
上图为Spark通用运行流程图,体现了基本的Spark应用程序在部署中的基本提交流程。
这个流程是按照如下的核心步骤进行工作的:
Spark支持多种集群管理器(Cluster Manager),分别为:
实际上,除了上述这些通用的集群管理器外,Spark内部也提供了方便用户测试和学习的本地集群部署模式和Windows环境。由于在实际工厂环境下使用的绝大多数的集群管理器是Hadoop YARN,因此关注的重点是Hadoop YARN模式下的Spark集群部署。
Spark的运行模式取决于传递给SparkContext的MASTER环境变量的值,个别模式还需要辅助的程序接口来配合使用,目前支持的Master字符串及URL包括:
Master URL | Meaning |
---|---|
local | 在本地运行,只有一个工作进程,无并行计算能力。 |
local[K] | 在本地运行,有K个工作进程,通常设置K为机器的CPU核心数量。 |
local[*] | 在本地运行,工作进程数量等于机器的CPU核心数量。 |
spark://HOST:PORT | 以Standalone模式运行,这是Spark自身提供的集群运行模式,默认端口号: 7077。详细文档见:Spark standalone cluster。 |
mesos://HOST:PORT | 在Mesos集群上运行,Driver进程和Worker进程运行在Mesos集群上,部署模式必须使用固定值:–deploy-mode cluster。详细文档见:MesosClusterDispatcher. |
yarn-client | 在Yarn集群上运行,Driver进程在本地,Executor进程在Yarn集群上,部署模式必须使用固定值:–deploy-mode client。Yarn集群地址必须在HADOOP_CONF_DIR or YARN_CONF_DIR变量里定义。 |
yarn-cluster | 在Yarn集群上运行,Driver进程在Yarn集群上,Work进程也在Yarn集群上,部署模式必须使用固定值:–deploy-mode cluster。Yarn集群地址必须在HADOOP_CONF_DIR or YARN_CONF_DIR变量里定义。 |
用户在提交任务给Spark处理时,以下两个参数共同决定了Spark的运行方式。
Standalone集群有四个重要组成部分,分别是:
在Standalone Client模式下,Driver在任务提交的本地机器上运行,Driver启动后向Master注册应用程序,Master根据submit脚本的资源需求找到内部资源至少可以启动一个Executor的所有Worker,然后在这些Worker之间分配Executor,Worker上的Executor启动后会向Driver反向注册,所有的Executor注册完成后,Driver开始执行main函数,之后执行到Action算子时,开始划分stage,每个stage生成对应的taskSet,之后将task分发到各个Executor上执行。
在Standalone Cluster模式下,任务提交后,Master会找到一个Worker启动Driver。Driver启动后向Master注册应用程序,Master根据submit脚本的资源需求找到内部资源至少可以启动一个Executor的所有Worker,然后在这些Worker之间分配Executor,Worker上的Executor启动后会向Driver反向注册,所有的Executor注册完成后,Driver开始执行main函数,之后执行到Action算子时,开始划分Stage,每个Stage生成对应的taskSet,之后将Task分发到各个Executor上执行。
注意,Standalone的两种模式下(client/Cluster),Master在接到Driver注册Spark应用程序的请求后,会获取其所管理的剩余资源能够启动一个Executor的所有Worker,然后在这些Worker之间分发Executor,此时的分发只考虑Worker上的资源是否足够使用,直到当前应用程序所需的所有Executor都分配完毕,Executor反向注册完毕后,Driver开始执行main程序。
object ExecutorLauncher {
def main(args: Array[String]): Unit = {
ApplicationMaster.main(args)
}
}
注意:SparkSubmit、ApplicationMaster和YarnCoarseGrainedExecutorBackend是独立的进程;Executor和Driver是对象。
注意:SparkSubmit、ApplicationMaster和CoarseGrainedExecutorBackend是独立的进程;Driver是独立的线程;Executor和YarnClusterApplication是对象。
注意:跟踪源码的时候添加一下依赖
org.apache.spark
spark-yarn_2.11
2.1.1