前言
最新在写structured streaming的程序,发现kafka输入和输出的数据不成比例,正常如果你的group里只有一个消费者,那么应该输入和输出是相等的才对。
但是我的生产者和消费者2边的数据不一样,如下图:
我的2个 topic,test19和test20可以看到2个程序的输出数据基本是输入的10倍,这肯定有问题,因此我要做几个实验,来验证到底是哪里除了问题,争取解决此问题。
实验1:
实现过程:从kafka获取输入后直接输出到另外一个topic里:
实验一代码:
数据生产与消费不写了,
基本流程是:
java生产者->kafka_topic34->spark消费者->kafka_topic35->java消费者
Spark代码:Scala
import org.apache.spark.sql.SparkSession
object KafkaOutputDataSizeTest {
def main(args: Array[String]): Unit = {
//解决找不到HADOOP环境问题
System.setProperty("hadoop.home.dir", "C://hadoop-3.1.1")
//初始化spark
val spark = SparkSession
.builder
.appName("KafkaOutputDataSizeTest")
.master("local[*]")
.getOrCreate()
//这行是必须的,好像用于类型转换
val df = spark
.readStream
.format("kafka")
.option("kafka.bootstrap.servers", "cdpcluster-1.futuremove.cn:9092,cdpcluster-2.futuremove.cn:9092,cdpcluster-3.futuremove.cn:9092")
.option("subscribe", "test34")
.load()
// var df2 =df.selectExpr("CAST(key AS STRING)", "CAST(value AS STRING)")
df.writeStream
.format("kafka")
.option("checkpointLocation", "test34_point")
.option("kafka.bootstrap.servers", "cdpcluster-1.futuremove.cn:9092,cdpcluster-2.futuremove.cn:9092,cdpcluster-3.futuremove.cn:9092")
.option("topic", "test34")
.start().awaitTermination()
}
}
实现结果:
输入和输出量级一样
实验2:
实现过程:做一个简单的filter,将数据1分2
val df = spark
.readStream
.format("kafka")
.option("kafka.bootstrap.servers", "cdpcluster-1:9092,cdpcluster-2:9092,cdpcluster-3:9092")
.option("subscribe", "test36")
.load()
var df2 = df.selectExpr("CAST(value AS STRING)").as[(String)]
var df3 = df2.map(x => {
// println(x)
val obj = JSON.parseObject(x)
KafkaTestJsonBean(obj.getString("id"), obj.getString("message"))
})
// df2.map(x => {
// val obj = JSON.parseObject(x)
//
// })
// df.show()
df3.writeStream.foreachBatch { (batchDF: Dataset[KafkaTestJsonBean], batchId: Long) =>
//如果没有对应报文就不去执行插入数据库操作否则会频繁的建立hbase数据库连接
val df_1 = batchDF.filter(x => {
x.key.toInt % 2 == 0
})
val df_2 = batchDF.filter(x => {
x.key.toInt % 2 == 1
})
df_1.write
.format("kafka")
.option("checkpointLocation", "test37_point")
.option("kafka.bootstrap.servers", "cdpcluster-1:9092,cdpcluster-2:9092,cdpcluster-3:9092")
.option("topic", "test37").save()
df_2.write
.format("kafka")
.option("checkpointLocation", "test37_point")
.option("kafka.bootstrap.servers", "cdpcluster-1:9092,cdpcluster-2:9092,cdpcluster-3:9092")
.option("topic", "test37").save()
// batchDF.unpersist()
// batchDF2.unpersist()
}.start().awaitTermination()
实现结果:
输入不变的情况下,输出是输入的2倍,再加一个filter数据就是3倍。
产生原因:
Spark里action和transform操作,执行action的时候会把这个action的一串transform都计算一遍,也就是一个filter相当于入去了输出的时候相当于读取了一遍kafka,第2个filter结果集保存的时候又读取了一遍kafka.所以相当于读取了2遍kafka.
最终结论:
可以看一下Spark里action和transform操作的说明,官方也给了处理办法。就是缓存,persist()函数.
以下代码来自官网Spark2.4.5,
加入batchDF.persist()这行之后,输入与输出终于一致了。
但是别忘了释放缓存batchDF.unpersist(),我做了个测试,一致不调用unpersist最后我内存溢出了。总之参考官方的写法就可以了。
streamingDF.writeStream.foreachBatch { (batchDF: DataFrame, batchId: Long) =>
batchDF.persist()
batchDF.write.format(...).save(...) // location 1
batchDF.write.format(...).save(...) // location 2
batchDF.unpersist()
}
官网地址:
http://spark.apache.org/docs/2.4.5/structured-streaming-programming-guide.html