计算服务资源调度管理

文章目录

  • 前言
  • 总体架构
    • “ULT”和“KLT”抽象
      • “内核”
      • “容器”
      • “虚容器”
    • 内存抽象
    • 虚拟存储(容器调用)
  • 多机器调度

前言

今天复习了一下操作系统,系统过了一下,感觉还有点时间,那么顺便来讨论一下,关于我的毕设的一些相关,那就是这个,HuteroxAI算法开放平台的一个构建,设计。当然,我们这边还是先讨论后端的构建,也就是我们整个系统的构建。

那么我们这里先来区分一下,这个项目和我们平常见到的一些项目有啥区别,和大部分人的一些软件项目毕设,有啥子不一样的地方。
首先是功能和用户群体:

  1. 我们做的是一个算法的开放平台,做的主要是开放API,为他人提供服务,也就是toB端,而不是toC端。
  2. 用户群体主要是搞技术的,一般是面向程序员。
  3. 具备交流社区的功能,除了官方文档,也鼓励用户在社区内活跃。具备技术交流平台的功能。

然后是系统设计上:

  1. 需要具备基本的中台管理,用户端,等等基本的一套curd(单体,微服务…)
  2. 有大量的算法资源需要进行管理,需要进行合理的调度,管理
  3. 跨语言开发,除了基本的JavaWeb,还涉及到Python深度学习算法的开发,整合,和基于flask等平台的服务开发

当然在算法部分,本质上和调库差不多,因为算法都是大佬搞好了的,我只需做微调,或者说设计好,我的调用程序,让这个算法能够很好的在我的平台发挥作用。毕竟,从底层构建新的算法是很难的,反正我一个本科生是做不到的,再说了,按照论文复现算法,从本质上来说不也是“调库”只是在这个过程当中,涉及到数学语言到计算机工程语言的转化,是更加低级的处理。我们做的应用是高级调用罢了,当然各有各的难点,不存在技术上的高低贵贱,都是为了搞钱,谁也别看不起谁,你要是真那啥,我只能说先生大义,记得开源给小弟look,look。哦,这里的高级低级是对接操作系统的定义,是指处理的工作,底层,上层的意思。

总体架构

首先基本的服务留存,和架构咱就不说了。我们主要关系的是我们如何实现这个调度,这个调度最主要的就是,算法方面的调度,算法资源的调度。

当然,背景我们再简单介绍一下。
计算服务资源调度管理_第1张图片

我们现在最主要关系的就是,这个图中框起来的部分。这个非常重要,因为其他的其实,也知道,都是老朋友了,都有现成的解决方案,而且说句难听的,大部分情况下,那些系统说白了都是IO密集型任务,无法就是查表+简单运算。但是这个不一样,它是属于计算密集型服务,我必须合理地分配好这些资源的调用。因此这个组件必须要好好设计好,那么这里和我们先前做的GPT聊天又不一样,那个算法固定,只有一个算法,只需要维护好上下文信息就好了,当然还有一个原因,当时只是做了一个示例的进程,毕竟只是一个简单的期末作业而已,我不需要考虑那么多,但是现在不一样,这个是毕设,再将来还是需要部署演示,那么这个就需要认真一点了。否则容易被怼,当然我打赌,答辩的时候估计没有老师能够问道这里来,整个系统目前,需要我们花点心思设计的其实也就是这个地方,其他的调用别人写好的中间件就好了。

“ULT”和“KLT”抽象

首先,我们来对我们的服务进行抽象,ULT,和KLT是啥,我想应该不需要我解释了,如果需要的话,那我觉得,这篇文章可能不适合你。因为接下来,我们对于这个系统的抽象,其实会涉及到很多操作系统当中的概念。因为,我们要知道的就是,操作系统本质上其实就是一种特殊的,能够加电自启的资源管理程序。只是这个资源包括:处理机,文件,设备,内存。而现在,我们这个系统的资源主要是我们算法的资源。

那么接下来,我们不妨将SpringBoot为主的业务程序和以Python为主的算法程序进行如下抽象:
计算服务资源调度管理_第2张图片

