使用GenericInputFormat生成数据时的竞争条件

时间:2017-03-19 13:04:00

标签: scala apache-flink

我正在尝试Flink并编写以下示例程序:

object IFJob {

  @SerialVersionUID(1L)
  final class StringInputFormat extends GenericInputFormat[String] {

    val N = 100
    var i = 0L

    override def reachedEnd(): Boolean = this.synchronized {
      i >= N
    }

    override def nextRecord(ot: String): String = this.synchronized {
      i += 1
      return (i % 2) + ""
    }
  }

  def main(args: Array[String]) {

    val env = ExecutionEnvironment.getExecutionEnvironment
    val text: DataSet[String] = env.createInput(new StringInputFormat())

    val map = text.map {
      (_, 1)
    }
    //        map.print()
    val by = map.groupBy(0)
    val aggregate: AggregateDataSet[(String, Int)] = by.aggregate(Aggregations.SUM, 1)
    aggregate.print()
  }
}

我创建一次StringInputFormat并且并行读取(默认并行度= 8)。 当我运行上述程序时,结果在执行之间有所不同,即它们不是确定性的。结果重复1-8次。

例如,我得到以下结果:

// first run 
(0,150) 
(1,150)

// second run 
(0,50) 
(1,50)

// third run 
(0,200) 
(1,200)

预期结果将是

(0,400) 
(1,400)

因为StringInputFormat应该生成8次50" 0"和" 1"记录。

我甚至在输入格式中添加了同步,但它没有帮助。

Flink计算模型中缺少什么?

1 个答案:

答案 0 :(得分:1)

您观察到的行为是Flink如何将工作分配给InputFormat的结果。其工作原理如下:

  1. 在主(JobManager)上,调用createInputSplits()方法,返回InputSplit数组。 InputSplit是要读取(或生成)的一大块数据。 GenericInputFormat为每个并行任务实例创建一个InputSplit。在您的情况下,它会创建8个InputSplit个对象,每个InputSplit应生成50个"1"和50个"0"个记录。
  2. DataSourceTask的并行实例是在worker(TaskManagers)上启动的。每个DataSourceTask都有自己的InputFormat
  3. 实例
  4. 一旦开始,DataSourceTask就会向主人请求InputSplit,并使用open()调用InputFormat的{​​{1}}方法。当InputSplit完成处理InputFormat后,InputSplit会向主人请求新内容。
  5. 在您的情况下,每个DataSourceTask都会得到快速处理。因此,DataSourceTasks之间存在争用,它们的InputFormats请求InputSplits,而一些InputFormats处理多个InputSplit。由于InputSplit未重置其内部状态(即设置InputFormat),因此在打开新i = 0时,它只会为其处理的第一个InputSplit生成数据。 / p>

    您可以通过将此方法添加到InputSplit

    来解决此问题
    StringInputFormat