1、背景
最近开发的数据大屏项目,使用echarts图表,通过拖拽的方式完成大屏的布局。
每一个图表编写一个vue文件,例如柱状图(barChart.vue):
<template>
<div>
<v-chart ref="barchart" :option="options" autoresize />
</div>
</template>
<script>
export default {
components: {},
props: {
value: Object,
index: Number
},
data() {
return {
options: {
grid: {},
legend: {
textStyle: {
color: "#fff"
}
},
xAxis: {
type: "category",
data: [],
axisLabel: {
show: true,
textStyle: {
color: "#fff"
}
}
},
yAxis: {
type: "value",
data: [],
axisLabel: {
show: true,
textStyle: {
color: "#fff"
}
}
},
series: [
{
data: [],
type: "bar",
barGap: "0%",
itemStyle: {
borderRadius: null
}
}
]
},
optionsStyle: {}, // 样式
optionsData: {}, // 数据
optionsSetup: {},
};
},
mounted() {
this.editorOptions();
},
methods: {
// 修改图标options属性
editorOptions() {
this.setOptionsData();
},
// 数据解析
setOptionsData() {
const data = this.queryEchartsData(val);
data.then(res => {
this.renderingFn(optionsSetup, res);
});
},
}
};
</script>
queryEchartsData的方法体如下:
queryEchartsData(params,_this) {
return new Promise(async (resolve) => {
const {code, data} = getData(params);
if (code != 200) return
resolve(data)
})
}
折线图等其他图表同理,都是使用queryEchartsData来获取后端接口返回的数据,queryEchartsData写在queryData.js中,然后通过全局混入的方式使用。当我一个大屏拖拽了太多的组件时,渲染时,每一个组件都同时通过queryEchartsData来获取数据,就会导致阻塞,最后一个接口返回数据的时间甚至可能达到9秒左右
从图上就可以看到光阻塞时长就达到了6秒,我的天啊!!!
2、思路
我实在受不了这种情况,于是就想办法解决喽,首先要弄清楚阻塞的原因到底在后端还是在前端。
- 后端使用ehcache来缓存接口数据
if (cacheHelper.exist(key)) {
data = cacheHelper.stringGet(key);
} else {
data = reportService.getChartData(dto)
cacheHelper.stringSetExpire(datakey, JSON.toJSONString(data), 3600);
}
这样写完以后发现情况并没有任何变化,所以阻塞的情况是前端同时一次性发起太多请求导致的。
- 前端使用vuex来缓存接口数据
在Vuex的store中定义一个状态,用于存储请求返回的数据。
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
echartsData: {},
},
mutations: {
setEchartsData(state, data) {
state.echartsData[data.key] = data.data;
},
},
});
然后在执行queryEchartsData的时候,先判断store中是否已存在,如果存在就直接从store获取,否则就调用接口然后往store中写入。
但是在实际测试过程中,发现每一个vue文件进入到queryEchartsData后,在执行到getData的时候就不在往下走,而是跳转到下一个vue文件进入queryEchartsData方法,当所有vue文件都进入过queryEchartsData方法后,才会再继续执行getData后面的内容,无语,这个也行不通啊…
3、解决
总归是要解决,最后通过不停的喂ChatGPT,终于是找到了解决办法,使用请求队列和限流,实现一个请求队列,控制并发请求的数量,避免同时发起过多的请求。可以设置最大并发数,并根据需求进行请求的限流,以平衡请求和服务器资源之间的关系。代码如下:
// your-queue-file.js
/**
* 创建请求队列
* @param {number} maxConcurrentRequests 最大并发请求数量
* @returns {object} 请求队列对象
*/
export function createRequestQueue(maxConcurrentRequests) {
const queue = []; // 存储请求的队列
let runningCount = 0; // 当前正在运行的请求数量
/**
* 处理下一个请求
*/
async function processNextRequest() {
if (runningCount >= maxConcurrentRequests || queue.length === 0) return;
runningCount++;
const request = queue.shift(); // 获取队列中的下一个请求
try {
const result = await request(); // 执行请求
// 处理请求结果
// ...
} catch (error) {
// 处理请求错误
// ...
} finally {
runningCount--;
processNextRequest(); // 处理下一个请求
}
}
return {
/**
* 添加请求到队列
* @param {function} request 请求函数,返回 Promise
* @returns {Promise} 请求的 Promise 对象
*/
add(request) {
return new Promise((resolve, reject) => {
queue.push(async () => {
try {
const result = await request(); // 执行请求
resolve(result); // 请求成功,返回结果
} catch (error) {
reject(error); // 请求失败,返回错误信息
}
});
processNextRequest(); // 添加请求后,处理下一个请求
});
}
};
}
// 在 queryform.js 中
queryEchartsData(params, _this) {
return new Promise(async (resolve) => {
const result = await addToRequestQueue(() => getData(params));
const { code, data } = result;
if (code !== 200) return;
resolve(data );
});
}
// 在 main.js 中
import { createRequestQueue } from './your-queue-file'; // 导入请求队列的实现
const requestQueue = createRequestQueue(3); // 创建最大并发请求数为3的请求队列
Vue.mixin({
methods: {
addToRequestQueue(request) {
return requestQueue.add(request);
}
}
});
测试结果如下:
除了最后一个接口时间在1.84秒,其他的都基本上在400多毫秒,而阻塞时间也控制在0.16ms左右
至此也算是解决了一桩心病啊,后面就可以在继续优化下去了!