Flink自定义聚合AggregateFunction实现计算特定时间内某个字段的TP99

业务场景: 统计所有请求时间到返回结果时间的的TP99,TP50等等
我参考了flink官方文档自定义函数 虽然可以,但是处理数据的性能降低了
之后有参考flink实战-使用自定义聚合函数统计网站TP指标,才知道如果存储状态的accumulate太大,会显著形影响处理数据性能,
看下面不懂的可以先看上面两个链接,之后写TP50只需要改几个地方就可以,或者直接写成通用的,到底TP几就带着参数
对于下面为什么Double和BigDecimal之间的转换需要经过先转换字符串,那是因为我不想丢失精度

import org.apache.flink.table.functions.AggregateFunction;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;


/**
 * Weighted Average user-defined aggregate function.
 *
 */
public class Tp99 extends AggregateFunction<Double, Tp99Accum> {
    /**
     * 初始化,每次Aggregate前都会调用一次这个
     */
    @Override
    public Tp99Accum createAccumulator() {
        return new Tp99Accum();
    }

    /**
     * 每次Aggregate结束后的结果调用一次这个
     */
    @Override
    public Double getValue(Tp99Accum acc) {
        if (acc.map.size() < 1) {
            return 0.0;
        }
        //排序,BigDecimal比较大小就用本身的hashCode就可以,不用重写比较大小的算法
        Map<BigDecimal, Integer> map = new TreeMap<>(acc.map);
        //求此次聚合到底有多少条数据
        int sum = map.values().stream().reduce(0, Integer::sum);
        //向上取整,计算TP99是第几个数
        int tp99Index = (int) Math.ceil((float) (99 * sum) / 100);
        //找出数据返回
        for (Map.Entry<BigDecimal, Integer> entry : map.entrySet()) {
            tp99Index = tp99Index - entry.getValue();
            if (tp99Index <= 0) {
                return Double.valueOf(entry.getKey().toString());
            }
        }
        return 0.0;
    }

    /**
     * accumulate提供了如何根据输入的数据更新  存放状态的accumulator
     *	不建议在此处理复杂的逻辑,否则数据量大时会拖延处理速度,像排序可以在getValue排
     * @param acc
     * @param params
     */
    public void accumulate(Tp99Accum acc, Double params) {
        if (params == null) {
            return;
        }
        String key = Double.toString(params);
        BigDecimal bigDecimal = new BigDecimal(key);
        if (acc.map.containsKey(bigDecimal)) {
            acc.map.put(bigDecimal, acc.map.get(bigDecimal) + 1);
        } else {
            acc.map.put(bigDecimal, 1);
        }
    }


    /**
     * retract方法是accumulate方法的逆操作,
     *
     * @param acc
     * @param params
     */
    public void retract(Tp99Accum acc, Double params) {
        BigDecimal bigDecimal = new BigDecimal(Double.toString(params));
        if (acc.map.containsKey(bigDecimal)) {
            if (acc.map.get(bigDecimal) >= 2) {
                acc.map.put(bigDecimal, acc.map.get(bigDecimal) - 1);
            } else {
                acc.map.remove(bigDecimal);
            }
        }

    }

    /**
     * 发生的场景:由于实时计算Flink版具有out of order的特性,后输入的数据有可能位于2个原本分开的session中间,这样就把2个session合为1个session。此时,需要使用merge方法把多个accumulator合为1个accumulator。
     *
     * @param acc
     * @param it
     */
    public void merge(Tp99Accum acc, Iterable<Tp99Accum> it) {
        for (Tp99Accum tp99Accum : it) {
            for (Map.Entry<BigDecimal, Integer> entry : tp99Accum.map.entrySet()) {
                if (acc.map.containsKey(entry.getKey())) {
                    acc.map.put(entry.getKey(), acc.map.get(entry.getKey()) + entry.getValue());
                } else {
                    acc.map.put(entry.getKey(), entry.getValue());
                }
                ;

            }
        }
    }

    /**
     * 重置
     *
     * @param acc
     */
    public void resetAccumulator(Tp99Accum acc) {
        acc.map = new HashMap<>();
    }
}

下面是存放聚合状态的临时accumulate

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;


public class Tp99Accum {
    public Map<BigDecimal, Integer> map;

    public Tp99Accum() {
        map = new HashMap<>();
    }
}

你可能感兴趣的:(#,Flink)