美文网首页
一种解决图表数据过多的接口方案

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

作者: 艺超51iwowo | 来源:发表于2021-04-08 06:13 被阅读0次

    当需要进行前端数据展示的时候,图形和表格是非常有用的利器。但是,最近在工作中遇到了一个问题,那就是在某些情况下,服务端需要返回大量的数据。另外,由于工作限制,没有直接使用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类型,但是计算类型存在平均值的情况,会有类型转换,不过对于前端绘制数据,没有实际影响。

    相关文章

      网友评论

          本文标题:一种解决图表数据过多的接口方案

          本文链接:https://www.haomeiwen.com/subject/ykhmkltx.html