尝试将变换器函数附加到Google Dataflow管道时出现NullPointerException

时间:2016-02-04 00:57:33

标签: scala serialization google-cloud-dataflow dataflow

在浏览Google Cloud Dataflow WordCount Pipeline Example并创建Scala应用程序以在本地运行管道时,我遇到以下异常:

Exception in thread "main" java.lang.NullPointerException
    at com.google.cloud.dataflow.sdk.util.SerializableUtils.clone(SerializableUtils.java:89)
    at com.google.cloud.dataflow.sdk.transforms.ParDo$Bound.<init>(ParDo.java:700)
    at com.google.cloud.dataflow.sdk.transforms.ParDo$Unbound.of(ParDo.java:661)
    at com.google.cloud.dataflow.sdk.transforms.ParDo.of(ParDo.java:551)
    at apps.MiniDataFlowApp$.delayedEndpoint$apps$MiniDataFlowApp$1(MiniDataFlowApp.scala:32)
    at apps.MiniDataFlowApp$delayedInit$body.apply(MiniDataFlowApp.scala:17)
    at scala.Function0$class.apply$mcV$sp(Function0.scala:34)
    at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
    at scala.App$$anonfun$main$1.apply(App.scala:76)
    at scala.App$$anonfun$main$1.apply(App.scala:76)
    at scala.collection.immutable.List.foreach(List.scala:381)
    at scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:35)
    at scala.App$class.main(App.scala:76)
    at apps.MiniDataFlowApp$.main(MiniDataFlowApp.scala:17)
    at apps.MiniDataFlowApp.main(MiniDataFlowApp.scala)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)

其中MiniDataFlowApp.scala:32对应于以下创建管道的代码段中的.apply(ParDo.of(extractWords))

val p: Pipeline = Pipeline.create(options)
p.apply(TextIO.Read.from("some.input.txt"))
.apply(ParDo.of(extractWords))
.apply(Count.perElement[String]())
.apply(ParDo.of(formatOutput))
.apply(TextIO.Write.to("some.output.txt"))

extractWords按如下方式实施DoFn

val extractWords = new DoFn[String, String]() {
    override def processElement(c: DoFn[String, String]#ProcessContext) {
      c.element.split("[^a-zA-Z']+").filter(_.nonEmpty).map(_ => c.output(_))
    }
}

this StackOverflow question中描述的问题似乎相似。但是,我不认为我有一个不可序列化的类,就像导致该问题的问题一样。至少,我不明白为什么我可能会遇到序列化问题,如果这就是问题。

感谢您抽出宝贵时间阅读我的问题和见解!

1 个答案:

答案 0 :(得分:1)

这是一个初始化顺序问题。在Scala中,类主体中的val(对象是相应类的单例实例)按声明顺序初始化。

这意味着当p初始化时,extractWords和formatOutput没有,并且为null。 (我已经看到了OP代码的其余部分;那些成员是val之后的val。)

至少有3种解决方案:

1)更改val的顺序,使依赖项(extractWords,formatOutput)成为第一。

2)make extractWords和formatOutput lazy vals。这将使它们在被访问时被初始化,并保证它们只被初始化一次。

3)make extractWords和formatOutput defs。这将使他们在每次访问时重新计算,这可能会也可能不会。