一.环境准备

1.pom文件

<dependencies>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.12</artifactId>
<version>3.0.0</version>
</dependency> <dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming_2.12</artifactId>
<version>3.0.0</version>
</dependency> <dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming-kafka-0-10_2.12</artifactId>
<version>3.0.0</version>
</dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency> <dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.27</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies> <build>
<plugins>
<!-- 该插件用于将Scala代码编译成class文件 -->
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<!-- 声明绑定到maven的compile阶段 -->
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

2.bean

import java.text.SimpleDateFormat
import java.util.Date
//数据格式:1597148289569,华北,北京,102,4,2020-08-11,11:12
case class AdsInfo(ts: Long,
area: String,
city: String,
userId: String,
adsId: String,
var dayString: String = null, // yyyy-MM-dd
var hmString: String = null) { // hh:mm val date = new Date(ts)
dayString = new SimpleDateFormat("yyyy-MM-dd").format(date)
hmString = new SimpleDateFormat("HH:mm").format(date)
}

3.工具类

JDBCUtils

object JDBCUtil {

    // 创建连接池对象
var dataSource:DataSource = init() // 连接池的初始化
def init():DataSource = { val paramMap = new java.util.HashMap[String, String]()
paramMap.put("driverClassName", PropertiesUtil.getValue("jdbc.driver.name"))
paramMap.put("url", PropertiesUtil.getValue("jdbc.url"))
paramMap.put("username", PropertiesUtil.getValue("jdbc.user"))
paramMap.put("password", PropertiesUtil.getValue("jdbc.password"))
paramMap.put("maxActive", PropertiesUtil.getValue("jdbc.datasource.size")) // 使用Druid连接池对象
DruidDataSourceFactory.createDataSource(paramMap)
} // 从连接池中获取连接对象
def getConnection(): Connection = {
dataSource.getConnection
} def main(args: Array[String]): Unit = { println(getConnection()) }
}

Properties工具类

/**
* project.properties文件
*/
#jdbc配置
jdbc.datasource.size=10
jdbc.url=jdbc:mysql://hadoop102:3306/steamingproject?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true
jdbc.user=root
jdbc.password=root
jdbc.driver.name=com.mysql.jdbc.Driver # Kafka配置
kafka.broker.list=hadoop102:9092,hadoop103:9092,hadoop104:9092
kafka.topic=mytest
kafka.group.id=cg1

import java.util.ResourceBundle
/**
* Properties文件工具类
*/
object PropertiesUtil { // 绑定配置文件
// ResourceBundle专门用于读取配置文件,所以读取时,不需要增加扩展名
// 国际化 = I18N => Properties
val summer: ResourceBundle = ResourceBundle.getBundle("project") def getValue( key : String ): String = {
summer.getString(key)
} def main(args: Array[String]): Unit = { println(getValue("jdbc.user")) }
}

3.创建BaseApp

/**
* @description: 基础类
* @author: HaoWu
* @create: 2020年08月11日
*/
abstract class BaseApp {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("myAPP")
val ssc: StreamingContext = new StreamingContext(conf, Seconds(3))
//设置消费kafka的参数,可以参考kafka.consumer.ConsumerConfig类中配置说明
val kafkaParams: Map[String, Object] = Map[String, Object](
"bootstrap.servers" -> "hadoop102:9092,hadoop103:9092,hadoop104:9092", //zookeeper的host,port
"group.id" -> "g3", //消费者组
"enable.auto.commit" -> "true", //是否自动提交
"auto.commit.interval.ms" -> "500", //500ms自动提交offset
"key.deserializer" -> "org.apache.kafka.common.serialization.StringDeserializer",
"value.deserializer" -> "org.apache.kafka.common.serialization.StringDeserializer",
"auto.offset.reset" -> "earliest" //第一次运行,从最初始偏移量开始消费数据
) //消费kafka的mytest主题生成DStream
val ds: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String, String](
ssc,
LocationStrategies.PreferConsistent,
//订阅主题
ConsumerStrategies.Subscribe[String, String](List("mytest"),
kafkaParams)) /**
* 将输入流InputDStream[ConsumerRecord[String, String]]=>stream[对象]
* @param ds
* @return
*/
def getAllBeans(ds: InputDStream[ConsumerRecord[String, String]]): DStream[AdsInfo] = {
val result: DStream[AdsInfo] = ds.map(
record => {
val arr: Array[String] = record.value().split(",")
AdsInfo(arr(0).toLong, arr(1), arr(2), arr(3), arr(4))
}
)
result
} /**
* 处理逻辑
* @param opt
*/
def runApp(opt: => Unit): Unit = {
try {
//处理逻辑
opt
//执行程序
ssc.start()
ssc.awaitTermination()
} catch {
case e: Exception => e.getMessage
}
} }

