[Structured Streaming]:结构化流式传输到Redshift接收器

时间:2018-01-19 04:37:33

标签: apache-spark-sql amazon-redshift spark-structured-streaming

是否可以将Kafka Streaming源支持的Dataframe写入AWS Redshift,我们过去使用spark-redshift写入Redshift,但我认为它不适用于DataFrame##writeStream。考虑到Redshift的工作方式,使用带有ForeachWriter的JDBC连接器编写也可能不是一个好主意。

我从Yelp blog遇到的一种可能的方法是将文件写入S3,然后使用具有S3对象路径的Manifest文件调用Redshift COPY,在结构化流式传输的情况下,如何我控制我写入S3的文件?并且在将5个文件写入S3后,还有一个单独的触发器来创建清单文件。

还感谢任何其他可能的解决方案。提前谢谢。

2 个答案:

答案 0 :(得分:2)

有一种方法可以在结构化流中使用spark-redshift,但是你必须在自己的fork中实现一些额外的类。首先,您需要一个应该实现org.apache.spark.sql.execution.streaming.Sink接口的RedshiftSink:

private[redshift] class RedshiftSink(
    sqlContext: SQLContext,
    parameters: MergedParameters,
    redshiftWriter: RedshiftWriter) extends Sink {

  private val log = LoggerFactory.getLogger(getClass)

  @volatile private var latestBatchId = -1L

  override def toString(): String = "RedshiftSink"

  override def addBatch(batchId: Long, data: DataFrame): Unit = {
    if (batchId <= latestBatchId) {
      log.info(s"Skipping already committed batch $batchId")
    } else {
      val mode = if (parameters.overwrite) SaveMode.Overwrite else SaveMode.Append
      redshiftWriter.saveToRedshift(sqlContext, data, mode, parameters)
      latestBatchId = batchId
    }
  }
}

然后com.databricks.spark.redshift.DefaultSource应该通过org.apache.spark.sql.sources.StreamSinkProvider

的实施进行扩展
  /**
   * Creates a Sink instance
   */
  override def createSink(
    sqlContext: SQLContext,
    parameters: Map[String, String],
    partitionColumns: Seq[String],
    outputMode: OutputMode): Sink = {
      new RedshiftSink(sqlContext, Parameters.mergeParameters(parameters), new RedshiftWriter(jdbcWrapper, s3ClientFactory))
  }

现在你应该可以在结构化流媒体中使用它了:

dataset.writeStream()
        .trigger(Trigger.ProcessingTime(10, TimeUnit.SECONDS))
        .format("com.databricks.spark.redshift")
        .outputMode(OutputMode.Append())
        .queryName("redshift-stream")
        .start()

更新

要解决将报表指标更新为StreamExecution RedshiftWriter.unloadData()的问题,必须更改为使用data.queryExecution.toRdd.mapPartitions而不是data.rdd.mapPartitions,因为data.rdd会创建一个StreamExecution不可见的新计划(使用现有计划收集指标)。它还需要将转换函数更改为:

val conversionFunctions: Array[(InternalRow, Int) => Any] = data.schema.fields.map { field =>
  field.dataType match {
    case DateType =>
      val dateFormat = Conversions.createRedshiftDateFormat()
      (row: InternalRow, ordinal: Int) => {
        if (row.isNullAt(ordinal)) null else dateFormat.format(
          DateTimeUtils.toJavaDate(row.getInt(ordinal)))
      }
    case TimestampType =>
      val timestampFormat = Conversions.createRedshiftTimestampFormat()
      (row: InternalRow, ordinal: Int) => {
        if (row.isNullAt(ordinal)) null else timestampFormat.format(
          DateTimeUtils.toJavaTimestamp(row.getLong(ordinal)))
      }
    case StringType =>
      (row: InternalRow, ordinal: Int) => {
        if (row.isNullAt(ordinal)) null else row.getString(ordinal)
      }
    case dt: DataType =>
      (row: InternalRow, ordinal: Int) => {
        if (row.isNullAt(ordinal)) null else row.get(ordinal, dt)
      }
  }
}

答案 1 :(得分:0)

Spark能够非常有效地将正常的数据帧加载到Redshift,但我还没有在Spark中使用过流。

如果您可以连续将流输出写入标准df,那么在指定的时间间隔内,您可以将该df加载到Redshift并清空它。

另一种选择是将流发送到Kinesis并使用Kinesis Firehose将其加载到Redshift。看起来过多,无法将另一个流层添加到堆栈中。