聚合元素(多个元素合并成1个)操作(类似于reduce操作)

如果要将数据集里的所有元素聚合成1个元素,在beam里称为combine操作。
假设现在我们有1个PCollection数据集 pInt
则我们以计算整数求和的方式,展示3种聚合方式:

用beam提供的sdk

PCollection<Integer> pSum = pInt.apply(Sum.integersGlobally());

用Combine.globally(SerializableFunction类)

PCollection<Integer> pSum = pInt.apply(Combine.globally(new IntergerSum());

IntegerSum定义如下:

//实现SerializableFunction<A,B>接口, A是管道里元素的迭代器类型, B是输出结果类型
public static class IntergerSum implements SerializableFunction<Iterable<Integer>, Integer> {
@Override
public Integer apply(Iterable<Integer> input) {
Integer sum = new Integer(0);
for (Integer item : input) {
sum += item;
}
return sum;
}
}

自定义Combine.CombineFn

这个方法较复杂,要实现4个接口,但是自由度也会高很多
可以用中间累加器做特殊的聚合操作,并最后再切换回需要的输出

PCollection<Integer> pSum = pInt.apply(Combine.globally(new IntSumFn());
/**
* 继承自Combine.CombineFn<A,B,C>
* A输入管道的元素, B中间累加器的类型, C输出结果类型
* 步骤:创建累加器、各机器管道元素合到累加器中、各管道累加器合并、处理最终结果
*/
class IntSumFn extends Combine.CombineFn<Integer, Integer, Integer> {
// 中间累加器可以自己定义

// 中间累加器初始化
@Override
public Integer createAccumulator(){ return 0;}

//单管道中的累加器操作
@Override
public Integer addInput(Integer accum, Integer input){
accum += input;
return accum;
}

//合并多个分布式机器的累加器方法
//最终返回1个累加器
@Override
public Integer mergeAccumulators(Iterable<Integer> accums){
Integer merged = createAccumulator();
for (Integer accum: accums){
merged += accum;
}
return merged;
}

//如何将累加器转化为你需要的输出结果
//这里可以对最后聚合的输出结果做特殊处理
@Override
public Integer extractOutput(Integer accum){
return accum;
}
}

利用CombineFn的中间累加器,可以灵活地实现各种聚合,我们换1个更有用的例子: 我希望将所有字符串元素合并成1个字符串,并转成自己需要的1个输出实体OutputEntity
这个需求中,如果要叠加字符串,则肯定不能直接让String相加,因为这比较消耗性能,正确的姿势是用StringBuilder做中间累加器。

class MergeStringToOutputEntity extends Combine.CombineFn<String, StringBuilder, OutputEntity> {

@Override
public StringBuilder createAccumulator() {
return new StringBuilder();
}

@Override
public StringBuilder addInput(StringBuilder mutableAccumulator, String input) {
return mutableAccumulator.append(input);
}

@Override
public StringBuilder mergeAccumulators(Iterable<StringBuilder> accumulators) {
StringBuilder mergeAccum = createAccumulator();
for(StringBuilder stringBuilder : accumulators) {
mergeAccum.append(stringBuilder);
}
return mergeAccum;
}

@Override
public OutputEntity extractOutput(StringBuilder accumulator) {
return new OutputEntity(accumulator);
}
}

不过也要注意一点,中间累加器切忌过大, 即使你控制中间累加器最多为10M,不会超出JVM内存,但也会出现合并过程及其缓慢的情况。这是beam的combine的实现决定的,后续会提到。