本期溪歪歪专栏将带大家一起探索,关于腾讯云弹性计算产品的技术设计要点。
在各行各业都一定程度上适用这句话:Those who talk don’t know, and those who know don’t talk. —— 而我相信,你终将成为那个懂得原理、能做成事还乐于分享的高手。
云计算底层离不开虚拟化技术,虚拟化让人们有安全感和幸福感,它解决了资源的安全隔离和高效利用两大问题。操作虚拟机,就像在泳道里游泳,因为有挡波阻浪的泳道线,我们无需关心旁边泳道里的人是何种泳姿翻腾出多大水花,而我们总是自由自在,仿佛拥有了整个泳池。
回到本文的主角:腾讯云弹性伸缩 AutoScaling,它是云上基础计算类产品的核心托管服务,能为用户高效管理云服务器集群的扩缩容活动。而这一切与周边相关产品的通力协作密不可分:
在相对底层,腾讯云基于 KVM 的虚拟化技术解决了单节点物理机上的虚拟机管理;在虚拟化之上,腾讯云的 VStation 调度系统完美解决了大规模分布式的物理机集群管理、虚拟机资源以及任务调度、动态迁移等核心问题,并直接支持了云服务器产品(CVM)的实现。
同时,CVM 也是其它产品如弹性伸缩 AutoScaling、轻量应用服务器 Lighthouse 、批量计算 Batch 等云上托管计算产品的坚实基础。弹性伸缩整合了云服务器、负载均衡 CLB、云监控等多项服务的核心能力,让用户方便地通过云API/控制台系统灵活地管理云服务器集群,共同为用户的业务动态扩缩容保驾护航。此外,弹性伸缩还是腾讯云容器服务 TKE 的基础支持。各个周边服务关系如下图所示:
腾讯云弹性伸缩 AutoScaling 及相关计算产品
VStation 作为计算产品 CVM 的底层分布式调度系统,其在资源管理维度上的实际效果是大大胜出彼时的 OpenStack 的。其中有若干原因,最重要体现在其组件间的通信方式上。二者虽然都用了消息队列,但 OpenStack 将其视为 RPC 的实现方式,各个组件彼此通讯效率低,而且还涉及服务注册发现等复杂的管理,如右图所示。而 VStation 的组件通信如左图所示,消息队列作为消息总线,而各个微服务组件只需要关心同 MQ 通信,而无需关注彼此的存在。VStation 消息总 vs.OpenStack 早期的 AllToAll 通讯与其说 VStation 是独辟蹊径的创新实践,不如说是 VStation 的作者对消息队列有着更加深刻的认知和理解,把握了消息队列的本质。腾讯云后续的产品等,也都在设计上不同程度借鉴了这种消息总线的模式。
弹性伸缩作为核心的云上计算类托管服务,其持续运转是否稳定高效,直接体现了云服务商的技术专业性水平,并会影响用户对云服务商产品的信任感。官网的AS简介可以概括为典型场景:削峰填谷——根据业务负载动态调整资源(主要是云服务器集群)大小,在优化资源成本的同时不失业务的高可用性(High Availibilitiy)。但其实对于集群的日常管理,通过弹性伸缩也是可以极大提升效率的,所以那么什么时候用AS呢,答案是:
“如果你的业务需要1台以上的云服务器,你就该考虑用AS了。”——溪歪歪
弹性伸缩主要解决了计算资源的横向/水平扩容场景下的问题(Scale-out),即通过增减云服务器实例数来动态调整系统的服务能力。以扩容举例,就像聚餐人数增加时,每类(热菜、甜点等)菜多上一份不同的,人再多时甚至加一个同样的整桌,而不是每个菜都来一个“大份的”,即通过实例副本和规模化来提升效率,降低综合成本。在真实场景中,业务架构是伴随业务发展而不断演变的。
腾讯云的云服务器 CVM 和弹性伸缩 AutoScaling 将陪伴用户业务发展的每一个阶段,一起见证用户的业务和架构的日新月异。当然,弹性伸缩服务本身也是持续续高速增长的业务:
腾讯云 2018-2019 弹性伸缩的扩缩容活动数量部分统计
弹性伸缩作为提供基础服务的计算类服务产品,其本身自诞生之初就一直高速增长。其典型重要客户如:超参数、小红书、VIPKid、作业帮、芒果TV、荔枝微课、Supercell、EpicGame 等,覆盖各个行业。它的用户量和伸缩活动数至今仍保持持续增长,已稳定支撑着用户上百万核数的集群资源管理。
那么,弹性伸缩到底为用户解决了什么本质问题?《The Art of Scalability》书中提到的扩展 cube(业务、副本、数据分片维度)广为人知,不过书中的左图提到的常见方法论或设计模式其实更加有指导意义,在稳定(高可用性)、快速上线(Time-to-Market)和成本节省这三大追求中的权衡其实才是工程中真正的艺术,而设计上的横向扩展(Scale Out)是同时解决这三个问题的为数不多的方法之一(另两个是异步设计和自动化)。弹性伸缩助力我们的业务实现稳、快、省。
Tips:真实的客观世界没有所谓同步,只有异步;没有更新,只有打破后的重建。所谓“同步更新”那只不过是人们的幻想。系统设计时试着多拥抱事件驱动 event-driven 和不可变性 immutability 吧,生活会简单不少。
弹性 Elasticity —— As the extension, so the force. 弹性就是物体受到外力时变形,并且当该外力解除时恢复其初始形状的能力。固体物体受到外力时将变形。如果材料是弹性的,当这些力被移除时,物体将返回到其初始形状。按广义胡克定律,应力(stress)大小和应变(strain)成正比。
弹性伸缩——甚至整个云服务——解决的最关键问题是:让业务稳稳地活下去,持续产生社会价值。实现时两个核心思想贯彻始终:
第一,有备无患 —— Design for failure, and nothing will fail.
第二,统一决策,灵活执行 —— 顶层指令在各个执行层级间“行政发包”。重点在于广义的机制(machanism)策略(strategy)分离,strategy可以是决策/策略/设计/调度/算法,mechanism可以是机制/执行/计算/IO/任务实现/服务等。
伸缩活动——保障业务架构弹力满满的“胶原蛋白”。弹性伸缩的伸缩活动包括扩容、缩容、不健康实例替换等等。弹性伸缩的核心就是伸缩活动的设计实现及其生命周期的管理。
做任何需要保证质量的工作都需要须形成一定惯例(Routine)流程,惯例一般是步骤(Step)的简单线性叠加,不过更复杂的需要工作流Workflow,串/并行甚至分支判断。
其实生活中,你或许每天都感受着流程,如简单的护肤流程:洁面乳 cleanser、爽肤水 toner、精华液 serum、润肤霜 moisturizer 和防晒霜 SPF,像这样“线性”的五步,每一步都依赖前一步的结束。不过,当你想更进一步提升魅力时,事情逐渐复杂起来:
第一,步骤会更多,如除了基本护肤还要粉底、遮瑕以及最后的定妆粉等;
第二,步骤间不完全依赖彼此的顺序执行,如睫毛、眼线、唇彩和轮廓修容等步骤的顺序,可自由发挥,按兴趣统筹调整,化妆师多的时候还可以并行;
等等,这看起来感觉像不像一个程序呢?的确。弹性伸缩的业务抽象完全可以类比化妆的流程,使用弹性伸缩也同样地可以让我们的架构更有活力和魅力,让我们的业务永葆青春。
伸缩活动的计算步骤复杂度如何?可以认为每个伸缩活动是由若干个子步骤 Step 组成的,往往每个 Step 都涉及至少一次的外部微服务或API调用,可以简单地理解为一次异步I/O任务,其中既有串行的,也有必须并行的,彼此的调用链路也由运行时的结果动态调整。
当然,真实的步骤已经是图中的若干倍,达到上百步,复杂度也更上了一个级别。步骤分为活动级别的步骤(下图蓝圈)和实例级别(下图绿圈)的步骤,如图所示。简单算下,如果100个伸缩活动,每个扩容100台云服务器的话,那么同一时刻(或极短时间内),按图中“最天真的”情况下,也要上万个I/O请求,实际中的步骤数量其实还要大两个数量级。
所以,伸缩活动,即弹性伸缩语境下的“执行任务”,其实现难度和复杂度是具备一定通用性的:即后台异步任务流程、多组件/微服务间彼此调用、业务相关的复杂分支逻辑判断、任务的多状态(异常/重试/取消)、相关涉及的元数据量大、用户任务间的上下文隔离,以及并发性能和稳定性都要求极高。
如果你做过后台的业务开发,上述情景和需求是否感觉亲切熟悉呢?可以思考下:这类任务是计算密集型(Compute Bound)还是I/O密集型(I/O Bound)呢?
为了实现统一的决策设计,通常需要任务流 WorkFlow 的步骤定义来完成。那么我们观察下真正的 Flow 是什么样子的呢?照片中,壮观的 Joekulgilkvísl 蜿蜒穿过冰岛高地,成辫状蛇行向前,流过两侧被积雪覆盖的 Rhyolite 流纹岩山脉:
冰岛峡谷中的 Joekulgikisl 河,真实世界的 Flow
- 现实的 Flow 不会逆流,永远向前,没有 rollback(回滚)
- 现实的 Flow 不是一条线,必有若干支流,而所谓的主流不过是最大(概率流经)的一支
- 现实的 Flow 中的路径中的每一个点,都是独一无二的,有自己的上下文。不可能两次踏入同一条河流
所以总结:用带 Context 的DAG(有向无环图)抽象 Flow。不要用类似 rollback/cleanup、retry、exception 这些概念来实现底层框架,这些复杂的概念可以有,不过最好放在更上层抽象。
另外,要摒弃朴素耿直的线性思维,相比成功(Success)或失败(Failure),关注「下一步去哪」Next 以及「干净地完成」 Done 更加重要。因为前二者可能都有很多情况,比如失败的原因和结果一定有多种,任务的成功或失败都只是 DAG 里的一条 Path 而已,没有本质差异。非黑即白的二元决策往往是极不成熟且适用范围狭窄的。
当然,如果之前熟悉线性的回滚思维方式,通过简单的转换,可以将流程归约到 DAG 形式的描述,如图:S2是S1的回滚清理步骤,S4是S3的回滚清理步骤,左侧是线性回滚模式,右侧是归约后的同构 DAG。
完成了统一的决策设计,再来看看灵活高效的执行。以下场景是人与人之间比较优雅的组织方式,管弦交响乐队的模式:
一个指挥,若干个乐器(弦乐、木管、铜管、打击)组,大家一起通力合作完成一次和谐的演奏流程。
对于整个乐团,指挥是大脑(CPU Bound),各个组的乐师是手脚(I/O Bound);对于指挥来说,他也有自己的大脑和挥舞指挥棒的双手。他们都是Actor,且互相有优雅的通讯方式。
指挥一个看起来是演奏得情绪节奏大脑,但他并没有直接下达命令到每个乐师,那么背后真正高效的组织力量又是什么呢?
下图是弹性伸缩的后台架构图简单示意:从 API 到任务调度器、定时任务触发器以及周边组件,都至关重要。对于伸缩活动的实际执行,其引擎组件在最后方,即图中红色的 Activator 服务组件,注意它同时也是一个 MQ 任务消费者,和你的业务中的通用消费者组件无太大差异。
放大上述的 Activator,我们看到大致如下的类似交响乐队的内部实现。其中红色的就是策略核心:伸缩活动步骤定义,即 WorkFlow 的定义,也是全部活动的“乐谱”。Activator 核心引擎负责伸缩活动的高效执行及生命周期的管理。可类比为:
- 进程 = 程序 + 虚拟机(解释器 + 运行时 + 库函数)
- 伸缩活动 = 步骤配置 + 执行框架(核心引擎 + 处理函数)
每个执行单元都是 Reactor 模型的异步事件处理器:上一层的“指挥”执行单元 Actor 查询工作流步骤表,并根据下一层的执行返回结果计算下一步的任务并分发给下层;下一层中的 Actor 通过总线和上层“指挥”执行单元沟通。
这里面有一个分形(Fractal)的设计考量,类似递归的感觉,这种分形树状结构是最自然也最高效的组织方式,尽管理论上可以无限层次的扩展,实践应用中一般四层以内足够了。实践上,可以考虑从协程/线程/进程/节点到微服务各级别来分别递进实现。上图只是单线程内的二层分型执行引擎的设计示例。
设计灵感源于:有限自动机、Actor 模型以及分形理论
系统设计实践先介绍到这里。关于性能,再补充两个让其质变的实现细节。
细节1:最小的执行单元(原子Actor)通过 eventfd 来进行彼此的消息通知,高效利用内核接口/资源,保证高性能。eventfd-with-epoll 可以保证单节点百万级的事件并发,极其适合这类高事件吞吐率的场景。
最小的分形 Actor 单元,基于 Linux event&epoll 高效实现
细节2:事务消息 Copy-on-Write 实现,通过 Immutability 节省空间同时保证通讯安全。注意,这里我们推荐用消息传递(Massage Passing)来实现内存共享(Memory Sharing),而不是相反。
重点无疑是策略机制分离(Separation mechanism from policy),它是处理统一策略和灵活执行之间的矛盾的最大前提和行之有效的方法。它也是操作系统里最重要的概念之一,如果你是技术人员应该不觉得太陌生。对于业务型后台计算逻辑,核心问题往往一般属于两大类:- 任务管理:难点在于执行调度、生命周期复杂状态、高并发、且动态决策
对于决策统一的诉求,在于可灵活设计组合、可靠可控,我们通过策略层采用 DAG 任务流张量编排来实现;
对于执行灵活的诉求,在于高性能和定制化,我们通过执行层采用分形 Reactor 逐层建模实现。
系统设计上,可以沿着这个思路进行优化:将需求问题转换为计算问题,再转换为I/O问题,再转换为组合设计问题,最终化为策略问题,我们的系统将因满足人的需求而愈发增值。
小结
本文所述的设计点及相关方法论已在腾讯云弹性伸缩 AutoScaling 、轻量应用服务器 Lighthouse 等多个产品中实践应用,不仅有效地支持了这些核心业务的快速迭代、改善了开发效率,更重要的是大家日常的代码撸得更开心了。如果你也有兴趣,欢迎一起来讨论吧~
参考资料
本文内容源于第十三届中国系统架构师大会 SACC2021 的直播分享《腾讯云弹性伸缩工程优化探秘技术》。
溪歪歪,2015年加入腾讯,专注云服务产品相关领域的技术探索。负责云服务器、弹性伸缩、轻量应用服务器、GPU 等产品的研发及运营工作。长期关注高性能集群管理、分布式任务调度系统、Web 全栈开发相关方向。