这样一看,是不是,就很像俺们操作系统里面,用户进程和内核进程之间的关系了。实际上,我们他们之间的关系就是这样的,操作系统随时需要处理好多道程序间的并发,并行工作。而我们的系统也是一样的,只是,我们是在软件层面上进行抽象,一样的,运算资源对我们来说是和操作系统里面的内存一样珍贵的。当然也确实是一样贵重的,因为这个运算资源烧的也是内存和cpu和gpu啊。

所以在这里,我们可以这样看待我们的系统。
计算服务资源调度管理_第3张图片

所以,在这里的话,当我们的一个用户请求过来之后,我们在SpringBoot里面维护了一个线程池,然后呢,在这个里面去分配一个空闲的进程,或者是任务队列最短的队列,然后分配给他,之后,我们的Python里面维护了基本的算法进程池,这里为啥是进程池,我就不多说了。这里的话,我们有好几个算法,我们先不管,后面再说。那么在这个Python里面处理完毕。那么这里为什么一个可以是线程池一个是进程池,我就不多说为啥了。

“内核”

那么在这里,我们开始进一步定义,我们的内核,也就是Python抽象。首先由于Python存在的性能问题,能够让它跑算法已经非常不容易了,所以,对于任务的管理,调度啥的,我们只能交给Java,其实我也想试试go的,但是可惜,我的代码库里面它的代码太少了,不方便cv, 整合。那么对于内核,我们一样,还是采用微内核的方式,没办法,问就是Python不行。换一句话说,就是,Python,什么都不要管,这个内核就是一个“最笨”的调度器。当我有指令说你要创建一个新的算法进程的时候,你就给我乖乖创建,叫你关闭就乖乖关闭。其他的啥都轮不到你管。所有的接口都不对外暴露,只对Java这边暴露。

“容器”

有了这个还不够,为了方便管理,我们在对这些进程进行抽象。我们知道,我们这里面维护了两个池,一个是Java的一个是Python的。里面维护了线程,进程来负责对我们的任务进行处理。那么为了更加方便地进行管理,资源对象的调用,尤其是我们算法部分的调用。以一个目标检测算法为例子,当用户在我这里创建这个服务的时候。假设,用户用的是最基本的服务,也就是没有定制化需求,用的是coco数据集。那么这个时候,我在算法层面肯定不能有几个用户就开几个目标检测算法的进程进行监听是吧,提供服务。我肯定是维护一个线程池,生产者,消费者消费这些请求。但是问题来了,我有好几个好几种算法,但是我的服务机器是有限的,我能可能每种算法都分配相应的资源,因为有些算法可能被调用的频率根本就不高,甚至没有,所以这里就扯到了资源调度的问题。在操作系统里面,以进程为单位,然后PCB维护里面的信息。那么在这里,我也是以进程为单位,但是我要维护的信息要更多。所以,这里我需要再抽象出一个东西。那就是容器。

一个容器,就是一个线程,一个进程,是参与我们这个系统的资源分配的最小单位。

他维护了这几个基本信息:

  1. ID
  2. 创建时间
  3. 运行时间
  4. 命中次数
  5. 最近命中时间
  6. 模型信息
  7. 算法类型

对不同的算法类型,在维护的时候,是没有分批的,虽然是说,例如对目标检测的一类进行维护,其他的一类进行维护。不同类型的算法,占用的资源不一样的,你跑个yolov5和跑个6B 资源消耗是完全不一样的。但是呢,不管你咋样,如果你长期没有被使用,那我就不管那么多直接杀掉。
此外,我们对这些信息都要在数据库里面备份好,相当于一个程序的PCB,一旦宕机,可以快速恢复现场。

“虚容器”

我们刚刚说的容器,是我们的算法容器,也就是我们Python当中的,对于Python当中每一个容器的运行状态,我们通过这个系统是可以获取的假设。由于我们的Python容器非常“笨”,它没有说明缓存功能,你给我一个待处理数据,我就给你处理,完了之后返回给你。而且,我们在这个部分,是同步的,Python接收一个数据,就必须处理好,所以我们要在Java这边对信息进行维护,而这个巧了,看到刚刚的玩意,这玩意不也就是一个线程嘛。所以,这里我们也抽象一下。信息是啥呢,是一样的:

  1. ID
  2. 创建时间
  3. 运行时间
  4. 命中次数
  5. 最近命中时间
  6. 模型信息
  7. 算法类型
  8. 日志
  9. 服务信息
  10. 资源占用

