主要问题
处理流式数据的两种方法
静态数据的几种格式
要介绍流式数据的处理,首先要介绍spark的几种静态的数据结构:RDD、dataset和dataframe。
简单来讲,RDD是spark最基础的数据,可以看出一行行独立的数据,每一行内部是封闭的黑箱,在MapReduce之前不知道是啥(MapReduce是hadoop的一种计算模型,浅显一点理解就是做筛选统计之类的活的,就是下图紫色的过程)。
图出自https://blog.csdn.net/MrZhangBaby/article/details/88840635,看不清可以点进去看
Dataset是整理过的RDD,同样可以理解为一行行的数据,只是里面更有序(有了固定的结构schema)
Dataframe就是Python和R里的那种dataframe,也就是最常见的带表头的表,是Dataset的特例,链接里的图就很形象
https://blog.csdn.net/weixin_42702831/article/details/82492421
RDD
Dataframe
Dataset
或者是
流式数据的2种处理方式
从文件里读数据,读一次就产生一个静态数据,而像日志之类的记录这种不断增长的数据,可以看做是一个瀑布流,源源不断的增长。针对这种流式数据(data stream)的处理,要么用flink(flink其实是处理流式数据更专业的方法,和spark、kafka一样都是apache的顶级项目,阿里开源的blink就是包含于flink的),要么用spark,这里由于自身原因使用spark。用spark一般有2种方法
- RDD转Dstream
因为RDD是最基础的数据格式,所以数据流最开始就是不断增长的RDD。不断增长怎么统计计算呢?切片!举个栗子,按照5分钟的间隔,将间隔内的数据增量作为整体(一个batch)进行计算。切片的原理很简单,可是5分钟的间隔感觉不够实时,那按微积分的思想,切成0.1s的间隔,就可以看成实时数据了。。。事实上batch的划分还是受限于数据量和可用资源,分的越细,耗费的资源自然就越大。官方文档里给的例子是1秒,想来应该是够用的。 - RDD转Dataframe
Dstream是Discretized Streams的缩写,顾名思义,是离散的。处理过程是先将整体打散,分别处理,然后再合起来。而RDD转Dataframe使用StructureStreaming,将不断增长的数据,变换为一张不断增长的具有结构(structure或者说schema)的大表,进而可以把它当成写SQL一样,直接处理当前时间的整体。如下图所示。
计算结果输出到kafka
使用RDD的Dstream(旧接口)
其实官方的例子已经很完善了,这里尽可能简化一下项目的代码,贴一些注释,可以结合着文档的例子看看。
object SparkStreamingObj {
def main(args: Array[String]): Unit = { //上面的都是固定写法,和java一样
val conf = new SparkConf() //spark的入口设置
.setMaster("local[*]")
.setAppName("test") //设置
val processInterval = '1' //这个就是切片的间隔,可以直接设置成1s
val ssc = new StreamingContext(conf, Seconds(processInterval))
val brokers = 'localhost:9092' //这里是数据的来源,kafka的地址
val topics = 'topic1,topic2' //kafka的topic
//kafka配置参数
val kafkaParams: Map[String, String] = Map[String, String](
"metadata.broker.list" -> brokers,
"serializer.class" -> "kafka.serializer.StringEncoder")
val topicslist = topics.split(",").toSet
val brokerList = host.split(",").toList
val stream = createDirectStream(ssc, kafkaParams, topicslist )//从kafka接收数据
val outtopic = "outtopic" //以kafka的方式将结果输出
val outbrokers = "localhost:9092" // kafka输出地址
val kafkaProducer: Broadcast[KafkaSink[String, String]] = {
val kafkaProducerConfig = {
val p = new Properties()
p.setProperty("bootstrap.servers", outbrokers)
p.setProperty("key.serializer", classOf[StringSerializer].getName)
p.setProperty("value.serializer", classOf[StringSerializer].getName)
p
}
ssc.sparkContext.broadcast(KafkaSink[String, String](kafkaProducerConfig))}
val windows = stream.map(_._2).window(Seconds(300),Seconds(60)) //设置时间窗,作为计算间隔
windows.foreachRDD{ rdd =>
val spark = SparkSession
.builder
.config(rdd.sparkContext.getConf)
// .config("spark.sql.crossJoin.enabled","true")//开启两表连接功能
.getOrCreate()
import spark.implicits._
val df = rdd.toDF("json")
val DF=df.select(
get_json_object($"json","$.meta.table").alias("table"),
.filter(" table ='table1'")
DF.createOrReplaceTempView("table_temp") //创建临时表,便于sql直接查询
val sqlText = "select count(*) as index from table_temp "
val result = spark.sql(sqlText)
.withColumn("sys",lit("sys1")) //withColum用于拼接列
.withColumn("timestamp", functions.current_timestamp()) //current_timestamp插入当前时间
// result.show(truncate = false)
// println("=====================================================")
//输出到kafka
val kafaValue = result.toJSON.collectAsList().toString
kafkaProducer.value.send(outtopic,kafaValue.substring(1, (kafaValue.length()-1)))// 将list的[]去掉,变成json
}
ssc.start()
ssc.awaitTermination()
}
def createDirectStream(scc: StreamingContext, kafkaParam: Map[String, String], topics: Set[String]) = {
KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](scc, kafkaParam, topics)
}
}
使用Structure Streaming(新接口)
object SparkStreamingObj {
def main(args: Array[String]): Unit = {//导入kafka参数
val app_conf = ConfigFactory.load("application.conf")
val brokers = app_conf.getString("kafka-config.brokers")
val topics = app_conf.getString("kafka-config.topic")
val spark = SparkSession
.builder()
.appName("appTest")
// .master("local[*]") //跑集群要注释掉
.getOrCreate()
import spark.implicits._
val inputstream = spark.readStream
.format("kafka")
.option("kafka.bootstrap.servers", brokers)
.option("subscribe", "topic_test") //官方用法
// .option("startingoffset", "smallest")
// .option("topics", topics) //第三方08kafka用法
.load()
//低版本spark无法自动识别字符串的schema,需要手动指定。
//kafka10版本以上也可以通过给一个实例自动推断schema
val schema = StructType(Seq(
StructField("meta",StringType,true), StructField("data",StructType(Seq(
StructField("ID",StringType,true),
StructField("NAME",StringType,true)
,StructField("SSSS",StringType,true)
)))))
val df=inputstream
.selectExpr("cast(value as string) as json") //值转string再转json
.select(from_json($"json",schema=schema).as("data") ) //筛选json字段
.select("data.data.*")
df.createOrReplaceTempView("TempTable")
val sqlText = "select * from TempTable"
//将所有符合条件的数筛出来加时间戳,再按1分钟统计
val DF = spark
.sql(sqlText)
.withColumn("timestamp", functions.current_timestamp()) //增加列,方便按时间统计
.groupBy(
window($"timestamp", "1 minutes", "60 seconds")
)
.count()
//====================输出到kafka=========================
val outtopic = "OutTopic"
val outbrokers = "OutBroker"
val writer = new KafkaSink(outtopic, outbrokers)
//关于kafkasink的官方说明http://spark.apache.org/docs/latest/structured-streaming-kafka-integration.html
val query = DF
.writeStream
.foreach(writer)
// .outputMode("update")
// .outputMode("append")
.outputMode("complete")
.trigger(ProcessingTime("10 seconds"))
.start()
query.awaitTermination()
}
}
来源:CSDN
作者:yiyizhl
链接:https://blog.csdn.net/yiyizhl/article/details/103069584