需求一:动态添加黑名单

实现实时的动态黑名单机制:将每天对某个广告点击超过 100 次的用户拉黑。

注:黑名单保存到MySQL中。

思路分析

1)读取Kafka数据之后,并对MySQL中存储的黑名单数据做校验;

2)校验通过则对给用户点击广告次数累加一并存入MySQL;

3)在存入MySQL之后对数据做校验,如果单日超过100次则将该用户加入黑名单。

准备工作

1)存放黑名单用户的表
CREATE TABLE black_list (userid CHAR(2) PRIMARY KEY);
2)存放单日各用户点击每个广告的次数
CREATE TABLE user_ad_count (
dt date,
userid CHAR (2),
adid CHAR (2),
count BIGINT,
PRIMARY KEY (dt, userid, adid)
);
/**
* @description: 需求一:动态添加黑名单
* 说明:实现实时的动态黑名单机制:将每天对某个广告点击超过 100 次的用户拉黑
* (用户,广告id,时间,次数)
* 注:黑名单保存到MySQL中
* @author: HaoWu
* @create: 2020年08月12日
*/
object ProjectDemo_1 extends BaseApp {
def main(args: Array[String]): Unit = {
runApp {
val asdInfo: DStream[AdsInfo] = getAllBeans(ds) /**
* 校验数据是否在黑名单中
*/
def isBlackList(userid: String, connection: Connection): Boolean = {
var flag: Boolean = true
val sql =
"""
|select * from black_list where userid = ?
|""".stripMargin
val ps: PreparedStatement = connection.prepareStatement(sql)
ps.setString(1, userid)
val result: ResultSet = ps.executeQuery()
if (result != null) {
flag = false
}
flag
} //1.聚合当前批次数据((timestamp,userid,adsid),count)
val countDS: DStream[((String, String, String), Long)] = asdInfo.map {
//((2020-08-11,102,1),1)
case adsInfo: AdsInfo => ((adsInfo.dayString, adsInfo.userId, adsInfo.adsId), 1L)
}.reduceByKey(_ + _) countDS.foreachRDD(
rdd => rdd.foreachPartition {
iter => {
//2.向mysql插入数据,准备插入sql和连接
val connection: Connection = JDBCUtil.getConnection()
val sql =
"""
|insert into user_ad_count values(?,?,?,?)
|ON DUPLICATE KEY UPDATE COUNT= count + ?
|""".stripMargin
val ps: PreparedStatement = connection.prepareStatement(sql)
//2.过滤出在名单中的数据
iter.filter {
case ((_, userid, _), _) => val falg = isBlackList(userid, connection); falg
}
//往mysql重插入更新数据
.foreach {
case ((date, userid, adsid), count) => {
ps.setString(1, date)
ps.setString(2, userid)
ps.setString(3, adsid)
ps.setLong(4, count)
ps.setLong(5, count)
ps.executeUpdate()
}
}
//关闭
ps.close() //3.插入成功之后,查询对应得userid点击广告此时是否 > 100?
val sql2 =
"""
|select userid from user_ad_count where count > 20
|""".stripMargin
val ps2: PreparedStatement = connection.prepareStatement(sql2)
val resultSet: ResultSet = ps2.executeQuery()
//封装查询出的黑名单列表
val block_list = new mutable.HashSet[String]()
while (resultSet.next()) {
val userid: String = resultSet.getString("userid")
block_list + userid
}
//关闭resulteSet,PreparedStatement
resultSet.close()
ps2.close() //4.将block_list数据依次插入黑名单表,没有就插入,有就更新
val sql3: String =
"""
|INSERT INTO black_list VALUES (?)
|ON DUPLICATE KEY UPDATE userid=?
|""".stripMargin
val ps3: PreparedStatement = connection.prepareStatement(sql3)
for (userid <- block_list) {
ps3.setString(1, userid)
ps3.setString(2, userid)
ps3.executeUpdate()
}
ps3.close()
connection.close()
}
}
)
} }
}

