Flink源码解析——ForwardedFields转发字段

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34842671/article/details/80774068

Flink中ForwardedFields这一概念为转发字段,所谓转发字段,字面理解就是某个字段不经过处理直接存储到另一位置,事实大致也是如此。ForwardedFields可以分为两类,一类是以map算子为代表的转发字段,另一类是join算子的转发字段,这两类算子的主要区别就是输入的DataSet个数。

1.单输入算子转发字段
filtermapflatMap等算子的转发字段是通过withForwardedFields函数实现的,该函数具体实现如下:

  def withForwardedFields(forwardedFields: String*) = {
    javaSet match {
      case op: SingleInputUdfOperator[_, _, _] => op.withForwardedFields(forwardedFields: _*)
      case _ =>
        throw new UnsupportedOperationException("Cannot specify forwarded fields for Operator " +
          javaSet.toString + ".")
    }
    this
  }

可以看出,该函数通过判断输入的DataSet是否是SingleInputUdfOperator的实例来进行下一步操作,filtermapflatMap等算子都继承了SingleInputUdfOperator

  def main(args: Array[String]): Unit = {
    val env = ExecutionEnvironment.getExecutionEnvironment
    val ds = env.fromElements(Point("a", 10.0), Point("b", 20.0), Point("a", 30.0))
    ds.map(t => (t.x, t.y))
      .map{x => (x._1, x._2, 1L)}.withForwardedFields("_1; _2")
      .reduce{(p1, p2) => (p1._1, p1._2 + p2._2, p1._3 + p2._3)}.withForwardedFields("_1")
      .map{x => new Point(x._1, x._2 / x._3)}.withForwardedFields("_1->x")
      .map(t => (1, t)).withForwardedFields("*->_2")
      .map(p => ("test", p._1)).withForwardedFields("_1->_2")
  }
  case class Point(var x: String = "", var y: Double = 0)

上述代码给出四种不同的定义转发字段的示例:
(1)withForwardedFields("_n")
要求该算子输入数据的第n个字段与输出的第n个字段相匹配,若有多个类似匹配,可用分号分隔。
(2)withForwardedFields("_n1->_n2")
要求该算子输入数据的第n1个字段与输出的第n2个字段相匹配。
(3)withForwardedFields("*->_n")
要求该算子输入数据的所有字段与输出的第n个字段相匹配。
(4)withForwardedFields("_n->x")
要求该算子的输出类型是某个class,并且x是该class中的某个属性,且输入数据的第n个字段与输出类型中的属性x相匹配。

定义ForwardedFields的原则为:
*在同一位置转发的字段由其位置指定。
*指定的位置必须对输入和输出数据类型有效并且相同。
2.双输入算子转发字段
由于join算子是两个DataSet作为输入,源码实现需要针对不同的输入数据集进行转发,因此有两个函数可以对双输入算子进行字段转发操作。

  //forwarding for the first dataset
  def withForwardedFieldsFirst(forwardedFields: String*) = {
    javaSet match {
      case op: TwoInputUdfOperator[_, _, _, _] => op.withForwardedFieldsFirst(forwardedFields: _*)
      case _ =>
        throw new UnsupportedOperationException("Cannot specify forwarded fields for Operator " +
          javaSet.toString + ".")
    }
    this
  }
  //forwarding for the second dataset
  def withForwardedFieldsSecond(forwardedFields: String*) = {
    javaSet match {
      case op: TwoInputUdfOperator[_, _, _, _] => op.withForwardedFieldsSecond(forwardedFields: _*)
      case _ =>
        throw new UnsupportedOperationException("Cannot specify forwarded fields for Operator " +
          javaSet.toString + ".")
    }
    this
  }

函数withForwardedFieldsFirst代表着将输入的第一个数据集(也就是join前的数据集)进行转发操作,函数withForwardedFieldsSecond代表着将输入的第二个数据集(也就是join后的数据集)进行转发操作。只有join算子使用该函数进行字段转发,定义规则与单输入算子转发一致。下面给出一个示例代码:

  def main(args: Array[String]): Unit = {
    val env = ExecutionEnvironment.getExecutionEnvironment
    val ds1 = env.fromElements((1, "a"), (1, "b"), (2, "c"), (3, "d"))
    val ds2 = env.fromElements((1, "just"), (2, "have"), (3, "a"), (4, "try"))
    val result = ds1.join(ds2).where(0).equalTo(0) {
      (p1, p2) => (p1._1, p1._2 + " " + p2._2)
    }.withForwardedFieldsFirst("_1")
  }

如果某一个算子使用的是创建RichMapFunction富函数的方式对数据集进行操作,则可以通过添加@ForwardedFields(Array("*->_2"))注释的方式定义转发字段。
需要注意的是,转发字段是可选择的,它只是对Flink任务的一种优化方式。如果正确使用了该注释,它能够帮助Flink的优化器生成更加高效的执行Plan,如果使用错误,可能会导致无效的Plan并且得到错误的计算结果,转发字段不一定需要但是定义了的转发字段一定要保证正确。因此,如果对转发字段的定义不熟或者不关注执行效率,可以不使用转发字段,对程序本身的运行结果没有影响。

猜你喜欢

转载自blog.csdn.net/qq_34842671/article/details/80774068