/ NETWORK PRIORITY QUEUE

带优先级队列的网络框架设计思路

在客户端账号系统中,用户在首次登录后,客户端获取token来作为与后端交互的身份标识,之后的大部分时间客户端都处于免登录的状态。尽管用户无感知但并不代表没有暗流涌动。

问题

除了用户身份的token之外,还有一个请求时候带的token。失效很快,每次失效都需向服务端索取后,再带着该token重新请求业务接口。比如该token的失效时间是2小时,那么用户隔夜之后打开app都必须先去请求token,这种场景我们只需要侦听app的启动事件,再控制相应的时序即可。 network1 还有一种场景是在用户在长时间使用中失效,比如看视频这类的操作,失效可以发生在任何界面,发生在任何接口上面。 network2 我们可以在每一个业务网络请求中加入对token失效的处理,或进一步在网络框架层的中统一处理。然后魔鬼隐藏于细节。比如在一个时间节点同时有多个请求,那么都会遭遇token失效的情况,那么该如何去处理多个token更新请求。token更新后,该如何把之前的失效请求重新分发出去。如何保证在token请求时,其他请求处于等待状况,等等。

设计

面对上述诸多问题,我们抽象了一下业务需求,重新审视和设计网络框架。新的网络框架起码需要满足如下几个需求:

  1. 拥有优先级,优先级高的请求会阻塞优先级低的请求,优先级低的请求在高请求回来后,有机会重新构造请求参数。
  2. 某一类请求在某一时刻只允许有一个请求。
  3. 网络框架与业务自始至终是解耦的。
  4. 请求直接是接耦的,避免嵌套发生。

首先仿照iOS的GCD的优先级,我们定义网络请求的业务优先级,Unique是个特殊优先级,级别最高且同时只存在一个。是针对本案例中token的存在,所有的请求都必须等待token请求的执行,且token请求只能同时有一个执行。在没有这样极端业务的网络层中Unique可以避免去使用。用次高优先级PriorityHigh即可。

typedef NS_ENUM(NSInteger, RequestPriority) {
    PriorityLow,
    PriorityDefault,
    PriorityHigh,
    PriorityUnique
};

我们在请求的状态中,增加了一项很重要的Redirect状态,它意味着某一请求可被临时重定向存储,在被调出后重新构造参数再发出请求。

typedef NS_ENUM(NSInteger, RequestState) {
    Initial,
    Retry,
    Redirect,
    Runing,
    Success,
    Failure,
    UseCache
}

我们需要自定义一个队列来满足请求的存放。在网络层之前增加一个存放层,用一个可变数组即可。根据请求的优先级和状态进行请求的选取执行和清理等操作,将筛选出的请求传递给网络层处理,比如AFNetworking。增加dumpWaitingQueue方法,用于通知请求队列中的请求继续执行。

flow

上面是一个简陋的手绘流程图,从流程图的中轴我们可以看到一个完整的请求流程。一个网络请求在进入这个“机器”后会根据自身的状态进行过滤指派到它应该对应的位置,是个典型的状体机模型。在设计上,讲build header和处理失败的代码用block开放出去,使得具体的业务逻辑交由框架之外处理。比如获取到token失效的业务码,就会再创建一个新的token请求,旧的业务请求被标记为redirect,两个请求一并传回给“机器”,“机器”再根据优先级先选择请求token而暂存业务请求。这样就较好得实现了请求之间的接耦。token请求成功后,触发dump机制,将缓存队列里的请求,进行筛选,执行有条件的请求。如果token请求失败,那么其他请求也就失去了意义,这种情况下的dump机制就会舍弃掉本地所有的请求,向各业务代码发回失败的回调。

总结

以上只是简单的设计思路,还有一些琐碎的实现细节暂不一一详述。这个框架在当前业务场景中发挥着核心的中转功能,之前迭代过程中发生过好几次状态考虑不全的情况,都是白日百思不得解,梦中惊醒幡然醒悟。包括状态机没有合适的终止机制,导致无限循环。状态疏漏导致某些请求不会回调等。深刻认识到在设计时思考全面的重要性,哪怕现在也不敢保证没有疏漏。状态就就好比现实生活中的流水线,吃进了东西不能排出,就会导致积压于某一点,最后瘫痪。如果监测到残次品,不能有效清除,残次品就好在流水线上反复无意义重造,浪费资源。