前言
前段时间,我司的IoT平台的客户大量上新了一堆设备后,在使用过程中,出现了网络连接超时
,服务器错误
等问题,客户小题大做,抓着这个尾巴不放手,喷我司系统不健壮,要求我们杜绝API错误
。
由于IoT多是日志、设备、测试、Action、图表、通信类的功能,API请求&返回数据量大、时间长
。在后端添加索引的情况下,接口仍然达不到秒级,接口普遍都是1~3s
,部分接口甚至达到小10s。我司把服务器的硬盘和CPU都进行了一波升级,效果都不是很理想。
这个烫手的山芋,落在了前端的头上。我们被迫进行了一波系统升级优化
解决方案
我们结合这个需求,制定了以下几条标准:
- 不能入侵其他的功能
- 对系统的破坏尽可能的小
- 杜绝或者尽可能的减少弹框问题
- 保证数据的正确展示,对于错误要正确的暴露出来
根据以上几条标准,于是方案就自然的确定了:
API请求时间
拉长API的请求时间
,将超时时间由30s,更新为60s
const service = axios.create({
baseURL: '/xxx',
timeout: 60 * 1000 // 请求超时时间
})
重发机制
- API请求超时: 当新页面大量接口请求,某个接口请求时间过长,总时间
>60s
时,我们会对这个接口进行至多重发3次
,用180s的时间去处理这个接口,当请求成功后,关闭请求
重发的接口不能超过timeout时长,否则一直会重发失败。也可以自定义重发的时间
- 偶发的服务器异常: 当接口出现50X时,重发一次
可以使用axois自带的方法,也可以使用
axios-retry
插件,axios-retry
插件更简单,底层也是axois方法实现的。相比较而言,axios-retry更简单,但不知为什么,我没有实现
// request.js
// 默认重发3次,每次的间隔时间为3s
service.defaults.retry = 3;
service.defaults.retryDelay = 3000;
export function againRequest(
error,
axios,
time = error.config.retry
) {
const config = error.config;
if (!config || !config.retry) return Promise.reject(error);
// 设置用于记录重试计数的变量 默认为0
config.__retryCount = config.__retryCount || 0;
// 判断是否超过了重试次数
if (config.__retryCount >= time) {
// alert
return Promise.reject(error);
}
config.__retryCount += 1;
const backoff = new Promise(resolve => {
setTimeout(() => {
resolve();
}, config.retryDelay || 1);
});
return backoff.then(() => {
/*
以下三行,根据项目实际调整
这三行是处理重发请求接口变成string,并且重定向的问题
*/
if (config.data && isJsonStr(config.data)) {
config.data = JSON.parse(config.data);
}
config.baseURL = "/";
return axios(config);
});
}
export let isLoop = config => {
if (
config.isLoop &&
config.isLoop.count >= 0 &&
config.url == config.isLoop.url
) {
return true;
}
return false;
};
export let isJsonStr = str => {
if (typeof str == "string") {
try {
var obj = JSON.parse(str);
if (typeof obj == "object" && obj) {
return true;
} else {
return false;
}
} catch (e) {
console.log("error: " + str + "!!!" + e);
return false;
}
}
};
注意到是: axois不能是0.19.x
issue中提到使用0.18.0。经过实践,0.20.x也是可以的,但需要进行不同的配置,具体请看issue。参考资料中有提到 axios issues github
也可以使用axios-retry
npm install axios-retry
// ES6
import axiosRetry from 'axios-retry';
axiosRetry(axios, { retries: 3 });
取消机制
当路由发生变化时,取消上一个路由正在请求的API接口
监控路由页面: 调用cancelAllRequest方法
// request.js
const pendingRequests = new Set();
service.cancelAllRequest = () => {
pendingRequests.forEach(cancel => cancel());
pendingRequests.clear();
};
轮询
轮询有2种情况,一种是定时器不停的请求,一种是监听请求N次后停止。
比如: 监听高低电平的变化 - 如快递柜的打开&关闭。
-
一直轮询的请求:
- 使用WebSocket
- 连续失败N次后,谈框。<N次的请求中,请求成功了,则重置计数器
-
轮询N次的请求:
- 连续失败N次后,谈框。<N次的请求中,请求成功了,则重置计数器
export function api(data, retryCount) {
return request({
url: `/xxx`,
method: "post",
isLoop: {
url: "/xxx",
count: retryCount
},
data: { body: { ...data } }
});
}
自定义api url的原因是:
同一个页面中,有正常的接口和轮询的接口,url是区分是否当前的接口是否是轮询的接口
监听滚动
对于图表类的功能,监听滚动事件,根据不同的高度请求对应的API
节流机制
- 用户连续多次请求同一个API
- 按钮loading。最简单有效
- 保留最新的API请求,取消相同的请求
错误码解析
网络错误 & 断网
if (error.toString().indexOf("Network Error") !== -1) {
if (configData.isLoop && configData.isLoop.count >= 0) {
networkTimeout();
// 关闭所有的定时器
let t = setInterval(function() {}, 100);
for (let i = 1; i <= t; i++) {
clearInterval(i);
}
return;
}
networkTimeout();
}
404
else if (error.toString().indexOf("404") !== -1) {
// 404
}
401
else if (error.toString().indexOf("401") !== -1) {
// 清除token,及相应的数据,返回到登录页面
}
超时
else if (error.toString().indexOf("Error: timeout") !== -1) {
if (!axios.isCancel(error) && !isLoop(configData)) {
return againRequest(error, service);
}
if (configData.isLoop && configData.isLoop.count === 3) {
requestTimeout();
}
}
50X
else if (error.toString().indexOf("50") !== -1) {
if (!axios.isCancel(error) && !isLoop(configData)) {
return againRequest(error, service, 1, 2);
}
if (configData.isLoop && configData.isLoop.count === 1) {
requestTimeout();
}
}
未知错误
else {
// 未知错误,等待以后解析
}
总结
结果将状态码梳理后,客户基本看不到API错误了,对服务的稳定性和可靠性非常满意,给我们提出了表扬和感谢。我们也期待老板升职加薪!