Spark中DStream 输出
输出操作指定了对流数据经转化操作得到的数据所要执行的操作(例如把结果推入外部数据库或输出到屏幕上)。与 RDD 中的惰性求值类似,如果一个 DStream 及其派生出的 DStream 都没有被执行输出操作,那么这些 DStream 就都不会被求值。如果StreamingContext 中没有设定输出操作,整个 context 就都不会启动。
输出操作如下:
➢ print():在运行流程序的驱动结点上打印 DStream 中每一批次数据的最开始 10 个元素。这用于开发和调试。在 Python API 中,同样的操作叫 print()。
➢ saveAsTextFiles(prefix, [suffix]):以 text 文件形式存储这个 DStream 的内容。每一批次的存储文件名基于参数中的 prefix 和 suffix。”prefix-Time_IN_MS[.suffix]”。
➢ saveAsObjectFiles(prefix, [suffix]):以 Java 对象序列化的方式将 Stream 中的数据保存为SequenceFiles . 每一批次的存储文件名基于参数中的为"prefix-TIME_IN_MS[.suffix]". Python中目前不可用。
➢ saveAsHadoopFiles(prefix, [suffix]):将 Stream 中的数据保存为 Hadoop files. 每一批次的存储文件名基于参数中的为"prefix-TIME_IN_MS[.suffix]"。Python API 中目前不可用。
➢ foreachRDD(func):这是最通用的输出操作,即将函数 func 用于产生于 stream 的每一个RDD。其中参数传入的函数 func 应该实现将每一个 RDD 中数据推送到外部系统,如将RDD 存入文件或者通过网络将其写入数据库。
通用的输出操作 foreachRDD(),它用来对 DStream 中的 RDD 运行任意计算。这和 transform()有些类似,都可以让我们访问任意 RDD。在 foreachRDD()中,可以重用我们在 Spark 中实现的所有行动操作。比如,常见的用例之一是把数据写到诸如 MySQL 的外部数据库中。
注意:
1) 连接不能写在 driver 层面(序列化)
2) 如果写在 foreach 则每个 RDD 中的每一条数据都创建,得不偿失;
3) 增加 foreachPartition,在分区创建(获取)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | package com.ljpbd.bigdata.spark.streaming import com.ljpbd.bigdata.spark.Util.JdbcUtil import org.apache.kafka.clients.consumer.{ConsumerConfig, ConsumerRecord} import org.apache.spark.SparkConf import org.apache.spark.rdd.RDD import org.apache.spark.streaming.dstream.{DStream, InputDStream} import org.apache.spark.streaming.kafka010.{ConsumerStrategies, KafkaUtils, LocationStrategies} import org.apache.spark.streaming.{Seconds, StreamingContext} import java.sql.{Connection, PreparedStatement, ResultSet} import java.text.SimpleDateFormat import java.util.Date import scala.collection.mutable.ListBuffer object SparkStreaming11_Req1BlockList { def main(args: Array[String]): Unit = { //1.创建 SparkConf val sparkConf: SparkConf = new SparkConf().setAppName( "ReceiverWordCount" ).setMaster( "local[*]" ) //2.创建 StreamingContext val ssc = new StreamingContext(sparkConf, Seconds( 3 )) //3.定义 Kafka 参数 val kafkaPara: Map[String, Object] = Map[String, Object]( ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG -> "linux1:9092,linux2:9092,linux3:9092" , ConsumerConfig.GROUP_ID_CONFIG -> "atguigu" , "key.deserializer" -> "org.apache.kafka.common.serialization.StringDeserializer" , "value.deserializer" -> "org.apache.kafka.common.serialization.StringDeserializer" ) //4.读取 Kafka 数据创建 DStream val kafkaDStream: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String, String](ssc, LocationStrategies.PreferConsistent, ConsumerStrategies.Subscribe[String, String](Set( "atguigu" ), kafkaPara)) //5.将每条消息的 KV 取出 val adClickData: DStream[AdClickData] = kafkaDStream.map( kafkaData => { val data: String = kafkaData.value() val datas: Array[String] = data.split( " " ) AdClickData(datas( 0 ), datas( 1 ), datas( 2 ), datas( 3 ), datas( 4 )) } ) //周期性获取黑名单数据 //判断点击用户是否在黑名单中 //如果用户不在黑名单中,那么进行统计数量(每个采集周期) val ds: DStream[((String, String, String), Int)] = adClickData.transform( rdd => { //通过jdbc周期性获取黑名单数据 val blackList = ListBuffer[String]() val connection: Connection = JdbcUtil.getConnection val pstat: PreparedStatement = connection.prepareStatement( "select userid from black_list" ) val rs: ResultSet = pstat.executeQuery() while (rs.next()) { blackList.append(rs.getString( 1 )) } rs.close() pstat.close() connection.close() val filterRdd: RDD[AdClickData] = rdd.filter( data => { //判断点击用户是不是在黑名单中 !blackList.contains(data.user) } ) //如果用户不在黑名单中,那么进行统计数量(每个采集周期) filterRdd.map( data => { val sdf = new SimpleDateFormat( "yyyy-MM-dd" ) val day = sdf.format( new Date(data.ts)) val user = data.user val ad = data.ad ((day, user, ad), 1 ) } ).reduceByKey(_ + _) } ) ds.foreachRDD( rdd => { //rdd.foreach会每一条数据创建连接 /* foreach是rdd的算子,算子之外的代码是在driver端执行,算子之内的代码是在executor执行 ,可以将对象从driver传输到executor,这样就会涉及到闭包操作,需要将数据序列化 但是数据库连接对象是不能序列化的 val conn: Connection = JdbcUtil.getConnection rdd提供了一个算子可以有效提供效率,foreachPartition 可以一个分区创建一个连接对象,这样就可以大幅度减少连接对象的创建 */ /* rdd.foreachPartition( iter=>{ val conn: Connection = JdbcUtil.getConnection conn.close() } )*/ rdd.foreach { case ((day, user, ad), count) => { println(s "${day} ${user} ${ad} ${count}" ) if (count >= 30 ) { //如果统计数量超过点击域值,那么将用户拉入到黑名单中 val conn: Connection = JdbcUtil.getConnection var sql = "" " |insert into black_list(userid) values(?) |on DUPLICATE KEY |UPDATE userid = ? | "" ".stripMargin JdbcUtil.executeUpdate(conn, sql, Array(user, user)) conn.close() } else { //如果没有超过域值,那么需要将当天的广告点击数量进行更新, val conn: Connection = JdbcUtil.getConnection val sql = "" " |select * from user_ad_count where dt=? and userid=? and adid=? | "" ".stripMargin //查询统计表数据 var flag = JdbcUtil.isExist(conn, sql, Array(day, user, ad)) //如果存在数据,则更新 if (flag) { val sql1 = "" " |update user_ad_count |set count=count+? |where dt=? and userid=? and adid=? | "" ".stripMargin JdbcUtil.executeUpdate(conn, sql1, Array(count, day, user, ad)) //判断更新后的点击数量是否超过域值,如果超过,那么将用户拉入到黑名单中 val sql2 = "" " |select * from user_ad_count |where dt=? and userid=? and adid=? and count>= 30 | "" ".stripMargin var flag1 = JdbcUtil.isExist(conn, sql2, Array(day, user, ad)) if (flag1) { val sql3 = "" " |insert into black_list(userid) values(?) |on DUPLICATE KEY |UPDATE userid = ? | "" ".stripMargin JdbcUtil.executeUpdate(conn, sql3, Array(user, user)) } } else { //如果不存在数据,那么新增 val sql4 = "" " |insert into user_ad_count(dt,userid,adid,count) values(?,?,?,?) | "" ".stripMargin JdbcUtil.executeUpdate(conn, sql4, Array(day, user, ad, count)) } conn.close() } } } } ) //7.开启任务 ssc.start() ssc.awaitTermination() } //广告点击数据 case class AdClickData(ts: String, area: String, city: String, user: String, ad: String) } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通