状态机模式调度实践:混合云平台调度框架

在运维平台体系的相关开发工作中常会出现这样的状况:系统调度工作流程过度冗杂,同时需要多层次的状态流转。常见的coder会将一个方法从头写到尾,写出无法维护的代码。但是,如果参考K8S的设计模式,意图拆解每个流程来分片执行,并遵循 当前状态 -> 目标状态 调谐的方案,可以将状态及的流转变得优雅且高效的同时提高可观测性。

背景

CMDB的建设就是一个例子:主线流程十分冗长,而且请求云厂商的过程会产生大量一步逻辑。内部最初的CMDB版本确实是通过Sleep来解决异步问题的,并且调度器有严重的问题。这导致了主线流程容易崩溃,并发数高的时候性能极差。加上为适配不同云厂商编写的代码,代码结构十分混乱。
主机申请流程

异步调度

为解决这个问题,提高性能和稳定性,在新的版本中,参入了异步运行,逐步调谐(Reconciliation)的概念:

  1. 申请过程中的资源(DeliveringServer)会处在不同的状态,每个状态有自己的上下文(Context);
  2. 调度器的每一次调度相当于一次时钟轮转,会计算当前状态和目标状态,并试图通过距离目标最近的路径进行更变;
  3. 计算变更状态,查看是否达到了目标状态;
  4. 更变的状态会被记录于自己的上下文中。如果没有到达目的状态,就会等待下一次调度。

这种计算状态-发起更变的思想参考自K8S中控制器的思路。这也是K8S调度器的思想之一,其核心思想都是基于Large-scale cluster management at Google with Borg 这篇论文。抢占与调度,在CMDB的建设中都得到了充分的应用。

在这里我们处理为每一次Tick会触发一次变更并入库,然后根据变更结果决定后续的走向。

以申请主机为例,不同云厂商有不同的SDK和流程,但整体流程大同小异。通过策略模式,用虚函数定义了通用的方法,如describeInstances等。然后通过实现判断状态与状态调谐流程,就可以实现异步处理申请流程:

  • 申请主机,获得厂商ID,DeliveringServer获得Creating状态,入库;
  • 获得申请中实例,判断创建状态,进入AllocateEni状态,入库;
  • 获得该实例,进入AllocateEip状态,入库;
  • ……

这里省略了一些错误处理以及一些告警逻辑。通过此番改造,对于阿里、腾讯、aws等云厂商,原有codebase基础上,效能从3min60台开机完毕提升到3min300+台无压力。

不只是CMDB这个场景,包括一些发布系统等场景,需要与第三方系统频繁交互的场景,都可以用这种方案来梳理主线流程,提高服务性能。

AbstractServerReconciler.drawio.svg

多云适配

每个云厂商的鉴权模式、调用方式等细节各方面会或多或少有所不同,这需要要求在流转框架上设计良好的适配。抽象出大多数云厂商共有的方法,如描述实例等。同时,调度器的入参出参也必须为公用的数据结构,以此降低多云接入时不同云厂商规范不一致导致的codebase大肆改造问题。

FAQ

为何不用Activiti等流程框架?

首先,流程框架的设计是比较重的,在一些大规模运转的场景下,会出现性能问题。而且流程框架的设计会让一些需要轻量调度的场景变得杀鸡用牛刀。这也是为何很多coder想要自己设计一套体系的原因(这也是bug的出处)。

一个 Tick 流程内无法完成对应任务的执行怎么办?

有时候因为网络抖动等原因,同步实例信息会有一定的延迟。一方面Tick间距设置要符合当前业务需求,另一方面对于调谐中的资源,通过分布式锁(Redis或者其他)上锁防止重复执行。这也将服务构建成了无状态服务,是可以横向扩展的。目前场景下,Tick时间区间为3秒。

评论

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×