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)
}

 

posted @   会飞的猪仔  阅读(57)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示