十六 服务优先级调度

当系统当前资源非常有限时,为了保证高优先级的服务能够正常运行,保障服务 SLA,需要降低一些非核心服务的调度频次,释放不防资源占用,保障系统的整体运行平稳。

1 设置服务优先级

服务优先级调度有多种策略:

  1. 基于线程调度器的优先级调度策略。
  2. 基于优先级队列的优先级调度策略。
  3. 基于加权配置的优先级调度策略。
  4. 基于服务迁入迁出的优先级调度策略。

2 线程调度器方案

Thread.setPriority()。
线程优先级被线程调度器用来判定何时运行哪个线程,理论上,优先级高的线程比优先级低的线程获得更多的 CPU时间。而实际上,线程获得的 CPU时间通常由包括优先级在内的多个因素决定。
服务在发布的时候,可以根据用户的优先级配置策略,将服务优先级映射到线程优先级中,然后创建多个不同的优先级线程,分别调度对应的服务,工作原理如下:
![][1]
算法简单,开发工作量小,但是不同的操作系统上,有自己不同的策略,这对于需要某些精确控制执行比例的服务是不可接受的。

线程优先级可以用来提高一个已经能够正常工作的服务的运行质量,但是却无法保证精确性和跨平台移植性。因此,通常不建议使用线程调度器实现服务的优先级调度。

3 Java 优先级队列

Java的 PriorityQueue是一个基于优先级堆的无界优先级队列。
![][2]
缺点在于:如果持续有优先级高的消息需要处理,会导致优先级低的消息得不到及时处理而积压。而积压到一定程度之后,低优先级的消息可能已经超时,即便后续得到执行机会,由于已经超时也需要丢弃掉,在此之前,它灰一直占用优先级队列的堆内存,同时导致客户端业务线程被挂住等待应答消息直到超时,从资源调度层面看,PriorityQueue 的算法并不太适合分布式服务框架。

4 加权优先级队列

分布式服务框架的服务优先级调度并不是只处理高优先级的消息,而是按照一定比例优先调度高优先级的服务,采用加权优先级队列可以很好地满足这个需求。
原理如下:它由一系列的普通队列组成,每个队列与服务优先级 1:1 对应。当服务端接收到客户端请求消息时,根据消息对应的服务优先级取值将消息投递到指定的优先级队列中。
工作线程按照服务优先级的加权值,按比例从各个优先级队列中获取消息,然后按照优先级的高低将消息设置到工作线程的待处理消息数组中,由于只有本工作线程会读写消息数组,因此该数组是线程安全的。
![][3]
缺点在于:如果优先级等级比较多,对应的优先级队列就会膨胀,如果优先级队列发生积压,这将导致内存占用迅速飙升。

5 服务迁入迁出

前面的几种优先级调度策略是比较传统的做法,基于服务迁入迁出的则是利用分布式服务框架的服务动态发现机制,通过调整服务运行实例数来实现优先级调度。
原理如下:

  1. 当系统资源紧张时,通过服务治理 Portal的服务迁入迁出界面,将低优先级服务的部分运行实例从服务注册中心中迁出,也就是动态去注册。
  2. 消费者动态发现去注册的服务,将这部分服务实例的地址信息从路由表中删除,后续消息将不会路由到已经迁出的服务实例上。
  3. 由于只迁出了部分服务实例,被迁出的低优先级服务仍然能够正常处理,只不过由于部署实例的减少,得到调度的机会就同比降低了很多,释放的资源将被高优先级服务使用。通过资源的动态调配,实现服务的优先级调度。
  4. 当业务高峰期结束之后,通过服务治理 Portal将迁出的服务重新迁入,低优先级的消息恢复正常执行,优先级调度结束。

缺点:自动化程度较低,对运维人员的要求较高。

6 个人总结

服务的优先级调度与动态流控不同,流控最终会拒绝消息,导致部分请求失败。优先级调度是在资源紧张时,优先执行高优先级的服务,在保障高优先级服务能够被合理调度的同时,也兼顾处理部分优先级低的消息,它们之间存在一定的比例关系。

优先级调度本身并不拒绝消息,但是如果在运行过程中发生了流控,则由流控负责拒消息。通常对于高优先级的管理类消息,例如心跳消息、指令消息等们不能被流控掉。