Spark Streaming结合redis实现state的功能

前言

  • 在前面的文章中有介绍SparkStreaming中的updateStateByKey算子,具体可见文章:Spark Streaming中状态算子的使用,但是使用updateStateByKey算子会存在一个问题,就是必须使用checkpoint,同时造成小文件扎堆的情况
  • 而在这篇文章中Spark Streaming中foreachRDD算子使用详解所提到的写入mysql的最佳实践中,细看也不难发现个问题,即每次写入到MySQL中的数据每次都是新增的,一旦数据更新后,那么从统计结果层面上来看是不对的,因此我们需要对其进行改进,实现类似于upsert的操作

结合上面这两点问题,在本篇文章中将使用SparkStreaming + redis来实现状态算子的功能,并同时保证数据库中数据的准确性

实现思路&代码

对于wordcount这种统计维度来说,redis的使用关键点在于如何选择合适的数据类型,可以使用hash的数据类型,并借助于hincrby的语法去实现对历史状态的累加,每次将新统计出的值去和数据库中存储的值进行累加

核心代码为RedisUtils和StreamingRedisApp,具体如下:

object RedisUtils {

  val poolConfig = new JedisPoolConfig
  poolConfig.setMaxTotal(2000)
  poolConfig.setMaxIdle(1000)
  poolConfig.setTestOnBorrow(true) //将连接用完放回池中

  // 默认端口即6379
  private val pool = new JedisPool(poolConfig, "localhost")

  def getJedis = pool.getResource

  def main(args: Array[String]): Unit = {
    val jedis = getJedis
    println(jedis)
  }

}
object StreamingRedisApp {

