为DataFlow中的发布/订阅自定义时间戳和窗口(Apache Beam)

时间:2019-03-28 15:14:03

标签: java python google-cloud-dataflow apache-beam spotify-scio

我想在Apache Beam中使用流传输管道(并在Google DataFlow上运行)实现以下方案:

  1. 从发布/订阅中读取消息(JSON字符串)
  2. 反序列化JSON
  3. 使用自定义字段(例如timeStamp)作为处理元素的时间戳值
  4. 应用60 seconds的固定窗口
  5. 从元素中提取密钥并按密钥分组
  6. <<执行进一步处理>>

我尝试使用Java(Scala)和Python来解决此问题,但是没有一种解决方案有效。

  1. Python解决方案
# p is beam.Pipeline()
_ = (p | beam.io.ReadFromPubSub(subscription="my_sub")
        | beam.Map(add_timestamping)
        | beam.WindowInto(window.FixedWindows(60))
        | beam.Map(lambda elem: elem) # exracting the key somehow, not relevant here
        | beam.GroupByKey()
        # (...)
        | beam.io.WriteToPubSub("output_topic")
        )
p.run()

add_timestamping按照documentation的功能:

def add_timestamping(elem):
    import json
    import apache_beam as beam
    msg = json.loads(elem)
    unix_timestamp = msg['timeStamp'] / 1000
    return beam.window.TimestampedValue(msg, unix_timestamp)

Python解决方案的输出

  1. 使用DirectRunner时,会发射窗口,并且取决于延迟,窗口本身或多或少是适当的。
  2. 使用DataFlowRunner时,所有消息将被跳过,并且在DataFlow UI中出现计数器: droppedDueToLateness

  1. Java / Scala解决方案 (我使用了 Scio ,但这也发生在Java的干净Beam SDK中)
sc.pubsubSubscription[String]("my_sub")
    .applyTransform(ParDo.of(new CustomTs()))
    .withFixedWindows(Duration.standardSeconds(60))
    .map(x => x) // exracting the key somehow, not relevant here
    .groupByKey
    // (...)
    .saveAsPubsub("output_topic")

根据documentation添加自定义时间戳记:

import io.circe.parser._
class CustomTs extends DoFn[String, String] {
  @ProcessElement
  def processElement(@Element element: String, out: DoFn.OutputReceiver[String]): Unit = {
    val json = parse(element).right.get
    val timestampMillis: Long = json.hcursor.downField("timeStamp").as[Long].getOrElse(0)
    out.outputWithTimestamp(element, new Instant(timestampMillis))
  }
}

Java / Scala解决方案的输出

Exception in thread "main" org.apache.beam.sdk.Pipeline$PipelineExecutionException: 
java.lang.IllegalArgumentException:
 Cannot output with timestamp 2019-03-02T00:51:39.124Z. 
 Output timestamps must be no earlier than the timestamp of the current input
 (2019-03-28T14:57:53.195Z) minus the allowed skew (0 milliseconds).

我在这里不能使用DoFn.getAllowedTimestampSkew,因为它已经过时了,我也不知道将发送什么范围的历史数据。


具有处理历史数据的能力对于我的项目至关重要(此数据将从某个商店发送到Pub / Sub)。管道必须同时处理当前数据和历史数据。

我的问题是: 如何使用自定义时间戳处理数据并能够在使用Beam API定义的窗口上运行?

1 个答案:

答案 0 :(得分:1)

如果能够将插入点处的时间戳提取到PubSub,则可以将用户指定的时间戳用作元数据。有关如何操作的信息记录在1.9 SDK下。

https://cloud.google.com/dataflow/model/pubsub-io#timestamps-and-record-ids

“您可以使用用户指定的时间戳来精确控制如何将从Cloud Pub / Sub中读取的元素分配给Dataflow管道中的窗口。”

不建议使用1.9,在2.11中,您将需要https://beam.apache.org/releases/javadoc/2.11.0/org/apache/beam/sdk/io/gcp/pubsub/PubsubIO.Read.html#withTimestampAttribute-java.lang.String-