异步任务轮询设计

达芬奇密码2018-06-27 14:07
需求
  处理时间较长的请求,需要转换成异步执行,而前端通过轮询来获得任务的进度,以便告知用户需要等待的时长。实践发现,不同任务的轮询逻辑往往是相同的,不同点仅在于任务类型和任务参数(个数、顺序)。因此,可以设计一种通用的异步任务轮询接口,来复用轮询逻辑。

异步任务流程
  异步任务的流程见下图。
  1. 前端第一次请求执行任务,此任务为异步,后端会返回一个结果,使得前端去执行轮询。
  2. 后端会将任务提交到”异步任务线程池“来执行,同时传入用来标识本次任务的进度key
  3. 异步任务在执行时,会根据执行情况进行三种操作:
    1. 任务进行中。更新进度。
    2. 任务完成。删除进度。并将结果放入存储。
    3. 任务异常。更新进度为“任务中止"。
  4. 与3同步,前端周期性的访问进度轮询接口。进度轮询接口会根据不同业务的进度key,从存储中得到进度,并返回给前端。此时有三种情况:
    1. 任务进行中。返回进度给前端。
    2. 任务完成。返回完成状态给前端。
    3. 任务中止。通知前端中止轮询,并将进度删除。
  5. 前端对轮询的三种结果做不同的处理:
    1. 任务进行中,继续轮询
    2. 任务完成时,重新请求任务,会得到异步任务完成后的结果。
    3. 任务中止时,停止轮询,并根据业务情况进行下一步操作(重新发起任务、向用户报错或其他操作)。

任务进度的结构
以Json结构为例,见下图。
  • "retCode"表示任务状态,0代表任务进行中,1代表任务中止;
  • "msg"是任务中止时的异常信息;
  • "total"是总任务数,比如分页操作的总页数;
  • "loaded"是任务当前的完成度,比如分页操作中已完成的页数。
当进度在存储中不存在时,轮询接口会返回给前端一个loaded>=total的结果,代表任务完成。

任务进度的key生成规则
进度key 的生成较为复杂。首先,它由两部分构成:
  1. 不同的任务类型,它的key有不同的前缀
  2. 同一任务类型,参数不同(名称、个数、类型),代表的操作不同,所以使用任务的参数列表作为后缀
前缀的管理较为简单,可以统一到一个枚举或常量类中;后缀的生成较为复杂,需要注意的问题如下:
  1. 后端在步骤①、④接收参数时,参数顺序未保证完全一致,导致生成的key不一致。
  2. 步骤①中,前端传来的参数,后端接收不全;而前端不知道这一情况,会在步骤④中传入所有参数,导致生成的key不一致。
  3. 前端会带一些时间相关的参数(比如_t时间戳),步骤①、④的参数值会不一样,导致生成的key不一致。
  4. 某些任务依赖cookie中的字段,某些不依赖。
解决方案为:
  1. 对作为进度key后缀的参数,进行字典序排序。
  2. 在任务类型(即key 的前缀)枚举类中,维护一个“请求参数list”,其中存放所有作为key后缀的参数名。在步骤①、④中,根据“请求参数list”提取参数。
  3. 解决方案同2。
  4. 同解决方案2,维护一个“上下文参数”

本文来自网易实践者社区,经作者程义授权发布。