  def main(args: Array[String]): Unit = {
    val ssc = ContextUtils.getStreamingContext(this.getClass.getSimpleName, 5)

    val lines = ssc.socketTextStream("localhost", 9999)
    val result = lines.flatMap(_.split(",")).map((_,1)).reduceByKey(_+_)

    // 使用redis来实现state
    result.foreachRDD(rdd => {
      rdd.foreachPartition(partition => {
        val jedis = RedisUtils.getJedis

        partition.foreach(pair => {
          // hincrBy为field加上指定的值,通过redis的特性即实现了累加的功能
          jedis.hincrBy("redis_wc", pair._1, pair._2)
        })

        jedis.close()
      })
    })

    ssc.start()
    ssc.awaitTermination()

  }

}
### Spark Streaming 广告实时统计实验教程 #### 实验目标 使用 Spark Streaming 实现广告点击流量的实时统计,包括以下功能: 1. 实现实时动态黑名单机制:将每天广告点击超过 100 次的用户拉黑。 2. 统计每天各省各城市的广告点击流量。 3. 实时统计每天每个省份 top3 热门广告。 4. 实时统计最近 1 小时内各广告的点击趋势(每分钟的点击量)。 5. 将统计结果写入 Redis 或 MySQL 数据库。 --- #### 技术栈与环境准备 - **编程语言**:Scala 或 Python - **框架**:Spark Streaming, Kafka - **数据库**:Redis, MySQL - **日志收集工具**:Flume 或 Logstash(可选) - **消息队列**:Kafka 确保已安装并配置好 Spark 和 Kafka 集群。如果使用 MySQL 或 Redis,请提前创建对应的表或键值存储结构。 --- #### 实验步骤 #### 1. 数据生成 为了模拟广告点击数据,可以使用 Scala 或 Python 编写脚本生成模拟数据,并通过 Kafka 发送。每条数据格式如下[^4]: ```text 时间戳 省份 城市 用户ID 广告ID ``` 以下是 Scala 数据生成代码示例: ```scala import scala.collection.mutable.ListBuffer import java.util.Date import java.text.SimpleDateFormat def generateData(): ListBuffer[String] = { val sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") val data = ListBuffer[String]() for (_ <- 1 to 10) { val timestamp = sdf.format(new Date()) val province = Seq("Beijing", "Shanghai", "Guangdong").scala.util.Random.shuffle.head val city = Seq("Chaoyang", "Huangpu", "Tianhe").scala.util.Random.shuffle.head val userId = scala.util.Random.nextInt(1000) val adId = scala.util.Random.nextInt(100) data += s"$timestamp $province $city $userId $adId" } data } ``` 将生成的数据发送到 Kafka 主题中。 --- #### 2. 实现实时动态黑名单机制 通过 Spark Streaming 监听 Kafka 主题,统计每个用户对每个广告的点击次数。当某个用户点击次数超过 100 次时,将其加入黑名单并存储到 MySQL 中[^4]。 以下是实现代码示例: ```scala val ssc = new StreamingContext(sc, Seconds(10)) val kafkaParams = Map[String, Object]( "bootstrap.servers" -> "localhost:9092", "key.deserializer" -> classOf[StringDeserializer], "value.deserializer" -> classOf[StringDeserializer], "group.id" -> "test", "auto.offset.reset" -> "earliest", "enable.auto.commit" -> (false: java.lang.Boolean) ) val topics = Array("ad-clicks") val stream = KafkaUtils.createDirectStream[String, String]( ssc, PreferConsistent, Subscribe[String, String](topics, kafkaParams) ) // 解析数据 val parsedStream = stream.map(record => { val fields = record.value().split(" ") (fields(3), fields(4)) // (userId, adId) }) // 更新用户点击次数状态 val userAdClickCounts = parsedStream.map { case (userId, adId) => ((userId, adId), 1) } .updateStateByKey((values: Seq[Int], state: Option[Int]) => { val currentCount = values.sum + state.getOrElse(0) Some(currentCount) }) // 过滤黑名单用户 userAdClickCounts.foreachRDD(rdd => { rdd.filter { case ((userId, adId), count) => count > 100 } .foreachPartition(partition => { partition.foreach { case ((userId, adId), count) => // 将黑名单用户写入 MySQL writeToMySQL(userId, adId) } }) }) ``` --- #### 3. 统计每天各省各城市的广告点击流量 通过 `reduceByKeyAndWindow` 方法,计算窗口内的广告点击流量[^3]。 代码示例: ```scala val provinceCityAdClicks = parsedStream.map { case (userId, adId) => ((fields(1), fields(2), adId), 1) } val windowedClicks = provinceCityAdClicks.reduceByKeyAndWindow( (v1: Int, v2: Int) => v1 + v2, Seconds(60 * 60), // 窗口大小为 1 小时 Seconds(10) // 滑动间隔为 10 秒 ) windowedClicks.foreachRDD(rdd => { rdd.saveAsTextFile("hdfs://path/to/save/clicks") }) ``` --- #### 4. 实时统计每天每个省份 top3 热门广告 使用 `groupByKey` 和 `sortBy` 方法,计算每个省份的 top3 广告[^3]。 代码示例: ```scala val provinceAdClicks = parsedStream.map { case (userId, adId) => ((fields(1), adId), 1) } val topAdsPerProvince = provinceAdClicks.reduceByKeyAndWindow( (v1: Int, v2: Int) => v1 + v2, Seconds(60 * 60), Seconds(10) ).map { case ((province, adId), count) => (province, (adId, count)) } .groupByKey() .mapValues(_.toList.sortBy(-_._2).take(3)) topAdsPerProvince.foreachRDD(rdd => { rdd.saveAsTextFile("hdfs://path/to/save/top_ads") }) ``` --- #### 5. 实时统计最近 1 小时内各广告的点击趋势 使用滑动窗口统计每分钟的广告点击量。 代码示例: ```scala val adClicksPerMinute = parsedStream.map { case (userId, adId) => (adId, 1) } val trendClicks = adClicksPerMinute.reduceByKeyAndWindow( (v1: Int, v2: Int) => v1 + v2, Seconds(60), // 窗口大小为 1 分钟 Seconds(10) // 滑动间隔为 10 秒 ) trendClicks.foreachRDD(rdd => { rdd.saveAsTextFile("hdfs://path/to/save/trends") }) ``` --- #### 6. 写入结果到 Redis 或 MySQL 将统计结果写入 Redis 或 MySQL,便于后续查询和展示[^1]。 Redis 示例: ```scala import redis.clients.jedis.Jedis def writeToRedis(adId: String, count: Int): Unit = { val jedis = new Jedis("localhost", 6379) jedis.set(s"ad_click:$adId", count.toString) jedis.close() } ``` MySQL 示例: ```scala import java.sql.DriverManager def writeToMySQL(userId: String, adId: String): Unit = { Class.forName("com.mysql.cj.jdbc.Driver") val connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db", "user", "password") val statement = connection.prepareStatement("INSERT INTO blacklist (user_id, ad_id) VALUES (?, ?)") statement.setString(1, userId) statement.setString(2, adId) statement.executeUpdate() connection.close() } ``` --- ### 性能优化 - 调整 Spark Streaming 的批量处理时间(`batch interval`),以平衡延迟和吞吐量。 - 使用 `updateStateByKey` 时,启用检查点机制以防止状态丢失。 - 对于高并发场景,可以增加 Kafka 分区数量和 Spark 执行器数量。 --- ###
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值