内容简介:轮询机制解决后端任务回调问题
某天晚上在上班时间偷偷摸鱼的时候,公司的同事的和我探讨一个问题。
问题
同事大概描述了这样一个需求:
现在有一个需求,前端有一个按钮,点击以后会调用后端一个接口,这个接口会根据用户的筛选条件去 hadoop 上跑任务,将图片的 base64 转为 img 然后打包成 zip ,生成一个下载连接返回给前端,弹出下载框。
hadoop 上的这个任务耗时比较久,一般都是 10s 以上,也就是说如果一直让前端等待,会出现请求超时的问题。
需求现阶段实现
以下是现阶段的实现状况:
用户点击下载按钮后,会把以下筛选条件传到后端,如图:
后端在收到请求后,在去 hadoop 上跑任务前会向数据库插入这么一条数据:
| id | name | status | url |
|---|---|---|---|
| 1 | 1496980062652 | 0 |
name - 以生成任务时的时间戳命名
status - 任务状态, 0 代表任务正常执行, 1 代表任务执行完成
url - 打包后 zip 的下载地址, status 为 0 时, url 为空, status 为 1 时, url 有对应下载地址
同事做到这里就卡住了。
解决方案
以生成任务时的时间戳命名真的好吗?
以生成任务时的时间戳命名使得生成的任务和任务的内容没有任何联系,举个例子:同样的筛选条件,用户每次去下载的时候都会在 hadoop 上重新跑一个任务,生成一条新数据。实际上,同样的筛选条件打包出来的数据应该是一样的,没必要每次下载都去重跑一次任务,造成不必要的时间损耗。更直观来讲,也就是说2个用户使用相同的筛选条件,但是他们创建下载任务的时间不同,导致了他们都在 hadoop 创建了一个任务和数据库插入了一条数据,实际上他们各自插入的数据除了虽然 name 和 url 不同,但是 url 下载下来的文件中的内容是一样的。
我的解决方案是将所有筛选条件拼接在一起,然后 encodeURIComponent 后作为唯一标志。
let name = [startTime, endTime, sid, client, sign, cuid, number, result];
name = encodeURIComponent(name.join(''));
这样做也就是说同样筛选条件下, name 的标志具有唯一性,避免任务重跑。
如果用户在下载 zip 的时候之前有人创建过相同条件的任务,那么则无许等待,直接就可以进行下载。
后端接口部分设计
再来设计后端接口,根据分析任务一共有三种状态,对应 status 值如下
- 未建立任务————- -1
- 任务运行中————- 0
- 任务结束,生成
url—- 1
接口伪代码如下
public function download () {
$startTime = $this->input->post('startTime');
$endeTime = $this->input->post('endeTime');
$sid = $this->input->post('sid');
$client = $this->input->post('client');
$sign = $this->input->post('sign');
$cuid = $this->input->post('cuid');
$number = $this->input->post('number');
$result = $this->input->post('result');
$name = $this->input->post('name');
$status = getTaskStatus($name);
$data = array(
'status' => $status,
'url' => ''
);
if ($status == -1) {
establishTask($startTime, $endTime, $sid, $cuid, $number, $result); // 去hadoop上跑任务
insertSQL($name, 0);
} else if ($status === 0) {
} else if ($status === 1) {
$data['url'] = getTaskUrl($name);
}
echo json_encode($data);
}
getTaskStatus 用于从数据库中获取任务状态,当数据库中没有与 $name 相匹配的数据时则返回 -1 ,有则返回对应的 status 。
接下来进行判断:
若 $status 为 -1 ,表明该筛选条件的请求是第一次出现, establishTask 创建 hadoop 任务的同时 insertSQL 在数据库中插入一条 name 为 $name , status 为 0 的数据。( establishTask 在任务结束时会自动修改数据库中的 status 为 1 ,同时插入 url )
若 $status 为 0 ,表明该筛选条件对应的任务正在 hapdoop 上运行。
若 $status 为 1 ,表明该筛选条件对应的任务已经完成,通过 getTaskUrl 从数据库中取得 url 。
最后将 data 返回给前端。
前端轮询机制
前端需要做的就是根据后端返回的结果来判定是否需要继续请求,也就是所谓的 轮询 ,根据 setInterval 来进行实现。
伪代码如下
const download = () => {
let interval = setInterval(() => {
let {
startTime,
endTime,
sid,
client,
sign,
cuid,
number,
result
} = $scope;
let name = [startTime, endTime, sid, client, sign, cuid, number, result];
name = encodeURIComponent(name.join(''));
let data = {
startTime,
endTime,
sid,
client,
sign,
cuid,
number,
result,
name
};
$http({
method: 'POST',
url: '/downlaod',
data: data,
headers:{'Content-Type': 'application/x-www-form-urlencoded'},
transformRequest: function (data) {
return $.param(data);
}
})
.then(res => {
if (res.status === 1) {
clearInterval(interval);
window.location.href = res.url;
}
}, error => {
console.log(error);
})
}, 2000);
}
时间设置为 2s 一次轮询,当 response 中的 status 为 1 即获得下载地址,此时通过 clearInterval 取消 interval ,关闭轮询。
页面刷新带来的影响
如果 hadoop 速度极慢,长时间没反应,用户可能会以为页面卡顿了,从而进行页面的刷新。
由于采取了上面同筛选条件下任务标识唯一的方法,即使刷新页面后,用户再点击下载相同条件的任务后也不会再去handoop上重新跑任务以及插入新的数据,如果任务还在 running 则等待,任务已经结束则直接下载。
如果以最初的时间戳为 name ,则会导致任务重跑,用户得重新开始等待。
关于为什么不引入socket.io?
交流中我曾询问过目前项目中是否还有其余与此相似的功能,但据了解暂时只有这一个需求,所以虽然 socket.io 相比轮询来说更节约性能,但是没有必要为了一个功能而引入一个库,这样做的感觉是得不尝试。这样做的行为类似于你为了使用 underscore 中的某个方法而引入整个 underscore 。
关于轮询机制和 websocket 的形象对比
轮询机制
客户端:服务器,你有没有消息要给我啊? 服务器:有。 客户端:服务器,你有没有消息要给我啊? 服务器:没有。 ——————————无限重复———————————— 客户端:服务器,你有没有消息要给我啊? 服务器:没有。 客户端:服务器,你有没有消息要给我啊? 服务器:有
websocket
客户端:服务器,你有我的消息了记得call我。 服务器:OK! ——————————当有消息的时候———————— 服务器:有你的消息了,客户端。 客户端:收到。
以上所述就是小编给大家介绍的《轮询机制解决后端任务回调问题》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Java 回调机制解读
- [OC] 关于block回调、高阶函数“回调再调用”及项目实践
- 即使回调IsOneWay,WCF客户端也会因回调而死锁
- 如何避免回调地狱
- 如何使用JPA回调?
- callback回调函数-python
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。