一个结合数据库的任务队列管理方案(iOS实现)
前言
在开发中如果碰到需要执行一些耗时比较长的任务,但是又要保证任务不能丢失,比如执行过程中由于某种原因app发生了crash,需要在下次app启动后的适当时机重新执行任务的情况,该如何解决?
解决方案
- 假设我们需要在后台执行的任务类型有上传图片的任务A,发送聊天消息的任务B,视频编辑处理的任务C,所有耗时比较久并且重要性很高,需要保证不能丢失的任务都可以抽象为一个TaskModel。这个TaskModel保留了任务执行需要的基本信息,以及用于队列管理的必要属性。方案的主要思路就是维护一个用于管理TaskModel的串行任务队列TaskQueueService(后面简称为TaskQueue),这个TaskQueue结合数据库对外提供管理TaskModel的各类方法,比如AddTask等,在一个任务需要被执行时,就会通过TaskQueue的add方法添加到队列中,并且同时将TaskModel持久化到数据库中,保证任务不会丢失。然后TaskQueue根据自己的某种规则从数据库中取出TaskModel,对任务进行调度执行。
类结构说明
-
我们抽象出任务的基类为TaskModel,需要执行的具体任务类型A、B、C为继承TaskModel的类TaskModelA、TaskModelB、TaskModelC,然后在各自的类中添加业务需要的基本信息,以及根据需要重写基类的方法。TaskModel结构如下类图所示:
TaskModel.png
taskID为自增Id,当task被添加到队列,写入数据库中时会自动加1。
status表示任务执行的状态,是一个枚举值,枚举类型分为init、running、suspend、finish、fail、remove六种状态,在taskmodel刚被写入数据中时是init的状态,在taskModel执行失败后会将状态置为fail。
priority表示Task的优先级,默认值为low,在taskQueue中可以根据task的优先级对高优先级的任务优先调度执行。
customID用于对task做某种标记,方便从数据库查询。
className存储具体任务的类型如TaskModelA。
data内存储TaskModel归档后的data数据
runCount用于存储task执行的次数,可以用于控制重试次数
run()方法是子类继承TaskModel后必须实现的方法,里面写任务执行的逻辑
prepareForAddToQueue()方法是在run之前会执行的方法,如果重写了该方法,那么在taskQueue执行task之前会先调用prepare方法,返回为yes才会继续执行task的run方法,prepare方法可以用于对task的一些校验。
retryNextTime()方法通过调用taskQueue的retryTask方法将自身的status重置为init状态,那么在taskQueue执行接下来的任务时,就会重新执行到该task。
suspend()方法是将自身的status改为suspend挂起状态,那么taskQueue在执行接下来的task时,由于只会取status为init的task执行,就不会执行到suspend状态的task。当需要重新执行已挂起的task时,调用retry方法就可以重新将该task添加到执行队列中。
-
TaskQueueService是我们维护的任务队列,它是一个单例对象。TaskQueueService的结构如下所示:
TaskQueueService.png
taskSignal用于在task执行改变状态时对外发送信号,在对应的controller中可以通过taskSignal传出的task做一些ui或者业务逻辑。
runningTask表示当前队列正在执行的taskModel
suspendTasks保存了所有被挂起的taskModel
- runNextTask()方法为TaskQueue最核心的方法,该方法中用异步执行通过某种规则从数据库中取出的最合适的taskModel。
架构图
主要架构图.png- 如上图所示,假设所有的task都为相同优先级,TaskQueue的runNextTask方法中取从数据库筛选出来status为init状态并且根据taskId排序,拿到最先进入的一个task去执行。在task执行完成后,又会触发runNextTask方法,从而继续从数据库读取下一个最合适的taskModel去执行任务。