大数据Spark实时搜索日志实时分析_百度搜索日志数据集

val kafkaDStream: DStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream(
  ssc, locationStrategy, consumerStrategy
)
// vi.返回DStream
kafkaDStream

}
}


## 3 实时数据ETL存储


实时从Kafka Topic消费数据,提取ip地址字段,调用【ip2Region】库解析为省份和城市,存储到HDFS文件中,设置批处理时间间隔BatchInterval为10秒,完整代码如下:



package cn.oldlut.spark.app.etl

import cn.oldlut.spark.app.StreamingContextUtils
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.spark.rdd.RDD
import org.apache.spark.streaming.StreamingContext
import org.apache.spark.streaming.dstream.DStream
import org.lionsoul.ip2region.{DataBlock, DbConfig, DbSearcher}

/**
* 实时消费Kafka Topic数据,经过ETL(过滤、转换)后,保存至HDFS文件系统中,BatchInterval为:10s
*/
object StreamingETLHdfs {
def main(args: Array[String]): Unit = {
// 1. 获取StreamingContext实例对象
val ssc: StreamingContext = StreamingContextUtils.getStreamingContext(this.getClass, 10)
// 2. 从Kafka消费数据,使用Kafka New Consumer API
val kafkaDStream: DStream[ConsumerRecord[String, String]] = StreamingContextUtils
.consumerKafka(ssc, “search-log-topic”)
// 3. 数据ETL:过滤不合格数据及转换IP地址为省份和城市,并存储HDFS上
kafkaDStream.foreachRDD { (rdd, time) =>
// i. message不为null,且分割为4个字段
val kafkaRDD: RDD[ConsumerRecord[String, String]] = rdd.filter { record =>
val message: String = record.value()
null != message && message.trim.split(“,”).length == 4
}
// ii. 解析IP地址
val etlRDD: RDD[String] = kafkaRDD.mapPartitions { iter =>
// 创建DbSearcher对象,针对每个分区创建一个,并不是每条数据创建一个
val dbSearcher = new DbSearcher(new DbConfig(), “dataset/ip2region.db”)
iter.map { record =>
val Array(_, ip, _, ) = record.value().split(“,”)
// 依据IP地址解析
val dataBlock: DataBlock = dbSearcher.btreeSearch(ip)
val region: String = dataBlock.getRegion
val Array(
, _, province, city, _) = region.split(“\|”)
// 组合字符串
s" r e c o r d . v a l u e ( ) , {record.value()}, record.value(),province,KaTeX parse error: Expected 'EOF', got '}' at position 15: city" }̲ } …{time.milliseconds}"
if (!etlRDD.isEmpty()) {
etlRDD.coalesce(1).saveAsTextFile(savePath)
}
}
// 4.启动流式应用,一直运行,直到程序手动关闭或异常终止
ssc.start()
ssc.awaitTermination()
ssc.stop(stopSparkContext = true, stopGracefully = true)
}
}