需求二:广告点击量实时统计

描述:实时统计每天各地区各城市各广告的点击总流量,并将其存入MySQL

步骤:①updateStateByKey有状态累加计算 ②向mysql执行插入更新操作

Mysql表


CREATE TABLE area_city_ad_count (
dt date,
area CHAR(4),
city CHAR(4),
adid CHAR(2),
count BIGINT,
PRIMARY KEY (dt,area,city,adid) --联合主键
);

代码实现

import java.sql.{Connection, PreparedStatement}
import com.spark.streaming_need.bean.AdsInfo
import com.spark.streaming_need.utils.JDBCUtil
import org.apache.spark.streaming.dstream.DStream /**
* @description: 需求二:广告点击量实时统计
* 描述:实时统计每天各地区各城市各广告的点击总流量,并将其存入MySQL
* @author: HaoWu
* @create: 2020年08月11日
*/
object ProjectDemo_2 extends BaseApp {
def main(args: Array[String]): Unit = {
runApp {
//updateStateByKey算子有状态,需要checkpoint
ssc.checkpoint("function2") //1.单个批次内对数据进行按照天维度的聚合统计
//数据格式:1597148289569,华北,北京,102,4
val DsAds: DStream[AdsInfo] = getAllBeans(ds)
val kvDS: DStream[((String, String, String, String), Int)] = DsAds.map {
case (adsInfo) => {
((adsInfo.dayString, adsInfo.area, adsInfo.city, adsInfo.adsId), 1)
}
} //2.结合MySQL数据跟当前批次数据更新原有的数据
//计算当前批次和之前的数据累加结果
val result: DStream[((String, String, String, String), Int)] = kvDS.updateStateByKey {
case (seq, opt) => {
var sum: Int = seq.sum
val value = opt.getOrElse(0)
sum += value
Some(sum)
}
}
//3.将结果写入Mysql
result.foreachRDD(
rdd => {
rdd.foreachPartition {
iter => {
//每个分区创建一个Connection连接
val connection: Connection = JDBCUtil.getConnection()
//准备sql,实现mysql的upsert操作
val sql =
"""
|insert into area_city_ad_count values (?,?,?,?,?)
|on duplicate key update count=?
|""".stripMargin
//PreparedStatement
val ps: PreparedStatement = connection.prepareStatement(sql)
//RDD分区中的每个数据都执行写出
iter.foreach {
case ((dayString, area, city, adsId), count) => {
//填充占位符
ps.setString(1, dayString)
ps.setString(2, area)
ps.setString(3, city)
ps.setString(4, adsId)
ps.setInt(5, count)
ps.setInt(6, count)
//执行写入
ps.executeUpdate()
}
}
//关闭资源
ps.close()
connection.close()
}
}
}
)
}
}
}

需求三:最近一小时广告点击量

需求说明

求最近1h的广告点击量,要求按照以下结果显示

结果展示:
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进行分组处理,组内按照时分排序。

代码实现