这里还多了一个日志,服务信息,这个也是一样的,要保存在数据库里面,方便恢复现场。到时候,该道歉道歉是吧。
此外这里还有一个资源占用信息,这个非常重要,一个虚拟容器,占用信息描述的是一个容器的资源信息。这个其实也就是虚拟化,虽然用的是一个容器可能,但是在这里面是相互独立的。并且由于不太算法索要的系统资源不同,所以,当需要淘汰和创建新的容器的时候,需要注意,系统能不能创建。比如在当前的机器上面,已经跑了5个yolo了,这个时候新的容器是要跑一个定制化的6B,那么整个系统直接裂开。所以这个时候就得来个“中断”。而且,我们任务,每一个容器的优先级是一样的,不能说,为了跑这个B类算法的容器,就把我A类算法好好的容器给挤下去了。如果A类调用的多,那么A类算法就是我的摇钱树,B类算法,如果用户没有冲VIP,我才不管呢。想要更好的服务就得交钱,没办法的,毕竟都不容易,谁也不是做慈善的,哪怕我白嫖,我也会给个三联加支持。当然要money没有。

内存抽象

那么接下来,我们继续抽象。按照我们刚刚说到的,我们有很多容器,这些容器是最基本的资源单位,然后为了方便服务,多个虚容器对应多个容器。容器本质上就是一个进程实现的。所以也是相当于抽象了的ULT,KLT。那么这个时候,我们在对我们的内存进行抽象。

现在我们的容器还是在一个进程池里面的。
计算服务资源调度管理_第4张图片

所以,这个时候,这个线程池其实就是相当于操作系统里面的“内存”,我们一个个容器其实就是,我们的程序,资源。

那么这个时候,你估计已经猜测到了,没错,我们接下来就要局部性原理用到底了!!!

虚拟存储(容器调用)

这里,我们把一个个容器,看作操作系统当中的程序就好了。什么缺页中断啊,啥的其实在我们这里一毛一样,但是同样的,我们在我们这个系统的资源分配上面,最小的单位还是容器。但是这里“内存”的大小单位还是操作系统的那个单位,容器只是参与资源分配,为啥呢,因为不太算法,容器计算资源占用不同。对于计算机资源的分配,这个是真正的操作系统干的活。操作系统,对我这个系统是透明的。

那么回到我们这里,我们此时容器的启动,调用,其实就和操作系统里面的虚拟存储是一致的。因为,我们的资源有限我们的计算机资源太少了,不能很好分配资源。例如在一个时间段内,A类算法请求次数多,B的也多,但是我的设备最多同时支持2个一个A,一个B,甚至只支持一个,那么就会涉及到频繁交换,置换的问题。

在我们的这个系统当中,有两种容器,一个是Java程序当中的虚拟容器,这个也就是用户基本的,基本上没有太大的运算,对于这个玩意来说,启动,创建非常快,容器。但是对于Python的来说,本来人家就跑得慢,跑的还是复杂运算,现在还要切来切去,那么就会很吃力。这个没有任何办法,只能加设备。很典型的例子,你一个SQL优化到死,机器不行,那它很行,机器要10s才能响应,一个SQL提升1,2秒差别不大,如果能够压缩到1,2秒,那个意义我觉得很大,如果单纯通过sql优化可以到这种程度,我有理由怀疑,你在夹带私货,或者编码存在问题。

那么我们回到这个,这里的话,就是用我们的置换算法去置换了。
图中指的是,我们的虚容器。在这里的话,我是这样处理的,一个用户开创的一个服务,对应了一个虚容器。主要是说,尽可能分散到用户头上,让跟多的用户能调用。然后对于我们的真正的容器的话,其实就好办了,我们假设,一个A算法容器,可以对应10虚拟容器,那么当没有那么容器的时候,我们就可以关掉真正的容器了。如果后面A算法,一直没有虚拟容器指向它,那么A算法就完成回收,暂时不开。这样,我们就只需要管理好我们的虚容器就好了。这个管理好了,我们去通知真正容器,也就是让我们提到的”微内核“去调度,关掉容器。

计算服务资源调度管理_第5张图片

那么这个时候的话,我们就有很多算法来做这件事情了。

那么这里的话,我们抽象好了,就可以正大光明地去使用这些个算法了:

