疫情期间,很多企业受到了较大冲击,正常的复工生产无法进行。腾讯会议作为一款非常便捷的远程协作工具,成为了国内众多企业日常会议沟通交流的主要平台,这款产品从2019年12月26号正式推出,如何在这么短的时间内有效支撑起国内数以亿计用户的访问量呢?如何保障系统的稳定运行?
下面为大家介绍,腾讯会议8天时间内完成100万核资源扩容背后的技术。
首先介绍腾讯会议大规模扩容,下图所示为腾讯会议的架构图,左右两端的用户通过交换机连接到基站,然后接入腾讯云机房,首先会通过我们的调度中心,还有腾讯后台的接入层,接收到用户发起会议的请求。
拿到请求之后,再通过一些状态机的判断,帮用户加入到对应的会议室里面,然后再跟远端的朋友或同事建立一个音频媒体长链接,再通过混音和流控达到稳定的沟通服务。
今年对于腾讯会议是非常有突破的一年,它在春节期间快速崛起,成为了众多中小企业和学校复工复学必备的APP之一。在4月15日广交会成立63年周年首次搬到网上用的就是腾讯会议,甚至腾讯会议还给联合国提供了在线的会议服务。
腾讯会议8天紧急扩容超过10万台云主机,投入的计算资源超过100万核,这在腾讯云的历史上尚属首次,这应该也是中国云计算史上前所未有的一个记录。
腾讯会议总体架构通过 TKE 对接腾讯云任务调度系统 VStation 的API进行快速扩容?那么,我们是如何在这么短的时间内交付这些资源?我们要做什么?会遇到什么样的问题呢?
实际上我们是借助当前成熟的虚拟化技术,基于已经制作好的镜像系统文件,快速部署一套用于提供服务的生产环境,所以我们只需要准备好足够的计算资源,就可以实现服务的无限扩容。
整个过程大概会分成以下4步:
第一、需要先准备资源,把物理机投放上线;
第二、再把这些资源快速分配出去,交付到用户手中;
第三、用户再将虚拟机进行初始化;
第四、上线服务。
比较常见的是接入层和代理层上的扩容。
除了腾讯会议,在春节过程中,大家或多或少也听过一些比较大型的活动,比如去年除夕夜快手成为了央视春晚的独家合作伙伴,完成了史上最大的春晚现金红包活动,发出了近70亿现金红包,最大的红包达到2020元。
腾讯云作为云服务厂商,为快手提供了全方位的解决方案和技术支持,其中利用腾讯云自研的规模调度系统 VStation,结合镜像快照、CLB等服务,在短时间内为快手扩容了数万台云服务器和超过1000片GPU资源,这以前在IDC的时代几乎是不可能实现的。据快手披露的数据,当天晚上抢红包参与的累计次数达到639亿,远超去年数据。
在这两个案例里面,其实都用到了腾讯云自研的大规模调度系统 VStation。
VStation到底是如何工作的?为什么能够支持快手和腾讯会议快速扩容的过程,下文将会展开详细介绍。
上图展示的是,腾讯云自研大规模调度系统 VStation的架构图,最上面是一些 PasS 服务,包括 BATCH、AS、TKE 等。腾讯会议使用的就是 TKE 的 PaaS服务,其他比如一些银行做大数据计算的集群,通过腾讯云提供的标准API接口,可以满足它们个性化的一些服务。
腾讯云自研大规模调度系统VStation,把成千上万台机器高效组织起来,灵活进行任务调度和管理,屏蔽了底层的技术,可以为PasS服务和其他客户带来更加稳定的计算服务。
目前VStation中可以调度包括我们的黑石、异构服务、GPU,还有具有丰富配置的云服务器,像标准型、内存型,还有高主频的计算型,以及我们最新推出的轻量、虚拟化服务,通过云API的方式,为PaaS服务提供高吞吐、高稳定的服务,比如像PaaS 和 AS 它们就是利用VStation的快速创建能力,打造自动化、低成本、容错性高的优势,给客户提供更好的一个服务。
腾讯云自研大规模调度系统 VStation 管理着数10万个物理节点承担的资源和任务调度工作,经过我们多年打磨,对于大批量高并发请求进行了充分的优化, 目前腾讯云交付的性能可以达到每分钟高达5000台。
下文详细介绍一下VStation的具体架构,如下图所示,我们会通过MQ,连接下面每一个模块,这样的话可以达到模块间的松耦合特点。我们每一个交互流程都是通过任务配置化的,可以快速去新增和修改任务。通过配置化,也可以快速把下面的几个不同模块进行关联。
另外VStation系统具备事务原子性的特点,让我们的每一个操作都是原子性的,如果失败的话,就会进行快速的回滚。另外我们也可以快速去追踪每一个操作的记录。
我们以腾讯会议交付云服务器为例,为大家介绍这个系统是怎么工作的?首先如果监控系统发现会议有扩容需求的话,它会通过API下发创建虚拟机的任务,把先前已经准备好的镜像文件和需要的配置,比如需要的是标准型还是内存型,需要什么样的云存储等这样相关的一些配置,打包成API的参数,我们接收到这个任务之后,会通过API去我们的任务配置中心去寻找API对应的配置,配置里面会记录了几个关键的信息,它的操作名称,操作里面包含的不同的模块,比如上图所示的 dispatch、scheduler、image、network、 compute等等,它们会去定义不同模块之间的执行顺序。
比如这里第1步可能会到 dispatch 这个模块,它负责的是任务分发。假设我们系统接收到1万个请求,就由 dispatch 这个模块对这些请求进行任务分发,我们会有一个队列去进行排队处理,当能够处理的任务,就会通过 MQ 流转到下一个模块。而这些模块就是上文提到的,配置化里面已经配置好了的,每一个步骤里面对应的是MQ一个模块,模块里面也会有对应的命令字。
比如下一步我们把任务分发到 schedule里面,就会有一个分配资源的命令字进行处理,从数10万台物理节点里筛选出一台合适的宿主机进行创建虚拟机这样的动作,然后它再通过任务配置化,将消息流转到下一个模块。
下一个模块再根据用户指定的镜像文件,下载到用户刚刚选择的宿主机上,接着把整个环境准备好。
然后,我们会去 network模块进行IP的申请,把IP配到虚拟机里面,这样把这些动作全部打包好之后,再到compute模块,让这些机器通过我们的虚拟化开源套件,把虚拟机快速启动起来。
所有这些过程中间都是一步接一步,当出现失败的情况,我们就会按照任务配置化的方式,配置它对应的回滚步骤,把刚刚做的所有事情进行一个回滚清理操作,保证我们整个事务的原子性,保证我们的资源都能够干净的清理。
error模块是用于异常处理,我们会把所有的异常情况都发到error模块,error模块会对当前的情况进行分析,判断它是处于正常情况下的error,还是处于一个回滚状态的error?如果它是一个正常的error的话,会触发回滚,并且及时发出告警,让研发人员快速处理。而debug模块,是用于记录我们整个过程,当我们要进行排障处理的话,就会利用到这个模块。
在腾讯会议和快手快速扩容的过程中,我们遇到了什么样的挑战呢?
首先任务时间都非常紧迫,其次调度的规模非常巨大,都是需要数万台的云服务器资源。因为时间紧迫,所以它们在扩容的过程中都会伴随着高并发的请求,这样的话我们整个系统也会遇到瓶颈,系统整体的吞吐量会下降。
针对调度质量吞吐率这样的问题,其实很多公司和研究机构都做了一些工作。曾经谷歌就提出,他们认为调度系统的演变规律是从统一调度架构,演变出二级调度架构和共享状态架构。如下图所示,最左端展示的就是最简单的统一调度架构。
第1代的Hadoop的MapReduce,其实就是这种简单的统一调度架构,它有什么样的特点呢?它的最大特点就是简单,因为它一个进程就把所有的宿主机都管理起来了,所以相对来说整个调度过程比较简单,也比较快速,调度出来的也是一个最优解。
但是这种架构的缺点也很明显:第一,它的规模不能太大,规模太大的话,整个调度性能就会急剧下降。第二,因为是统一调度架构,所以它不可能是分布式的,会存在单点的问题。
为了解决这些问题,演进到第二层架构,它是两级调度架构,跟统一调度架构的最大差别是,它把调度域拆成了两部分,把整个宿主机分到了多级节点里面,每个节点只有一小部分是主机,然后再通过状态标识宿主机是否被占用。
这样做的好处就是解决了规模性的问题,它可以支持的规模比统一调度架构要大。当我们的调度请求下发下来,要去寻找一个最优解的情况下,它需要在每一个自己集群里面去寻找一个解,但是这样的话它需要一个串行的动作,所以整体来说性能也是会存在一些问题的。比较典型的代表是 mesos,就是通过这种方式来进行调度的。
第三层就是共享状态调度架构,其实腾讯云VStation早期也是使用这样的共享状态调度架构。它的一个优点就是采用了乐观无锁机制,把我们的全局人员通过分布式的方式来进行部署,这样我们每一个节点里都有全局的宿主机数据,这样它调动起来的话,就可以找到一个最优解,同时它也支持大规模的调度。但它也有缺点,就是当请求量比较大,宿主机规模比较多的情况下,会出现调度冲突,这样会导致整体性能急剧下降。
接下来我们再来看看详细的系统调度情况。如下图所示,展现了一个比较简单的调度过程。
以腾讯会议为例,假设收到一个调度任务,传下来的参数,包括机型、CPU,对应的云盘、GPU卡这样的一些信息,VStation会通过这些信息进行一些基本的数据过滤,这些条件我们称之为硬性条件。
过滤完了之后,我们会对剩下符合条件的数据进行打分,打分的依据,比如根据资源利用率、碎片率,亲和/反亲和性,是否有一些约束等等条件来进行打分,打分完了之后,我们就会对数据进行排序。
以上图右边为例子,里面有6台宿主机,首先我们过滤了两台不合适的,还剩下4台,接着对这4台进行简单的打分之后,得到一个3、2、1、4 这样的结果,然后对它们进行排序,排序完成之后我们取第1个,这就是一个最直观朴素的调度过程。
很明显可以发现,因为我们采用共享状态调度架构,每个节点都会有一个全局的数据视图,所以它们选举出来的宿主机基本上都是非常类似的,所以在并发的情况下会加剧整个调度的冲突。
我们在现场观察发现,在高并发情况下,当数据规模达到一定的情况下,整个系统的调度吞吐量会下降得非常严重,原本的单次调度在100毫秒的左右,甚至会上升到分钟级别。
为了解决这个问题,我们引入了最新的架构,从原本的共享状态调度引进到我们现在的混合式调度架构,如下图所示。
混合式调度架构下面是接入层,有 shard 分片和 master,其他跟之前的两级调度架构是有点类似的。但是我们是基于两级调度架构的缺点进行优化,最终整合出这样的混合调度架构思路出来。
我们引入了机型管理模块,机型管理模块主要负责对虚拟机进行定义,比如有标准型、内存型、计算型等不同的机型,机型里面也会有不同的一些定义,我们可以根据用户指定的机型,抽象出一个调度因子,最后我们的调度系统不是在调度一个虚拟机,而是在调度这些调度因子,我们还可以对调度因子进行一些适当的抽象和调优。
比如在大规模高并发场景下,我们对调动因子可以进行一些相似任务的合并,比如前文提到的腾讯会议或者快手,如果要进行快速扩容的话,其实他们在选取的机型上有非常大的相似度。
如下图所示的这个例子,这里大概有6个调度因子,根据它们的因子抽象成不同的颜色,如图中展示的进入顺序先后是蓝色、黄色、红色、蓝色、黄色、蓝色这样的一个入队顺序,接着我们通过类似于优先队列这样的思路,把相似的调度因子进行重新排序。
这样的话在出队的时候,我们就可以把调动因子比较相似的那些调度任务进行快速的合并。像上面这个例子,我们把原本6个调度任务抽象节省到三个。这样的好处很明显,在大并发的情况下,我们原本可能要调出1万个任务,通过相似合并之后,最终只需要调度1000个可能就可以了,这样的话就可以降低10倍的请求量。
接入worker预处理完这些任务之后,就会把它们交到分片处理,分片会存储整个数据节点里面的一部分,这跟两级调度架构比较类似,就是每一个分片,作为存储整个集群里面的一部分数据,也有单点或者串行这样的弊端,但其实这个问题是可以解决的。
首先我们每个方面都考虑到了冷备的情况,当主节点出现问题,可以快速切到备节点,就算是我们某一个分片出问题,也即是分片上的数据没有被调度成功,但其实对整个系统来说影响不是特别大,所以我们在冷备这种情况下就可以满足分片的可用性要求。
那么,在这样的一种调度情况下,能不能解决串型和最优解这样的问题呢?其实也是可以的。
如上图所示,其实我们分片采用的思路就是一个分治的思想。如图例,上面一共有10台宿主机,我们首先过滤了两台不可用,剩下这8台已经是分布在两个分片当中。因为我们的接入层是丢到队列里面的,并且队列会实时通知到下面的每一个分片,所以跟分片之间是可以并行处理任务的。当它们认为一个局部最优解求到之后,就会把该局部最优解传给我们的master,master就又像分片一样,重新再对局部最优解进行计算,最后求出全局的最优解。
这样的好处就是,当我们的规模不断扩大的时候,我们可以通过增加更多的分片来保持我们整个系统的吞吐能力。
这就是我们混合式调度架构里面比较重要的一部分,基本上解决了前文提到的:当规模上涨的时候,从原本的100毫秒的调动性能上涨到一分钟这样的问题。
但是我们除了这个问题之外,我们还有一些其他的问题需要解决。比如一开始提到的整个VStation调度架构,它其实是通过配置化的方式来配置处理的,所以我们任何一个流程,都通过不同的步骤进行串联处理的。如下图所示,在最初的情况下,每一个步骤都要一个接一个去处理。
还是以交付云服务器为例,如前文所述的步骤,首先我们会到 dispatch 模块,先对这个任务进行分析处理,看它现在是属于繁忙还是空闲的情况,第二步到schedule调度模块,它会在众多的宿主机当中找出一台合适的宿主机,再下一步是在我们的镜像模块中选择适当的镜像,通过 network模块分配IP,最后把这些信息都通过compute模块启动一台虚拟机。
在这样的过程中,我们发现它们并不一定所有步骤都是相互依赖的,所以我们提出了一个优化思路,把串行改成并行。
首先我们可以在每一个模块的command里去做一些参数的声明,比如第1个步骤需要什么样的入场,然后会输出什么样的参数等,我们对每个步骤都做出这样的操作声明之后,就可以根据参数声明的依赖自动生成一个 DAG 流程图。
如上图所示的例子,可以看出,step1和step2其实是可以并行处理的,它们之间没有任何的依赖。而step3它是依赖step1和step2的输出的,所以必须要放在它们两个之后。step4其实跟1、2、3步都没有任何关联,所以它可以单独处理,step5需要依赖step3和step4的输出。
最后我们可以看到,从原本要进行5步这样的一个操作,我们通过并行,最后只要三步就可以完成,这样的话也可以让我们的整个流程在耗时上从原本的超过一分钟交付一台虚拟机,最后下降到10秒以内,最快速度只要5秒钟就可以交付一个虚拟机。
在这种情况下,其实我们还是要保证我们的原子性,所以在生成DAG流程图之后,我们在回滚机制上也做了一些优化,让它在出现异常情况下,也可以按照我们约定的步骤进行回滚。
除了这两块,我们还有一个主要部分可以做优化,那就是镜像文件。原本我们的镜像是存储在镜像系统的,客户如果要到一台宿主机上创建虚拟机,他需要去镜像系统上把镜像文件先下载下来。因为一个镜像文件是比较大的,有些甚至达到几十个G,下载肯定需要花费很长时间,这样我们就没有办法做到秒级交付使用。
最后通过腾讯云CBS云存储快照系统,帮我们解决了镜像下载的问题。用户事先制作镜像快照,定期制作快照备份,通过镜像快照实现快速扩容。
我们的快照系统是怎么做到秒级启动虚拟机的?主要经过以下四个步骤:
第一、当用户需要访问待拷贝数据块,宿主机通知云硬盘快照系统优先从COS对象存储搬迁指定数据;
第二、云硬盘快照系统将数据写入CBS云硬盘中;
第三、快照系统返回写入成功信息;
第四、宿主机下发读请求IO正常访问云硬盘数据。
通过这一系列的方案,最终达到一个效果。当虚拟机在收到大规模快速请求的情况下,我们能保证创建速度。从原本的遇到大规模情况下卡顿60秒,做到了每分钟能处理5000次冲突。单机最快以做到5秒钟交付一台。
最后给大家分享一下这么多年工作的一些经验,我觉得一个好的架构,它不是空想出来的,而是在问题当中去成长的。
如果你去设计一个架构,不可能一开始就想好了。就像前文所述,可能一开始想到是一个统一的架构,不可能会快速想到两级调度架构,因为你还没有遇到大规模这样的问题,只要在遇到问题之后,你才会去不断优化你的架构。就算你做到了两级架构,还是会有不断的问题出现,比如你解决了规模的问题,就有可能会引入最优解的问题,所以对每个问题去深究,提出对应的解决方案,才能把你的架构不断的去做优化。
另外,如果你的架构是一个比较简单的架构,你可能不会遇到一些问题,那怎么办呢?如果没有问题的话,我们就去制造问题。比如对当前的系统进行压测,或者对系统进行一些故障演习。通过不断的压测,不断的演习去让我们的系统做得更加健壮。
我觉得一个好的架构设计,除了要考虑高可用高性能,还需要有一些比较通用的监控。通用监控要怎么理解?它其实是一些比较底层的监控。当我们的架构在不断演变过程中,功能会不断的去修改,在修修补补这样的过程当中,肯定会遇到一些监控遗漏的问题,所以如果一个系统能够设计出一些比较通用的监控的话,这样它在不断的演进过程中,我们所遇到的监控问题通通都能发现。
比如在VStation中,我们是怎样去做通用监控的呢?比如我们的第一个模块就是一个很好的通用监控的例子,任何一个任务只要出错,它都会把异常发到我们的模块上面去,再告警出来。所以在这种情况下可以让你在整个架构的演变过程中,出什么问题都能快速去发现,你再把对应的一些监控指标都细化补上,就可以让你的系统更加健壮。
最后是快速恢复工具,因为我觉得一个架构它在设计过程中虽然考虑了很多问题,但是肯定还会有一些遗漏。当出现问题的时候,我们怎么样能让我们的系统快速恢复起来,这是非常重要的。因为如果你的架构出了问题,宕机超过几个小时甚至几天都恢复不了的话,基本上这个架构就会被老板放弃,也不可能再让你继续演进下去。以上就是我在这么多年工作过程中整理出来的一些经验。
Q:能不能说说VStation的发展史?
A:腾讯其实在2012年左右就开始接触云了,但是正式去做云应该是在2014和2015年,VStation其实也是在那个时候诞生的。当时腾讯第1台虚拟机就是我们创建出来的,当时第一版的VStation还比较粗糙,并没有像刚刚PPT上面看到的一些架构,但是它的整个框架雏形其实一直都没怎么变化,因为它遵循的就是简单就是美这样的设计理念。因为如果一个系统设计太复杂的话,其实它后面修修补补就已经让你够呛了,所以VStation整个设计理念其实一直贯彻到底,基本上没有太多的改动,我们更多的优化是在一些性能规模上面,还有在整体稳定性、可用性方面的一些优化。
Q:这个大规模调度系统是怎么设计的?
A:我们要去知道在云计算里面调度物理机的困难点。第1个,我们可以看看 OpenStack它是怎么去做数据调度的,它用的方式也跟我上文提到的那几个架构是比较类似的,它应该用的也是共享状态调度这样一个架构。但这种架构会有比较大的问题,当数据规模达到3万或者10万的情况下,它的性能其实是非常差的,因为它里面用的是乐观无锁这样的机制,但是乐观无锁有个问题,它每一个节点里面看到的资源视图都是一致的,所以通过相同的调度因子调度出来的宿主机基本上都一样,在大并发情况下,会存在非常大量的冲突,导致无锁变成有锁这样的一个问题。
所以我们的设计理念,通过参考两级调度架构把请求给分散,它的好处就是可以把请求规模固定在一定的量级里面,当量级不断扩大,我们就通过增加分片这样的方式来去解决,然后再去解决分片跟分片之间的最优解这样的问题。
Q:是全双工网络编程实现的吗?
A:它是通过消息队列来进行通讯的,所以它是全双工的。
Q :冷备为什么可以马上切换?
A:通过分布式锁这样的方式,如果主节点挂了,就可以快速切到备节点上面去,说是主备,但其实是双活的,只不过看是哪一个在工作而已。
Q:不同调度因子都用同一个队列,相比同颜色调度因子加多个队列的优点是什么?
A:为什么要用同一个队列?区分不同的调度因子,如果你是多个队列的话,你的这个队列就会变成动态的。因为你不知道你的调度因子有多少,所以我们通过一个类似优先队列这样的方式,它可以相应的去进行一些合并。这里的任务队列处理,其实性能非常高,就算是万级的任务,对于这种队列出入队来说,性能完全不会出现问题。
它的主要的性能瓶颈还是在后面的,包括资源调度,还有镜像下载这些地方。所以我觉得没有必要一定要用多个队列,因为你去管理动态队列的话,管理复杂度还是比较高的。
Q: API和任务之间用的是轮询吗?
A:我们一开始用的是轮询,但是后面也是通过状态来上报。当我们一个任务完成了之后,会把状态直接写到一个对应的有点像一个状态机这样的地方。
Q:能否谈谈在故障演练这一块如何实施的?
A:故障演练更多还是看成功率。把某些服务器直接宕机,如果成功率没有受损,其实就基本上证明你的这个架构的高可用是可行的,如果你任意一个服务器宕机之后,成功率都下降了,那就说明架构肯定还有需要优化的地方。或者看你成功率的曲线,如果宕了某台机器之后,需要大概一分钟或两分钟才能恢复的,其实也是有优化空间的。最好能够做到完全无感知,在秒级以内就能发现有宕机的情况且快速把它踢掉。
Q:关于监控,开源有哪些做得比较优秀的?
A:你指的是一些开源的监控系统,其实我觉得监控在本身的架构里面埋点会更合适。
Q:合并任务,这块的效率怎么样?设计的细节可否说明一下?
A:还是举腾讯会议的例子,假设要创建10万台虚拟机,如果单次请求达到5000到1万台的话,对我们后端的任务调度系统来说,它就要调度5000到1万次,但其实这5000到1万次里,可能是不同模块在进行扩容,比如它可能是代理层在扩容,或者是接入层在扩容,又或者它的状态机在扩容,在不同模块扩容的情况下,它们的调度因子几乎是一致的,在这种情况下就可以进行一个合并,这样5000到1万次请求,合并之后可能就降到几百次这样的规模,这种合并的效果是非常好的。
Q:任务多了,生成DAG流程图是不是非常耗时间?
A:其实DAG图不是动态生成的。而是静态生成的。我们一开始设计为了让一个任务得到简单处理,就是一步一步串行下去,当出现异常了,再一步一步的回退。当它不断去做优化功能的时候,步骤会变得越来越多,性能就会越来越慢。我们引入DAG,是让我们在设计一个流程的时候,我们的每一个步骤、每个模块都会去做一些参数声明,这样的话你去修改了这个步骤的时候,它就可以实时生成对应的一个DAG,生成完了之后就静态的存储在那里了,每一个任务它的DAG图几乎是一致的,并不是说一个任务对应的DAG图不一样,所以这里不会有非常大的耗时。