import org.apache.spark.streaming.{Minutes, Seconds}
import org.apache.spark.streaming.dstream.DStream /**
* @description: 需求三:最近一小时广告点击量,3秒更新一次
* @author:
* 结果展示:
* 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]
* @create: 2020年08月12日
*/
object ProjectDemo_3 extends BaseApp {
def main(args: Array[String]): Unit = {
//运行app
runApp {
val AdsDStream: DStream[((String, String), Int)] = getAllBeans(ds).map {
case adsInfo => ((adsInfo.adsId, adsInfo.hmString), 1)
}
val result: DStream[(String, List[(String, Int)])] = AdsDStream
//窗口内聚合
.reduceByKeyAndWindow((a: Int, b: Int) => {
a + b
}, Minutes(60), Seconds(3))
.map { case ((adsId, ahmString), count) => (adsId, (ahmString, count)) }
//按照广告id分组
.groupByKey()
//组内按时间升序
.mapValues {
case iter => iter.toList.sortBy(_._1)
}
result.print(10)
}
}
}

结果

-------------------------------------------
Time: 1597234032000 ms
-------------------------------------------
(1,List((20:01,12), (20:02,112), (20:03,98), (20:04,95), (20:05,104), (20:06,96), (20:07,13)))
(2,List((20:01,24), (20:02,97), (20:03,99), (20:04,103), (20:05,95), (20:06,105), (20:07,6)))
(3,List((20:01,30), (20:02,87), (20:03,92), (20:04,108), (20:05,117), (20:06,88), (20:07,22)))
(4,List((20:01,15), (20:02,101), (20:03,100), (20:04,99), (20:05,84), (20:06,112), (20:07,22)))
(5,List((20:01,19), (20:02,103), (20:03,111), (20:04,95), (20:05,100), (20:06,99), (20:07,10))) -------------------------------------------
Time: 1597234035000 ms
-------------------------------------------
(1,List((20:01,12), (20:02,112), (20:03,98), (20:04,95), (20:05,104), (20:06,96), (20:07,20)))
(2,List((20:01,24), (20:02,97), (20:03,99), (20:04,103), (20:05,95), (20:06,105), (20:07,13)))
(3,List((20:01,30), (20:02,87), (20:03,92), (20:04,108), (20:05,117), (20:06,88), (20:07,26)))
(4,List((20:01,15), (20:02,101), (20:03,100), (20:04,99), (20:05,84), (20:06,112), (20:07,26)))
(5,List((20:01,19), (20:02,103), (20:03,111), (20:04,95), (20:05,100), (20:06,99), (20:07,15))) -------------------------------------------
Time: 1597234038000 ms
-------------------------------------------
(1,List((20:01,12), (20:02,112), (20:03,98), (20:04,95), (20:05,104), (20:06,96), (20:07,23)))
(2,List((20:01,24), (20:02,97), (20:03,99), (20:04,103), (20:05,95), (20:06,105), (20:07,16)))
(3,List((20:01,30), (20:02,87), (20:03,92), (20:04,108), (20:05,117), (20:06,88), (20:07,34)))
(4,List((20:01,15), (20:02,101), (20:03,100), (20:04,99), (20:05,84), (20:06,112), (20:07,30)))
(5,List((20:01,19), (20:02,103), (20:03,111), (20:04,95), (20:05,100), (20:06,99), (20:07,20)))

