一种解决图表数据过多的接口方案

当需要进行前端数据展示的时候,图形和表格是非常有用的利器。但是,最近在工作中遇到了一个问题,那就是在某些情况下,服务端需要返回大量的数据。另外,由于工作限制,没有直接使用echarts和highcharts,但是该方案不仅仅是前端页面绘制的问题。

数据量大的问题
超过了网关的限制

在微服务体系下,前端一般会直接同网关接口交互,然后再由网关将请求转发到真正的服务端。所以,网关需要对传入的内容(比如body和header等)进行解析。为了解析效率,通常需要对body大小进行限制(如2M),超过之后,就会拦截。

针对这种情况,可以通过分页或者加时间条件,缩写数据查询的范围。在无法缩写返回结果大小的情况下,会有2种解决方案。

  1. 将该类接口,作为类似数据下载的网关接口类型。

    很明显,数据下载类接口,不会受到body大小的限制,因为这类下载接口,数据大小很容易超过2M。

    但是,因为数据量大,网关无法再对服务端返回内容进行解析,进而完成返回值的参数映射工作。

    如果服务端的接口返回响应和网关通用的响应不一致(比如,服务端叫data,网关叫Data),这种情况下,就

    需要前端单独处理,不便于后续的维护。

  2. 采用异步下载的方案。

    收到查询请求后,将数据获取之后,放置到类似阿里云对象存储里面,再由前端去对象存储中获取。

    该方案会对交互有很大改动,毕竟是从同步修改为异步,改造成本比较大。

    此外,我们的场景中,由于查询范围的多样性,每次都需要将数据存储到对象存储中,数据几乎没有被复用,对象存储有些浪费。

前端组件的渲染问题

当数据量特别多的时候,此时对于前端来说,需要在同一个页面上,进行绘制。所以,业务使用的组件需要对数据进行过滤渲染,但是具体的渲染效果却不理想,比如1s的数据,颗粒度是ms,那对于服务端返回的1000个点,业务组件会按照某种规律丢弃点,造成整体的曲线不够圆滑。

解决方案

由服务端根据数据特点,结合前端实际的渲染能力和网关的限制,设置好要切割的数据范围个数,按照计算类型(比如求和、求平均值)对每一个范围的数据点进行聚合操作。这样可以保证,无论查询条件如何,都会返回统一大小的数据点集合。

示例代码

 /**
     * 对原始数据进行auto scale操作
     * @param originData 原始数据
     * @param startTime 开始时间
     * @param endTime 截止时间
     * @param splitSize 分隔的大小
     * @param aggType 计算类型 sum或者Agg
     * @return scale 后的结果
     * 此时结果会统一将值转为String类型
     */
    public static List<CommonTimeData<Double>> autoScale(List<CommonTimeData<Long>> originData,
                                                        Long startTime, Long endTime, int splitSize, CommonTimeDataAggType aggType) {

        if (CollectionUtils.isEmpty(originData)) {
            return Lists.newArrayList();
        }

        CommonTimeData<Long> firstData = originData.get(0);
        CommonTimeData<Long> lastData = originData.get(originData.size() - 1);

        long minStartTime = firstData.getTimestamp();
        long maxEndTime = lastData.getTimestamp();

        if (null != startTime) {
            minStartTime = startTime;
        }

        if (null != endTime) {
            maxEndTime = endTime;
        }

        // 如果时间戳范围小于要切割的大小,则不进行切割
        if (maxEndTime - minStartTime < splitSize) {
            return originData.stream().map(value -> {
                CommonTimeData<Double> commonTimeData = new CommonTimeData<>();
                commonTimeData.setTimestamp(value.getTimestamp());
                commonTimeData.setValue(Double.parseDouble(String.valueOf(value)));
                return commonTimeData;
            }).collect(Collectors.toList());
        }

        long delta = (maxEndTime - minStartTime) / splitSize;

        List<CommonTimeData<Double>> autoScale = Lists.newArrayListWithCapacity(originData.size());
        int startIndex = 0;
        // 总共会有splitSize个数据点
        for (int i = 0; i < splitSize; i++) {
            double aggValue = 0L;
            long currentDataTimeStamp = minStartTime + delta * i;
            long count = 0;
            // 对该范围内的点进行统计汇总
            for (int j = startIndex; j < originData.size(); j++) {
                CommonTimeData<Long> data = originData.get(j);
                if (data.getTimestamp() - currentDataTimeStamp <= delta) {
                    long value = data.getValue();
                    aggValue = aggValue + value;
                    count = count + 1;
                } else {
                    startIndex = j;
                    break;
                }
            }
            CommonTimeData<Double> newData = new CommonTimeData<>();
            newData.setTimestamp(currentDataTimeStamp);
            // 由于是浮点数,对返回数据进行2位小数的格式化处理
            DecimalFormat decimalFormat = new DecimalFormat("#.##");
            switch (aggType) {
                case SUM:
                    // 求和
                    String formatSumValue= decimalFormat.format(aggValue);
                    newData.setValue(Double.parseDouble(formatSumValue));
                    break;
                case AVG:
                    // 求平均值
                    double avgValue = 0;
                    if (count > 0) {
                        avgValue = aggValue / count;
                    }
                    String formatAvgValue= decimalFormat.format(avgValue);
                    newData.setValue(Double.parseDouble(formatAvgValue));
                    break;
                default:
                    break;
            }
            autoScale.add(newData);
        }
        return autoScale;

    }

存在的问题:

  1. 如果原数据为long类型,但是计算类型存在平均值的情况,会有类型转换,不过对于前端绘制数据,没有实际影响。

你可能感兴趣的:(java,java)