SparkStreaming之foreachRDD

来源:互联网 发布:在线java程序编译器 编辑:程序博客网 时间:2024/04/28 12:30

原文链接: http://blog.csdn.net/legotime/article/details/51836039

参考链接:http://blog.csdn.net/erfucun/article/details/52312682


首先我们来对官网的描述了解一下。

DStream中的foreachRDD是一个非常强大函数,它允许你把数据发送给外部系统。因为输出操作实际上是允许外部系统消费转换后的数据,它们触发的实际操作是DStream转换。所以要掌握它,对它要有深入了解。下面有一些常用的错误需要理解。经常写数据到外部系统需要创建一个连接的object(eg:根据TCP协议连接到远程的服务器,我们连接外部数据库需要自己的句柄)和发送数据到远程的系统为此,开发者需要在Spark的driver创建一个object用于连接。

为了达到这个目的,开发人员可能不经意的在Spark驱动中创建一个连接对象,但是在Spark worker中 尝试调用这个连接对象保存记录到RDD中,如下

dstream.foreachRDD { rdd =>  val connection = createNewConnection()  // executed at the driver  rdd.foreach { record =>    connection.send(record) // executed at the worker  }}

这是不正确的,因为这需要先序列化连接对象,然后将它从driver发送到worker中。这样的连接对象在机器之间不能

传送。它可能表现为序列化错误(连接对象不可序列化)或者初始化错误(连接对象应该 在worker中初始化)等

等。正确的解决办法是在worker中创建连接对象。

然而,这会造成另外一个常见的错误-为每一个记录创建了一个连接对象。例如:
dstream.foreachRDD { rdd =>  rdd.foreach { record =>    val connection = createNewConnection()    connection.send(record)    connection.close()  }}

通常,创建一个连接对象有资源和时间的开支。因此,为每个记录创建和销毁连接对象会导致非常高的开支,明显

的减少系统的整体吞吐量。一个更好的解决办法是利用rdd.foreachPartition方法。 为RDD的partition创建一个连接对

象,用这个两件对象发送partition中的所有记录。

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

最后,可以通过在多个RDD或者批数据间重用连接对象做更进一步的优化。开发者可以保有一个静态的连接对象

池,重复使用池中的对象将多批次的RDD推送到外部系统,以进一步节省开支

dstream.foreachRDD { rdd =>  rdd.foreachPartition { partitionOfRecords =>    // ConnectionPool is a static, lazily initialized pool of connections    val connection = ConnectionPool.getConnection()    partitionOfRecords.foreach(record => connection.send(record))    ConnectionPool.returnConnection(connection)  // return to the pool for future reuse  }}

需要注意的是,池中的连接对象应该根据需要延迟创建,并且在空闲一段时间后自动超时。这样就获取了最有效的

方式发生数据到外部系统。

其它需要注意的地方:

(1)输出操作通过懒执行的方式操作DStreams,正如RDD action通过懒执行的方式操作RDD。具体地看,RDD 

actions和DStreams输出操作接收数据的处理。因此,如果你的应用程序没有任何输出操作或者 用于输出操作

dstream.foreachRDD(),但是没有任何RDD action操作在dstream.foreachRDD()里面,那么什么也不会执行。系统

仅仅会接收输入,然后丢弃它们。

(2)默认情况下,DStreams输出操作是分时执行的,它们按照应用程序的定义顺序按序执行。


实验1:把SparkStreaming的内部数据存入Mysql

(1)在mysql中创建一个表用于存放数据

  1. mysql> create database sparkStreaming;  
  2. Query OK, 1 row affected (0.01 sec)  
  3.    
  4. mysql> use sparkStreaming;  
  5. Database changed  
  6. mysql> show tables;  
  7. Empty set (0.01 sec)  
  8.    
  9. mysql> create table searchKeyWord(insert_time date,keyword varchar(30),search_count integer);  
  10. Query OK, 0 rows affected (0.05 sec)  

(2)用scala编写连接Mysql的连接池

  1. import java.sql.Connection  
  2. import java.sql.PreparedStatement  
  3. import java.sql.ResultSet  
  4.   
  5. import org.apache.commons.dbcp.BasicDataSource  
  6. import org.apache.log4j.Logger  
  7.   
  8. object scalaConnectPool {  
  9.   val  log = Logger.getLogger(scalaConnectPool.this.getClass)  
  10.   var ds:BasicDataSource = null  
  11.   def getDataSource={  
  12.     if(ds == null){  
  13.       ds = new BasicDataSource()  
  14.       ds.setUsername("root")  
  15.       ds.setPassword("iamhaoren")  
  16.       ds.setUrl("jdbc:mysql://localhost:3306/sparkStreaming")  
  17.       ds.setDriverClassName("com.mysql.jdbc.Driver")  
  18.       ds.setInitialSize(20)  
  19.       ds.setMaxActive(100)  
  20.       ds.setMinIdle(50)  
  21.       ds.setMaxIdle(100)  
  22.       ds.setMaxWait(1000)  
  23.       ds.setMinEvictableIdleTimeMillis(5*60*1000)  
  24.       ds.setTimeBetweenEvictionRunsMillis(10*60*1000)  
  25.       ds.setTestOnBorrow(true)  
  26.     }  
  27.     ds  
  28.   }  
  29.   
  30.   def getConnection : Connection= {  
  31.     var connect:Connection = null  
  32.     try {  
  33.       if(ds != null){  
  34.         connect = ds.getConnection  
  35.       }else{  
  36.         connect = getDataSource.getConnection  
  37.       }  
  38.     }  
  39.     connect  
  40.   }  
  41.   
  42.   def shutDownDataSource: Unit=if (ds !=null){ds.close()}  
  43.   
  44.   def closeConnection(rs:ResultSet,ps:PreparedStatement,connect:Connection): Unit ={  
  45.     if(rs != null){rs.close}  
  46.     if(ps != null){ps.close}  
  47.     if(connect != null){connect.close}  
  48.   }  
  49.   
  50. }  

(3)编写SparkStreaming程序

  1. import org.apache.spark.SparkConf  
  2. import org.apache.spark.streaming.{Seconds, StreamingContext}  
  3.   
  4. object dataToMySQL {  
  5.   def main(args: Array[String]) {  
  6.     val conf = new SparkConf().setAppName("use the foreachRDD write data to mysql").setMaster("local[2]")  
  7.     val ssc = new StreamingContext(conf,Seconds(10))  
  8.   
  9.     val streamData = ssc.socketTextStream("master",9999)  
  10.     val wordCount = streamData.map(line =>(line.split(",")(0),1)).reduceByKeyAndWindow(_+_,Seconds(60))  
  11.     val hottestWord = wordCount.transform(itemRDD => {  
  12.       val top3 = itemRDD.map(pair => (pair._2, pair._1))  
  13.         .sortByKey(false).map(pair => (pair._2, pair._1)).take(3)  
  14.       ssc.sparkContext.makeRDD(top3)  
  15.     })  
  16.   
  17.   
  18.     hottestWord.foreachRDD( rdd =>{  
  19.       rdd.foreachPartition(partitionOfRecords =>{  
  20.         val connect = scalaConnectPool.getConnection  
  21.         connect.setAutoCommit(false)  
  22.         val stmt = connect.createStatement()  
  23.         partitionOfRecords.foreach(record =>{  
  24.           stmt.addBatch("insert into searchKeyWord (insert_time,keyword,search_count) values (now(),'"+record._1+"','"+record._2+"')")  
  25.         })  
  26.         stmt.executeBatch()  
  27.         connect.commit()  
  28.       }  
  29.       )  
  30.     }  
  31.     )  
  32.   
  33.   
  34.   
  35.     ssc.start()  
  36.     ssc.awaitTermination()  
  37.     ssc.stop()  
  38.   }  
  39. }  
(4)编写一个socket端的数据模拟器

  1. import java.io.{PrintWriter}    
  2. import java.net.ServerSocket    
  3. import scala.io.Source    
  4.     
  5.     
  6. object streamingSimulation {    
  7.   def index(n: Int) = scala.util.Random.nextInt(n)    
  8.     
  9.   def main(args: Array[String]) {    
  10.     // 调用该模拟器需要三个参数,分为为文件路径、端口号和间隔时间(单位:毫秒)    
  11.     if (args.length != 3) {    
  12.       System.err.println("Usage: <filename> <port> <millisecond>")    
  13.       System.exit(1)    
  14.     }    
  15.     
  16.     // 获取指定文件总的行数    
  17.     val filename = args(0)    
  18.     val lines = Source.fromFile(filename).getLines.toList    
  19.     val filerow = lines.length    
  20.     
  21.     // 指定监听某端口,当外部程序请求时建立连接    
  22.     val listener = new ServerSocket(args(1).toInt)    
  23.     
  24.     while (true) {    
  25.       val socket = listener.accept()    
  26.       new Thread() {    
  27.         override def run = {    
  28.           println("Got client connected from: " + socket.getInetAddress)    
  29.           val out = new PrintWriter(socket.getOutputStream(), true)    
  30.           while (true) {    
  31.             Thread.sleep(args(2).toLong)    
  32.             // 当该端口接受请求时,随机获取某行数据发送给对方    
  33.             val content = lines(index(filerow))    
  34.             println("-------------------------------------------")    
  35.             println(s"Time: ${System.currentTimeMillis()}")    
  36.             println("-------------------------------------------")    
  37.             println(content)    
  38.             out.write(content + '\n')    
  39.             out.flush()    
  40.           }    
  41.           socket.close()    
  42.         }    
  43.       }.start()    
  44.     }    
  45.   }    
  46.     
  47. }    
实验数据为:

spark
Streaming
better
than
storm
you
need
it
yes
do
it

(5)实验启动

在客户端启动数据流模拟

对socket端的数据模拟器程序进行 jar文件的打包,并放入到集群目录中

启动程序如下:

  1. java -cp DataSimulation.jar streamingSimulation /root/application/upload/Information 9999 1000  
启动SparkStreaming程序

结果如下:



原创粉丝点击