Spark(十七)【SparkStreaming需求练习】的更多相关文章

  1. 基于spark和sparkstreaming的word2vec

    概述 Word2vec是一款由谷歌发布开源的自然语言处理算法,其目的是把words转换成vectors,从而可以用数学的方法来分析words之间的关系.Spark其该算法进行了封装,并在mllib中实 ...

  2. spark or sparkstreaming的内存泄露问题?

    关于sparkstreaming的无法正常产生数据---->到崩溃---->到数据读写极为缓慢(或块丢失?)问题 前两阶段请看我的博客:https://www.cnblogs.com/wa ...

  3. 【Spark】SparkStreaming和Kafka的整合

    文章目录 Streaming和Kafka整合 概述 使用0.8版本下Receiver DStream接收数据进行消费 步骤 一.启动Kafka集群 二.创建maven工程,导入jar包 三.创建一个k ...

  4. 【Spark】SparkStreaming与flume进行整合

    文章目录 注意事项 SparkStreaming从flume中poll数据 步骤 一.开发flume配置文件 二.启动flume 三.开发sparkStreaming代码 1.创建maven工程,导入 ...

  5. 【Spark】SparkStreaming从不同基本数据源读取数据

    文章目录 基本数据源 文件数据源 注意事项 步骤 一.创建maven工程并导包 二.在HDFS创建目录,并上传要做测试的数据 三.开发SparkStreaming代码 四.运行代码后,往HDFS文件夹 ...

  6. 【Spark】SparkStreaming的容错机制

    文章目录 检查点机制 驱动器程序容错 工作节点容错 接收器容错 处理保证 检查点机制 Metadata checkpointing -- 将定义流计算的信息存入容错的系统如HDFS. Data che ...

  7. Spark之 Spark Streaming流式处理

    SparkStreaming Spark Streaming类似于Apache Storm,用于流式数据的处理.Spark Streaming有高吞吐量和容错能力强等特点.Spark Streamin ...

  8. Spark2.1.0之初识Spark

    随着近十年互联网的迅猛发展,越来越多的人融入了互联网——利用搜索引擎查询词条或问题:社交圈子从现实搬到了Facebook.Twitter.微信等社交平台上:女孩子们现在少了逛街,多了在各大电商平台上的 ...

  9. Spark基础知识详解

    Apache Spark是一种快速通用的集群计算系统. 它提供Java,Scala,Python和R中的高级API,以及支持通用执行图的优化引擎. 它还支持一组丰富的高级工具,包括用于SQL和结构化数 ...

随机推荐

  1. hdu 5171 GTY's birthday gift(数学,矩阵快速幂)

    题意: 开始时集合中有n个数. 现在要进行k次操作. 每次操作:从集合中挑最大的两个数a,b进行相加,得到的数添加进集合中. 以此反复k次. 问最后集合中所有数的和是多少. (2≤n≤100000,1 ...

  2. hdu 5094 Maze (BFS+状压)

    题意: n*m的迷宫.多多要从(1,1)到达(n,m).每移动一步消耗1秒.有P种钥匙. 有K个门或墙.给出K个信息:x1,y1,x2,y2,gi    含义是(x1,y1)与(x2,y2)之间有gi ...

  3. 集合之Map接口

    Map接口概述 Map与Collection并列存在.用于存储具有映射关系的数据 : key-value Map 中的 key 和 value 都可以是任何引用类型的数据 Map 中的 key 用Se ...

  4. 讲分布式唯一id,这篇文章很实在

    分布式唯一ID介绍 分布式系统全局唯一的 id 是所有系统都会遇到的场景,往往会被用在搜索,存储方面,用于作为唯一的标识或者排序,比如全局唯一的订单号,优惠券的券码等,如果出现两个相同的订单号,对于用 ...

  5. ssh密码登录

    https://stackoverflow.com/a/16928662/8025086 https://askubuntu.com/a/634789/861079 #!/usr/bin/expect ...

  6. css--元素居中常用方法总结

    前言 元素居中是日常开发和学习中最常见的问题,同时也是面试中经常考察的知识点,本文来总结一下这方面的知识点. 正文 1.水平居中 (1)子父元素宽度固定,子元素设置 margin:auto,并且子元素 ...

  7. 7.深入TiDB:range 范围计算优化

    本文基于 TiDB release-5.1进行分析,需要用到 Go 1.16以后的版本 我的博客地址:https://www.luozhiyun.com/archives/605 这篇文章首先会回顾一 ...

  8. Linux基础四:软件包管理

    四.软件包管理器: 1.概念 红帽有两款软件包管理器,分别是rpm和yum. 1.rpm软件包管理器  ->  用来安装单个包  ->  .rpm文件 红帽的安装包文件,都放在Packag ...

  9. 【linux系统】命令学习(五)linux三剑客 grep \ awk \ sed

    grep----基于正则表达式查找满足条件的行 1.内容检索 获取行 grep pattern file 获取内容 grep -o pattern file 获取上下文grep -A -B -C pa ...

  10. 常用的Dos(Win+R)命令

    打开CMD的方式 开始 + 系统 + 命令提示符 win + R --> 输入CMD 管理员方式运行:开始-->windows系统-->右击命令提示符-->管理员身份运行(最高 ...