FIFO算法(First In First
Out):按照页面调入内存的时间顺序进行置换。缺点是无法考虑页面使用频率,可能会将经常使用的页面替换出去。

LRU算法(Least Recently
Used):置换最近最少被使用的页面。实现有两种方法:一是通过维护一个访问时间戳来记录每个页面最后的访问时间,置换时间戳最老的页面;二是通过维护一个链表或二叉树来记录每个页面最近的使用情况,置换链表末尾或者二叉树叶子结点最长时间未被访问的页面。相对于FIFO算法,LRU算法的效果更加优秀,但是由于需要维护数据结构,因此实现成本更高。

LFU算法(Least Frequently
Used):置换访问频率最低的页面。通过维护每个页面的访问次数和使用次数,将使用次数最少的页面置换出去。LFU算法能够有效地识别和淘汰长期不活跃的页面,但是需要额外的计数器来记录访问次数,因此增加了算法的实现成本。

最优算法(Optimal Replacement
Algorithm):置换距离未来使用最久的页面,理论的完美算法,但是由于需要预测页面未来的使用情况,因此实现难度非常大。

Clock算法:维护一个循环队列,其中每个页面都有一个访问位(也称为“存在位”)、一个修改位、以及一个时钟指针。当页面被访问时,将对应的访问位设置为1。在进行页面置换时,从时钟指针指向的位置开始遍历队列,如果该页面的访问位为0,则直接替换该页面;如果该页面的访问位为1,则将其访问位设置为0,并将时钟指针后移。如果时钟指针经过多次遍历后,仍然没有找到可以替换的页面,则必须选择某个访问位为0的页面来置换。

这些都是考纲里面的玩意。那么在这里的话,OPT是不可能实现的,在这里,我们基本上都是内存基本的调用。所以在这里clock要用的话,不需要双位的,也就是改进的,但是我们这里对缺页率的要求要高一点,所以,这里还是采用LRU。当然,对于充钱的用户,直接开新的机器(狗头)

多机器调度

之后的话,就是我们大体的设计了。现在我们已经抽象出来了,容器这个东西,对于每一个虚拟容器来说,它是在SpringBoot服务里面做了个维护。然后在这里的话,目前已经有的高并发,微服务组件帮做我们做了这方面的多机器的调度问题。
计算服务资源调度管理_第6张图片

但是,在我们Python程序当中是没有的。

所以这里就很难受了,那么在这里,我要做的就是,由于是跨系统语言,跨机器调用。我需要一个管理中心,把每一个容器都注册上去,选择哪一个机器的话,我们可以还是交给这个现有微服务组件,让它来给我选择。但是选择哪个容器,需要我们这边定义。

所以,这里的问题,在于,我需要知道每一台机器当中,每一个真正的容器的情况。选择哪一台机器,是由我们的微服务组件决定,例如,距离,机器状态啊觉得的。哪一个容器,那就是我们调度决定的,所以,我要先知道选择哪个机器,然后选择哪个容器。换一句话来说,我需要一个接线员。

刚刚我们讨论了那么多,都是在说单独一个服务里面怎么做,那么现在要将他们都连接起来。

计算服务资源调度管理_第7张图片
它的作用很明确,就是,获取到算法也就是容器的信息,然后处理到虚容器发过来的请求,然后安排合适的容器完成操作,并且在调度的时候,能够获取到这些组件的信息。所以,这个连接系统又需要使用到服务组件之间的高性能通讯,并且这个组件也是在微服务当中的一个组件,用来调度。

那么这个时候,没办法,我们的服务将由如下结构大结构组成:

  1. 网关服务
  2. 平台服务,开发算法的API接口,技术社区,充值接口,登录接口…
  3. 网盘服务,这里可以上传自己的数据集,所以需要网盘管理
  4. 中台管理
  5. 算法服务
  6. 连接调度系统

其实要是非要拆的话,还能拆,但是吃过一次亏了,就我一个干活的,疯了?!所以,不拆了。所以这个还需要基于netty做一个高性能的连接调度组件。

当然完整的情况是,还需要要像nacos之类的组件一样提供一个管理界面,这里的话,就算了,我打算统一给到中台去。

你可能感兴趣的:(突发奇想,系统架构,考研,学习)