不变性是不可避免的"或"应该"自定义累加器?

时间:2016-03-23 21:01:58

标签: java apache-spark accumulator

我想创建自定义累加器,使用它时我感觉不安全,因为我现在只能在本地测试它们。

我的问题是:

不变性是否必须"或"应该"什么时候创建累加器?

虽然我现在无法找到链接/引用,但我已经读过,累加器只允许使用不可变对象。 但是,在spark的api(1.6)中,AccumulableParam和AccumulatorParam的addInPlace方法的描述如下: "将两个累计值合并在一起。允许修改并返回第一个效率值(以避免分配对象)。"

哪一个是正确的?如果允许可变对象如何使用它们来安全地创建累加器?

让我们说,我有一个带有一个字段的可变类,并让该字段成为整数数组。当我们有一个可变类时,如何覆盖addInPlace方法?

我应该写(Option1):

public MyClass addInPlace(MyClass c1, MyClass c2){
c1.update(c2); //Where int array of c1 is updated(let's say we add two arrays) and c1 itself is returned.
return c1;
}

或者我应该写(Option2):

public MyClass addInPlace(MyClass c1, MyClass c2){
return update2(c1,c2); //Where a new MyClass object is returned with an array(created by adding arrays of c1 and c2)
}

Option2似乎更安全但需要额外分配。但是,API的上述引用表示允许修改以避免分配。

另外,如果我有一个对象数组(让我们说MyClass2)而不是整数数组,我应该克隆对象,还是使用对象本身。 让我们说我想为MyClass2的PriorityQueue创建一个累加器(也许我应该为这个问题输入另一个条目?)。

我将非常感谢accumulators / Spark上的任何答案和高级参考/文档,特别是在java中。

编辑:

我感谢zero323的答案。

我希望我能找到让我困惑的链接,但现在情况更清楚了。 但是,我还有2个问题。

1)我遇到了以下累加器实现,以跟踪浏览器类型在日志文件中看到的次数。您可以看到(https://brosinski.com/post/extending-spark-accumulators/)的详细信息。

以下是实施:

public class MapAccumulator implements AccumulatorParam<Map<String, Long>>, Serializable {

@Override
public Map<String, Long> addAccumulator(Map<String, Long> t1, Map<String, Long> t2) {
    return mergeMap(t1, t2);
}

@Override
public Map<String, Long> addInPlace(Map<String, Long> r1, Map<String, Long> r2) {
    return mergeMap(r1, r2);

}

@Override
public Map<String, Long> zero(final Map<String, Long> initialValue) {
    return new HashMap<>();
}

private Map<String, Long> mergeMap( Map<String, Long> map1, Map<String, Long> map2) {
    Map<String, Long> result = new HashMap<>(map1);
    map2.forEach((k, v) -> result.merge(k, v, (a, b) -> a + b));
    return result;
}

}

我的问题是:

为什么我们没有

map2.forEach((k, v) -> map1.merge(k, v, (a, b) -> a + b));

另外,让我们说我想要一个

Map<Integer, ArrayList<MyClass>> or ArrayList<ArrayList<MyClass>>

我可以拥有(Option1):

public ArrayList<ArrayList<MyClass>> addInPlace(ArrayList<ArrayList<MyClass>> a1, ArrayList<ArrayList<MyClass>> a2) {
//For now, assume that a1 and a2 have the same size
for(int i=0;i<a2.size();i++){
    a1.get(i).addAll(a2.get(i))
}
return a1;
}

或者我应该写(Option2):

public ArrayList<ArrayList<MyClass>> addInPlace(ArrayList<ArrayList<MyClass>> a1, ArrayList<ArrayList<MyClass>> a2) {
//For now, assume that a1 and a2 have the same size
ArrayList<ArrayList<MyClass>> result= new ArrayList<ArrayList<MyClass>>();
for(int i=0;i<a1.size();i++){
    result.add(new ArrayList<MyClass>());
    result.get(i).addAll(a1.get(i));
    result.get(i).addAll(a2.get(i));
}
return result;
}

在累加器安全方面,两个选项之间有区别吗?

2)通过说累加器不是线程安全的,你的意思是rdd元素可以多次更新累加器吗?或者你的意思是在进程中使用的对象可以通过另一个线程从代码中的其他地方更改?

或者仅在将累加器运送到驱动程序时出现问题,如链接零点共享(https://github.com/apache/spark/blob/master/core/src/main/scala/org/apache/spark/Accumulable.scala#L43)中所述:

&#34;如果[[Accumulable]]是内部的。内部[[可累计]]将通过心跳报告给驾驶员。对于内部[[Accumulable]] s,R必须是线程安全的,以便正确报告它们。&#34;

我为长篇文章道歉,但我希望它对社区也有帮助。

1 个答案:

答案 0 :(得分:1)

创建自定义累加器时是否需要不变性?不它不是。您已经发现AccumulableParam.addAccumulatorAccumulableParam.addInPlace都明确允许修改第一个参数。如果你深入了解,你会发现这个场景实际上是在使用了以下参数的AccumulatorSuite中进行测试的:

new AccumulableParam[mutable.Set[A], A] {
  def addInPlace(t1: mutable.Set[A], t2: mutable.Set[A]) : mutable.Set[A] = {
    t1 ++= t2
    t1
  }
  def addAccumulator(t1: mutable.Set[A], t2: A) : mutable.Set[A] = {
    t1 += t2
    t1
  }
  def zero(t: mutable.Set[A]) : mutable.Set[A] = {
    new mutable.HashSet[A]()
  }
}

直观地说,由于每个任务都有自己的累加器并以顺序方式在分区上运行,因此在可变性成为问题时应该不会出现这种情况。

尽管如此,as stated somewhere else累积量不是线程安全的。所以你应该忘记在分区级别上将累加器与并行处理相结合。