聚合元素(多個元素合併成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的實現決定的,後續會提到。