多线程提高spark streaming数据写入到数据库

多线程提高spark streaming数据写入到数据库

需求

集群环境资源有限,需要跑多个spark streaming任务,每个任务必须占据1核,cpu利用率很低,需要对数据进行实时统计更新到数据库mysql给业务实时展示,数据聚合程度较低每批数据对数据库交互过多,正常提交submit提交使用一个核只能单线程操作数据库,数据高峰会出现延迟现象,如何不增加资源情况提高效率?

Spark Streaming foreachRDD以及foreachPartition 操作数据库连接写入数据

当资源富裕,正常使用foreachPartition实现数据遍历入库代码如下

dstream.foreachRDD { rdd =>
  rdd.foreachPartition { partitionOfRecords =>
    val connection = createNewConnection()
    partitionOfRecords.foreach(record => connection.send(record))
    connection.close()
  }
}

这里我们使用了springboot对数据库操作的插件,foreachRDD是在driver端执行的,而foreach是在worker端执行的引用的类要在foreachPartition 里面实例化。我们知道我们在提交代码的时候,提交这个动作是在driver端执行的。springboot的类要用注解的方式实例化,使用foreachPartition会报错无法序列化,所以这里我们没有用foreachPartition,例如下面代码

val stateDstream = words_course.reduceByKey(_ + _)
   stateDstream.foreachRDD(rdd => {
     val list = rdd.collect
     for (r <- list) {
       courseStudyMincCount.studyCourse(r._1,r._2)
     }
   })

但是上述代码方式只能是单线程的,效率很低,这里我们跳出spark-Streaming自己实现多线程数据库交互

foreachRDD内自定义多线程

代码如下

    stateDstream.foreachRDD(rdd => {
      val list = rdd.collect
      //300是每批线程数据量,根据数据总量动态设定总线程数
      val num = (list.length/300)+1
      var count = 0
      var list_num = 0
      val array: Array[util.ArrayList[Map[String, Int]]] = new Array[util.ArrayList[Map[String, Int]]](num)
      array(list_num) = new util.ArrayList[Map[String, Int]]
      val threadPool: ExecutorService = Executors.newFixedThreadPool(num)
      val fList: Array[FutureTask[String]] = new Array[FutureTask[String]](num)
      //切分数据到不同的list
      for (r <- list) {
        count = count + 1
        val map = Map(r._1 -> r._2)
        array(list_num).add(map)
        if (count % 300 == 0) {
          list_num = list_num+1
          array(list_num) = new util.ArrayList[Map[String, Int]]
        }
      }
      //将不同list的数据扔到不同线程去处理
      for(i <- 0 to num-1) {
        val f = new FutureTask[String](new Callable[String] {
          override def call(): String = {
            val bizLog = array(i)
            for(s <- bizLog){
              for ((key , value) <- s){
                courseStudyMincCount.studyCourse(key, value,i)
              }
            }
            return "work finished at " + Thread.currentThread().getId()
          }
        })
        fList(i) = f
        threadPool.execute(f)
      }
      for(i <- 0 to num-1) {
        println(fList(i).get())
      }
      threadPool.shutdown()
    })

注意点
1.最后需要executors.shutdown()。
如果是executors.shutdownNow()会发生未执行完的task强制关闭线程。
如果使用executors.awaitTermination()则会发生阻塞,不是我们想要的结果。
如果没有这个shutdowm操作,程序会正常执行,但是长时间会产生大量无用的线程池,因为每次foreachRDD都会创建一个线程池。

2.可不可以将创建线程池放到foreachRDD外面?
不可以,这个关系到对于scala闭包到理解,经测试,第一次或者前几次batch是正常的,后面的batch无线程可用。

3.线程池executor崩溃了就会导致数据丢失
原则上是这样的,但是正常的代码一般不会发生executor崩溃。至少我在使用的时候没遇到过。

### 解决方案 当使用 Spark Streaming 读取 HDFS 数据流时遇到无响应的问题,可能的原因有多种。以下是常见的原因及其对应的解决方案: #### 1. **HDFS 路径错误** 如果指定的 HDFS 路径不存在或者路径不正确,则 `textFileStream` 不会抛出异常,而是进入等待状态[^1]。 - 确认 HDFS 路径是否存在并具有正确的权限。 - 使用以下命令验证路径: ```bash hdfs dfs -ls /path/to/directory ``` #### 2. **文件写入方式不符合要求** Spark Streaming 的 `textFileStream` 方法仅支持新增加的文件,并且这些文件必须是完全写入完成后再移动到目标目录下[^5]。如果文件正在被写入过程中就被放入监控目录,可能会导致数据丢失或无响应。 - 修改文件写入逻辑,确保文件完全写入完成后才移至监控目录。 - 推荐使用 Flume 或其他工具来管理日志文件的收集和传输过程[^3]。 #### 3. **时间间隔设置过短** 如果批处理的时间间隔(batch interval)设置得过短,而实际的数据到达频率较低,可能导致长时间未检测到新文件从而表现为“无响应”。 - 增大批次间隔参数 `Seconds(n)` 的值以匹配数据流入速度。 - 示例调整代码如下: ```scala val context: StreamingContext = new StreamingContext(conf, Seconds(30)) ``` #### 4. **网络连接问题** 由于分布式环境中的节点间通信依赖于稳定的网络条件,任何网络延迟或中断都可能影响 Spark Streaming 对 HDFS 文件的操作效率甚至造成失败。 - 检查集群内部各机器之间的连通性和带宽状况; - 查看是否有防火墙阻止必要的端口通讯; #### 5. **资源不足引发性能瓶颈** 当分配给 Spark 应用程序的内存或其他硬件资源配置不足以支撑当前规模的任务执行计划时,也可能表现出卡顿现象。 - 提升 Executor 和 Driver 所需最小 RAM 数量以及 Core 数目; - 合理规划 Partition 分区数目以便更好地利用多线程优势加快 I/O 密集型操作的速度。 ```scala val conf: SparkConf = new SparkConf() .setAppName("test") .setMaster("local[2]") .set("spark.executor.memory", "2g") // 设置executor内存大小 .set("spark.driver.memory", "1g") // 设置driver内存大小 ``` --- ### 实现改进后的完整示例代码 下面提供了一个经过优化后能够有效应对上述潜在风险因素版本的应用实例: ```scala import org.apache.spark.SparkConf import org.apache.spark.streaming.{Seconds, StreamingContext} object ImprovedStreamingApp { def main(args: Array[String]): Unit = { // 初始化配置项 val conf: SparkConf = new SparkConf() .setAppName("ImprovedStreamingApp") .setMaster("local[*]") // 生产环境中应改为yarn模式 .set("spark.executor.memory", "4g") .set("spark.driver.memory", "2g") // 创建Streaming Context对象 并定义每批次时间为60秒 val ssc: StreamingContext = new StreamingContext(conf, Seconds(60)) try{ // 定义输入源为HDFS上的特定目录 val linesDStream = ssc.textFileStream("/user/data/input/") // 进行简单的词频统计作为业务逻辑演示部分 val words = linesDStream.flatMap(_.split(" ")) val pairs = words.map(word => (word, 1)) val wordCounts = pairs.reduceByKey(_ + _) // 输出结果到控制台查看效果 wordCounts.print() // 开始接收事件流直到手动停止为止 ssc.start() ssc.awaitTermination() }catch{ case e : Exception => println(s"Error occurred during processing:${e.getMessage}") } } } ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值