Spark流编程指引(六)-----------------------DStreams上的输出操作

DStreams上的输出操作

通过输出操作,我们可以将DStream的数据存放到数据库或者文件系统这样的外部系统中。尽管实际上,输出操作允许经过转换后的数据被外部系统消耗,它们也会触发执行所有DStreams上的转换操作(与RDDs上的action操作类似)。目前,定义了如下的输出操作:

Output Operation Meaning
print() Prints first ten elements of every batch of data in a DStream on the driver node running the streaming application. This is useful for development and debugging.
Python API This is calledpprint() in the Python API.
saveAsTextFiles(prefix, [suffix]) Save this DStream's contents as a text files. The file name at each batch interval is generated based onprefix and suffix: "prefix-TIME_IN_MS[.suffix]".
saveAsObjectFiles(prefix, [suffix]) Save this DStream's contents as a SequenceFile of serialized Java objects. The file name at each batch interval is generated based onprefix and suffix: "prefix-TIME_IN_MS[.suffix]".
Python API This is not available in the Python API.
saveAsHadoopFiles(prefix, [suffix]) Save this DStream's contents as a Hadoop file. The file name at each batch interval is generated based onprefix and suffix: "prefix-TIME_IN_MS[.suffix]".
Python API This is not available in the Python API.
foreachRDD(func) The most generic output operator that applies a function, func, to each RDD generated from the stream. This function should push the data in each RDD to a external system, like saving the RDD to files, or writing it over the network to a database. Note that the function func is executed in the driver process running the streaming application, and will usually have RDD actions in it that will force the computation of the streaming RDDs.


dstream.foreachRDD

一个允许我们将数据发送到外部系统的强大原语。所以,明白如何高效和正确地使用这个原语很重要。下面是一些需要避免的常见错误:


通常将数据写到外部系统需要创建一个连接对象(比如一个连接到远端系统的TCP连接),通过它将数据发送到远端系统。为了达到上述目的,开发人员可能很自然地在Spark驱动程序里创建一个连接对象,然后在一个Spark执行体里使用这个连接对象在RDDs里保存数据。如下所示:

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

这样做是错误的,因为需要将连接对象序列化并从Spark驱动程序里发送到执行体。这样的连接对象很少能够跨机器传送。这样的错误表现为序列化错误(连接对象没有正常被序列化),初始化错误(连接对象需要在执行体被初始化)。正确的做法是在执行体里创建连接对象。


然而,这又会导致另外一个常见的错误,为每条记录创建一个连接对象。如下所示:

dstream.foreachRDD { rdd =>
  rdd.foreach { record =>
    val connection = createNewConnection()
    connection.send(record)
    connection.close()
  }
}
通常情况下,创建一个连接对象有时间和资源开销。因此,为每个记录创建和销毁一个连接对象会导致不必要的高开销,并显著地降低整个系统的吞吐量。

一个较好地解决方案是使用 rdd.foreachPartition-----创建一个连接对象,并通过它发送一个RDD分区中的所有记录。如下所示:
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上的外部操作是惰性的,和RDDs上的action操作是类似的。特别地,DStreams外部操作中的RDD actions操作会强制触发处理接收到的数据。所以,如果你的应用程序没有任何外部操作,或者执行 dstream.foreachRDD()这样的外部操作而在内部却没有任何RDD action操作,就会导致什么也不执行。系统将会简单地接收数据并丢弃它。

2.默认情况下,输出操作会一个接一个地执行,执行顺序就是在应用程序里定义的顺序。


你可能感兴趣的:(hadoop,scala,spark,spark,大数据,分布式)