Flink Scala-比较方法违反了其一般合同

时间:2018-10-12 23:14:43

标签: java scala flink-streaming

我正在用Flink编写一个项目,该项目涉及在批处理数据上流式传输一组查询点,并执行完整的顺序扫描以找到最近的邻居。对单个Float值的简单排序操作应该违反常规合同错误。主要方法定义为:

object StreamingDeCP{
  var points: Vector[Point] = _

  def main(args: Array[String]): Unit = {
    val queryPointsVec: Vector[Point] = ... // Read from file
    val pointsVec: Vector[Point] = ...      // Read from file

    val streamEnv: StreamExecutionEnvironment = 
                   StreamExecutionEnvironment.getExecutionEnvironment
    val queryPoints = streamEnv.fromCollection(queryPointsVec)

    points = pointsVec
    queryPoints.map(new StreamingSequentialScan)

    streamEnv.execute("StreamingDeCP")
  }

  final class StreamingSequentialScan 
                    extends MapFunction[Point, (Point, Vector[Point])] {

    def map(queryPoint: Point): (Point, Vector[Point]) = {
      val nn = points
                .map{ _.eucDist(queryPoint) }
                .sorted

      (queryPoint, nn)
    }
  }
}

Point类和伴随对象是:

case class Point(pointID: Long,
                 descriptor: Vector[Float]) extends Serializable {
  var distance: Float = Float.MaxValue

  def eucDist(that: Point): Point = {
    // Simple arithmetic to calculate and set the distance variable
  }
}

object Point{
  implicit def orderByDistance[A <: Point]: Ordering[A] =
    Ordering.by(_.distance)
}

这里有一些关于我尝试过的事情的注释,以便查明原因:

  • 断言所有distance值都在Float.MaxValue和Float.MinValue之间,并且不存在负零值
  • 断言在同一排序操作中没有重复的distance变量(我的用例允许这样做,但我想我会检查以防万一)
  • 将Float转换为Integer值,然后对这些值进行排序
  • Point上添加了显式排序,而不是使用隐式
  • 根据唯一的pointID而不是distance进行排序,这虽然有效,但对于该问题的上下文没有用。

我还注意到,执行相同的代码并不总是能够可靠地重现该错误。我正在以一种完全确定性的方式阅读Vector[Points],因此导致此行为的唯一可能原因必须是Flink调度程序或排序方法中的某些状态计算。

同一主题上的其他帖子似乎在自定义比较器中涉及一种遗漏的情况,但这应该是对单个Float值的简单排序操作,因此我不知道是什么原因引起的。

1 个答案:

答案 0 :(得分:1)

我对Flink并不熟悉,但是我没有任何理由假设它会以顺序单线程的方式执行每个embarrassingly parallel MapFunction任务。

由于您的Point包含var,并且这些varmap的{​​{1}}方法中发生了突变,因此代码必须以“以并行方式MapFunction执行MapFunction时,“比较方法违反了其一般约定” 例外。

为避免在!= 1函数内部产生任何副作用,可以按如下所示修改代码:

  • map中删除所有var,将main设为不变的points
  • val中删除任何类型的var
  • 实现方法

    Point

    可以简单地计算到另一个点的距离(不改变任何东西)。

  • 使用def eucDist(other: Point): Double

    sortBy

或者,如果您希望避免在排序过程中多次重新计算欧几里得距离,请预先计算一次距离,然后再将距离扔掉:

val nn = points.sortBy(_.eucDist(queryPoint))