运行模拟日志数据程序和ETL应用程序,查看实时数据ETL后保存文件,截图如下:![在这里插入图片描述](https://img-blog.csdnimg.cn/cbeb8fbb51e240d1804bba2941463a8d.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6LW15bm_6ZmG,size_13,color_FFFFFF,t_70,g_se,x_16)


## 4 实时状态更新统计


实 时 累 加 统 计 用 户 各 个 搜 索 词 出 现 的 次 数 , 在 SparkStreaming 中 提 供 函 数【updateStateByKey】实现累加统计,Spark 1.6提供【mapWithState】函数状态统计,性能更好,实际应用中也推荐使用。


### 4.1 updateStateByKey 函数


状态更新函数【updateStateByKey】表示依据Key更新状态,要求DStream中数据类型为【Key/Value】对二元组,函数声明如下:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/affad248a3bc4288b721de1c22fb84d3.png)  
 将每批次数据状态,按照Key与以前状态,使用定义函数【updateFunc】进行更新,示意图如下:![在这里插入图片描述](https://img-blog.csdnimg.cn/a5ef83ccfb1d40cba61a2f11b0fb858d.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6LW15bm_6ZmG,size_20,color_FFFFFF,t_70,g_se,x_16)


文档: http://spark.apache.org/docs/2.4.5/streaming-programming-guide.html#updatestatebykey-operation  
 针对搜索词词频统计WordCount,状态更新逻辑示意图如下:![在这里插入图片描述](https://img-blog.csdnimg.cn/2d44873677024f87bc835fc3d49c61ea.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6LW15bm_6ZmG,size_20,color_FFFFFF,t_70,g_se,x_16)  
 以前的状态数据,保存到Checkpoint检查点目录中,所以在代码中需要设置Checkpoint检查点目录:![在这里插入图片描述](https://img-blog.csdnimg.cn/eb38013645794b46b32a1b94e6ef5f9d.png)  
 完整演示代码如下:



package cn.oldlut.spark.app.state

import cn.oldlut.spark.app.StreamingContextUtils
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.spark.streaming.StreamingContext
import org.apache.spark.streaming.dstream.DStream

/**
* 实时消费Kafka Topic数据,累加统计各个搜索词的搜索次数,实现百度搜索风云榜
*/
object StreamingUpdateState {
def main(args: Array[String]): Unit = {
// 1. 获取StreamingContext实例对象
val ssc: StreamingContext = StreamingContextUtils.getStreamingContext(this.getClass, 5)
// TODO: 设置检查点目录
ssc.checkpoint(s"datas/streaming/state-${System.nanoTime()}“)
// 2. 从Kafka消费数据,使用Kafka New Consumer API
val kafkaDStream: DStream[ConsumerRecord[String, String]] = StreamingContextUtils
.consumerKafka(ssc, “search-log-topic”)
// 3. 对每批次的数据进行搜索词次数统计
val reduceDStream: DStream[(String, Int)] = kafkaDStream.transform { rdd =>
val reduceRDD = rdd
// 过滤不合格的数据
.filter { record =>
val message: String = record.value()
null != message && message.trim.split(”,“).length == 4
}
// 提取搜索词,转换数据为二元组,表示每个搜索词出现一次
.map { record =>
val keyword: String = record.value().trim.split(”,").last
keyword -> 1
}
// 按照单词分组,聚合统计
.reduceByKey((tmp, item) => tmp + item) // TODO: 先聚合,再更新,优化
reduceRDD // 返回
}
/*
def updateStateByKey[S: ClassTag](
// 状态更新函数
updateFunc: (Seq[V], Option[S]) => Option[S]
): DStream[(K, S)]
第一个参数:Seq[V]
表示的是相同Key的所有Value值
第二个参数:Option[S]
表示的是Key的以前状态,可能有值Some,可能没值None,使用Option封装
S泛型,具体类型有业务具体,此处是词频:Int类型
*/
val stateDStream: DStream[(String, Int)] = reduceDStream.updateStateByKey(
(values: Seq[Int], state: Option[Int]) => {
// a. 获取以前状态信息
val previousState = state.getOrElse(0)
// b. 获取当前批次中Key对应状态
val currentState = values.sum
// c. 合并状态
val latestState = previousState + currentState
// d. 返回最新状态
Some(latestState)
}
)
// 5. 将结果数据输出 -> 将每批次的数据处理以后输出
stateDStream.print()
// 6.启动流式应用,一直运行,直到程序手动关闭或异常终止
ssc.start()
ssc.awaitTermination()
ssc.stop(stopSparkContext = true, stopGracefully = true)
}
}


运行应用程序,通过WEB UI界面可以发现,将以前状态保存到Checkpoint检查点目录中,更新时在读取。![在这里插入图片描述](https://img-blog.csdnimg.cn/ffbdaccf31c042bda08b5f449968aff0.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6LW15bm_6ZmG,size_20,color_FFFFFF,t_70,g_se,x_16)  
 此外,updateStateByKey函数有很多重载方法,依据不同业务需求选择合适的方式使用。


### 4.2 mapWithState 函数


Spark 1.6提供新的状态更新函数【mapWithState】,mapWithState函数也会统计全局的key的状态,但是如果没有数据输入,便不会返回之前的key的状态,只是关心那些已经发生的变化的key,对于没有数据输入,则不会返回那些没有变化的key的数据。![在这里插入图片描述](https://img-blog.csdnimg.cn/3ae4449721934b38915ed5bb89040136.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6LW15bm_6ZmG,size_20,color_FFFFFF,t_70,g_se,x_16)  
 这样的话,即使数据量很大,checkpoint也不会像updateStateByKey那样,占用太多的存储,效率比较高;![在这里插入图片描述](https://img-blog.csdnimg.cn/ae55e536ed4d4e40940c5d3db1ef7309.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6LW15bm_6ZmG,size_20,color_FFFFFF,t_70,g_se,x_16)  
 需要构建StateSpec对象,对状态State进行封装,可以进行相关操作,类的声明定义如下:![在这里插入图片描述](https://img-blog.csdnimg.cn/f27bdfbf1d6b415da6485cdfb66cd5cf.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6LW15bm_6ZmG,size_20,color_FFFFFF,t_70,g_se,x_16)  
 状态函数【mapWithState】参数相关说明:![在这里插入图片描述](https://img-blog.csdnimg.cn/6206c086222e4c28bc12b91c557ae055.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6LW15bm_6ZmG,size_20,color_FFFFFF,t_70,g_se,x_16)  
 完整演示代码如下:



package cn.oldlut.spark.app.state

import cn.oldlut.spark.app.StreamingContextUtils
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.spark.rdd.RDD
import org.apache.spark.streaming.{State, StateSpec, StreamingContext}
import org.apache.spark.streaming.dstream.DStream

/**
* 实时消费Kafka Topic数据,累加统计各个搜索词的搜索次数,实现百度搜索风云榜
*/
object StreamingMapWithState {
def main(args: Array[String]): Unit = {
// 1. 获取StreamingContext实例对象
val ssc: StreamingContext = StreamingContextUtils.getStreamingContext(this.getClass, 5)
// TODO: 设置检查点目录
ssc.checkpoint(s"datas/streaming/state-${System.nanoTime()}“)
// 2. 从Kafka消费数据,使用Kafka New Consumer API
val kafkaDStream: DStream[ConsumerRecord[String, String]] = StreamingContextUtils
.consumerKafka(ssc, “search-log-topic”)
// 3. 对每批次的数据进行搜索词进行次数统计
val reduceDStream: DStream[(String, Int)] = kafkaDStream.transform { rdd =>
val reduceRDD: RDD[(String, Int)] = rdd
// 过滤不合格的数据
.filter { record =>
val message: String = record.value()
null != message && message.trim.split(”,“).length == 4
}
// 提取搜索词,转换数据为二元组,表示每个搜索词出现一次
.map { record =>
val keyword: String = record.value().trim.split(”,").last
keyword -> 1
}
// 按照单词分组,聚合统计
.reduceByKey((tmp, item) => tmp + item) // TODO: 先聚合,再更新,优化
// 返回
reduceRDD
}
// TODO: 4、实时累加统计搜索词搜索次数,使用mapWithState函数
/*
按照Key来更新状态的,一条一条数据的更新状态
def mapWithState[StateType: ClassTag, MappedType: ClassTag](
spec: StateSpec[K, V, StateType, MappedType]
): MapWithStateDStream[K, V, StateType, MappedType]
a. 通过函数源码发现参数使用对象
StateSpec 实例对象
b. StateSpec
表示对状态封装,里面涉及到相关数据类型
c. 如何构建StateSpec对象实例呢??
StateSpec 伴生对象中function函数构建对象
def function[KeyType, ValueType, StateType, MappedType](
// 从函数名称可知,针对每条数据更新Key的转态信息
mappingFunction: (KeyType, Option[ValueType], State[StateType]) => MappedType
): StateSpec[KeyType, ValueType, StateType, MappedType]
*/
// 状态更新函数,针对每条数据进行更新状态
val spec: StateSpec[String, Int, Int, (String, Int)] = StateSpec.function(
// (KeyType, Option[ValueType], State[StateType]) => MappedType
(keyword: String, countOption: Option[Int], state: State[Int]) => {
// a. 获取当前批次中搜索词搜索次数
val currentState: Int = countOption.getOrElse(0)
// b. 从以前状态中获取搜索词搜索次数
val previousState = state.getOption().getOrElse(0)
// c. 搜索词总的搜索次数
val latestState = currentState + previousState
// d. 更行状态
state.update(latestState)
// e. 返回最新省份销售订单额
(keyword, latestState)
}
)
// 调用mapWithState函数进行实时累加状态统计
val stateDStream: DStream[(String, Int)] = reduceDStream.mapWithState(spec)
// 5. 将结果数据输出 -> 将每批次的数据处理以后输出
stateDStream.print()
// 6.启动流式应用,一直运行,直到程序手动关闭或异常终止
ssc.start()
ssc.awaitTermination()
ssc.stop(stopSparkContext = true, stopGracefully = true)
}
}


运行程序可以发现,当Key(搜索单词)没有出现时,不会更新状态,仅仅更新当前批次中出现的Key的状态。  
 mapWithState 实现有状态管理主要是通过两点:a)、历史状态需要在内存中维护,这里必需的了,updateStateBykey也是一样;b)、自定义更新状态的mappingFunction,这些就是具体的业务功能实现逻辑了(什么时候需要更新状态)![在这里插入图片描述](https://img-blog.csdnimg.cn/33a4ee8b3a4f4f90ba00dd74139b08e2.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6LW15bm_6ZmG,size_20,color_FFFFFF,t_70,g_se,x_16)  
 首先数据像水流一样从左侧的箭头流入,把mapWithState看成一个转换器的话,mappingFunc就是转换的规则,流入的新数据(key-value)结合历史状态(通过key从内存中获取的历史状态)进行一些自定义逻辑的更新等操作,最终从红色箭头中流出。


## 5 实时窗口统计


SparkStreaming中提供一些列窗口函数,方便对窗口数据进行分析,文档:



http://spark.apache.org/docs/2.4.5/streaming-programming-guide.html#window-operations


在实际项目中,很多时候需求:每隔一段时间统计最近数据状态,并不是对所有数据进行统计,称为趋势统计或者窗口统计,SparkStreaming中提供相关函数实现功能,业务逻辑如下:![在这里插入图片描述](https://img-blog.csdnimg.cn/8add8a626819453eb271ead67d836afb.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6LW15bm_6ZmG,size_20,color_FFFFFF,t_70,g_se,x_16)  
 针对用户百度搜索日志数据,实现【近期时间内热搜Top10】,统计最近一段时间范围(比如,最近半个小时或最近2个小时)内用户搜索词次数,获取Top10搜索词及次数。窗口函数【window】声明如下,包含两个参数:窗口大小(WindowInterval,每次统计数据范围)和滑动大小(每隔多久统计一次),都必须是批处理时间间隔BatchInterval整数倍。![在这里插入图片描述](https://img-blog.csdnimg.cn/4e1fa1911431414fba2fbe93a3017613.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6LW15bm_6ZmG,size_20,color_FFFFFF,t_70,g_se,x_16)  
 案例完整实现代码如下,为了演示方便,假设BatchInterval为2秒,WindowInterval  
 为4秒,SlideInterval为2秒。



package cn.oldlut.spark.app.window

import cn.oldlut.spark.app.StreamingContextUtils
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.{Seconds, StreamingContext}

/**

  • 实时消费Kafka Topic数据,每隔一段时间统计最近搜索日志中搜索词次数
  • 批处理时间间隔:BatchInterval = 2s
  • 窗口大小间隔:WindowInterval = 4s
  • 滑动大小间隔:SliderInterval = 2s
    /
    object StreamingWindow {
    def main(args: Array[String]): Unit = {
    // Streaming应用BatchInterval
    val BATCH_INTERVAL: Int = 2
    // Streaming应用窗口大小
    val WINDOW_INTERVAL: Int = BATCH_INTERVAL * 2
    val SLIDER_INTERVAL: Int = BATCH_INTERVAL * 1
    // 1. 获取StreamingContext实例对象
    val ssc: StreamingContext = StreamingContextUtils.getStreamingContext(this.getClass, BATCH_INTERVAL)
    // 2. 从Kafka消费数据,使用Kafka New Consumer API
    val kafkaDStream: DStream[String] = StreamingContextUtils
    .consumerKafka(ssc, “search-log-topic”)
    .map(record => record.value())
    // TODO: 添加窗口,设置对应参数
    /

    def window(windowDuration: Duration, slideDuration: Duration): DStream[T]
    警告信息:
    ERROR KafkaRDD: Kafka ConsumerRecord is not serializable.
    Use .map to extract fields before calling .persist or .window
    */
    val windowDStream: DStream[String] = kafkaDStream.window(
    Seconds(WINDOW_INTERVAL), Seconds(SLIDER_INTERVAL)
    )
    // 4. 对每批次的数据进行搜索词进行次数统计
    val countDStream: DStream[(String, Int)] = windowDStream.transform { rdd =>
    val resultRDD = rdd
    // 过滤不合格的数据
    .filter(message => null != message && message.trim.split(“,”).length == 4)
    // 提取搜索词,转换数据为二元组,表示每个搜索词出现一次
    .map { message =>
    val keyword: String = message.trim.split(“,”).last
    keyword -> 1
    }
    // 按照单词分组,聚合统计
    .reduceByKey((tmp, item) => tmp + item)
    // 返回
    resultRDD
    }
    // 5. 将结果数据输出 -> 将每批次的数据处理以后输出
    countDStream.print()
    // 6.启动流式应用,一直运行,直到程序手动关闭或异常终止
    ssc.start()
    ssc.awaitTermination()
    ssc.stop(stopSparkContext = true, stopGracefully = true)
    }
    }

SparkStreaming中同时提供将窗口Window设置与聚合reduceByKey合在一起的函数,为了更加方便编程。![在这里插入图片描述](https://img-blog.csdnimg.cn/afa75a7ad7f34880a81fe05eb7669283.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6LW15bm_6ZmG,size_20,color_FFFFFF,t_70,g_se,x_16)  
 使用【reduceByKeyAndWindow】函数,修改上述代码,实现窗口统计,具体代码如下:



package cn.oldlut.spark.app.window

import cn.oldlut.spark.app.StreamingContextUtils
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.{Seconds, StreamingContext}

/**
* 实时消费Kafka Topic数据,每隔一段时间统计最近搜索日志中搜索词次数
* 批处理时间间隔:BatchInterval = 2s
* 窗口大小间隔:WindowInterval = 4s
* 滑动大小间隔:SliderInterval = 2s
*/
object StreamingReduceWindow {
def main(args: Array[String]): Unit = {
// Streaming应用BatchInterval
val BATCH_INTERVAL: Int = 2
// Streaming应用窗口大小
val WINDOW_INTERVAL: Int = BATCH_INTERVAL * 2
val SLIDER_INTERVAL: Int = BATCH_INTERVAL * 1
// 1. 获取StreamingContext实例对象
val ssc: StreamingContext = StreamingContextUtils.getStreamingContext(this.getClass, BATCH_INTERVAL)
// 2. 从Kafka消费数据,使用Kafka New Consumer API
val kafkaDStream: DStream[String] = StreamingContextUtils
.consumerKafka(ssc, “search-log-topic”)
.map(recored => recored.value())
// 3. 对每批次的数据进行搜索词进行次数统计
val etlDStream: DStream[(String, Int)] = kafkaDStream.transform { rdd =>
val etlRDD = rdd
// 过滤不合格的数据
.filter(message => null != message && message.trim.split(“,”).length == 4)
// 提取搜索词,转换数据为二元组,表示每个搜索词出现一次
.map { message =>
val keyword: String = message.trim.split(“,”).last
keyword -> 1
}
etlRDD // 返回
}
// 4. 对获取流式数据进行ETL后,使用窗口聚合函数统计计算
/*
def reduceByKeyAndWindow(
reduceFunc: (V, V) => V, // 聚合函数
windowDuration: Duration, // 窗口大小
slideDuration: Duration // 滑动大小
): DStream[(K, V)]
*/
val resultDStream: DStream[(String, Int)] = etlDStream.reduceByKeyAndWindow(
(tmp: Int, value: Int) => tmp + value, //
Seconds(WINDOW_INTERVAL), //
Seconds(SLIDER_INTERVAL) //
)
// 5. 将结果数据输出 -> 将每批次的数据处理以后输出
resultDStream.print()
// 6.启动流式应用,一直运行,直到程序手动关闭或异常终止
ssc.start()
ssc.awaitTermination()
ssc.stop(stopSparkContext = true, stopGracefully = true)
}
}







![img](https://img-blog.csdnimg.cn/img_convert/47a695ce623ee21f11ca2a9c1e7a4b39.png)
![img](https://img-blog.csdnimg.cn/img_convert/8dcdf3b632a4e981a400c031c1057b7d.png)
![img](https://img-blog.csdnimg.cn/img_convert/9f34b025ed89c69df63c2729873398fb.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**

tion()
    ssc.stop(stopSparkContext = true, stopGracefully = true)
  }
}

[外链图片转存中…(img-xGhpGfXv-1714293625223)]
[外链图片转存中…(img-q2gkpYBt-1714293625223)]
[外链图片转存中…(img-zPLUXpAU-1714293625223)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

  • 12
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值