1.简单介绍
我在写这个博客的时候spark已经出到2.4.0的版本了,在基础的板块里面spark官网上有strucrtred Streaming的应用。有兴趣的话可以去官网上去看看。
2.话不多说,代码奉上
1.第一步,使用结构的的流读取kafka的消息(这里关于kafka的部分就不多做解释了),
//创建SparkSession
val spark =
SparkSession.builder().appName("structured_streamingApp").master("local[*]").getOrCreate()
spark.sparkContext.setLogLevel("OFF")//由于日志信息太多,设置了一下输出等级
//使用结构化的流读取卡夫卡中的内容
import spark.implicits._
val df = spark.readStream
.format("kafka")
.option("kafka.bootstrap.servers", "bigdata1:9092")
.option("subscribe", "test2")
.option("startingOffsets", "latest")//注意流式处理没有endingOffset
.option("includeTimestamp", value = true)//输出内容包括时间戳
.load()
val dataSet: Dataset[(String, Timestamp)] = df.selectExpr("CAST(value AS STRING)", "CAST(timestamp AS TIMESTAMP)")
.as[(String, Timestamp)]//这里定义了消费结构,value表示kafka消息键值对的value数据,timestamp表示当时时间戳。
- 将接受到的DataSet[Row]定义字段,以便于下一步的spark-sql操作。
//对kafka的数据进行处理
val words = dataSet.map(line => {
val lineArr = line._1.split("\001")
//将传入的数据根据\001切割然后添加成DataFrame的字段
(lineArr(0), lineArr(1), lineArr(2), lineArr(3), lineArr(4), lineArr(5).toInt, lineArr(6), lineArr(7), line._2)
}).toDF("date", "monitorId", "cameraId", "car", "actionTime", "speed", "roadId", "areaId", "timestamp")
- 下一步就是定义窗口,在这一步就有两种操作,一个是聚合模式,一个是非聚合的。我在这里演示一下聚合的。
import org.apache.spark.sql.functions._//使用window函数要导入
val windowedCounts: DataFrame = words.groupBy(
//根据窗口、卡口id、区域id、道路id分组
window($"timestamp","300 seconds","5 seconds").alias("windows"), $"monitorId",
$"roadId", $"areaId"
).agg(avg("speed").alias("aspeed"))//根据速度的总和和速度的个数求出平均速度
这里的agg方法有兴趣的可以自己去看一下源码。对DataFrame根据窗口分组后就不能使用像map这样的函数了。只有一下想max、min、avg等这样的函数能使用。
- 最后就是对分析后的实时流数据的输出方式,这里我提供了两种简单的方式。
4.1 输出到控制台
//再将处理好的数据重新写入到kafka中输出
val query = windowedCounts.writeStream
.outputMode("complete")//三种模式:append、complete、update
//检查点必须设置,不然会报错
.option("checkpointLocation", "/spark/test/checkpoint_realTime_4")
.format("console")//控制台输出
.option("truncate", "false")//是输出的信息是否截断
.start()
query.awaitTermination()//等待结束,必须要写。
关于输出模式的参考链接:https://blog.youkuaiyun.com/zeroder/article/details/73650738
4.2 输出导入到mysql
//创建流失数据写入格式对象
val mysqlSink = new MysqlSink("jdbc:mysql://hostName:3306/tableName", "userName", "pwd")
//再将处理好的数据重新写入到kafka中输出
val query = windowedCounts.writeStream
.outputMode("complete")//三种模式:append、complete、update
//检查点必须设置,不然会报错
.option("checkpointLocation", "/spark/test/checkpoint_realTime_4")
.foreach(mysqlSink)//输出到mysql
.start()
query.awaitTermination()//等待结束
注意:因为structred streaming暂时没有直接提供mysql的输出格式,我们就要自己定义一个mysql输出通道类。
import java.sql.{Connection, DriverManager}
import org.apache.spark.sql.{ForeachWriter, Row}
/**
* mysql输出类
*/
class MysqlSink(url: String, userName: String, pwd: String) extends ForeachWriter[Row]{
//创建连接对象
var conn: Connection = _
//建立连接
override def open(partitionId: Long, version: Long): Boolean = {
Class.forName("com.mysql.jdbc.Driver")
conn = DriverManager.getConnection(url, userName, pwd)
true
}
//数据写入mysql
override def process(value: Row): Unit = {
val p = conn.prepareStatement("replace into traffic(windows, monitorId, roadId, areaId, aspeed) values(?,?,?,?,?)")
p.setString(1, value(0).toString)
p.setString(2, value(1).toString)
p.setString(3, value(2).toString)
p.setString(4, value(3).toString)
p.setString(5, value(4).toString)
p.execute()
}
//关闭连接对象
override def close(errorOrNull: Throwable): Unit = {
conn.close()
}
}
写到这里关于实时流的处理就已经结束了,如果小伙伴们还想进行数据可视化,我可以推荐一款贼6的国产可视化工具–echarts。使用非常简单,官网上的api和教程实例什么的也给的非常详细。
相关echarts方案的实例传送门。
https://blog.youkuaiyun.com/Apache_Jerry/article/details/86549429