AMS是一个活动运营平台,活动指的是在AMS平台上所展现的四个页面内容。这些页面涵盖了各种不同类型的活动。也许有些读者会认为这些页面内容很简单,觉得这里似乎没有涉及太多技术含量。
确实,如果只看这几个页面的内容,可能会给人简单的印象。但是,当这些活动在比较大的流量规模下进行时,例如每秒处理15万个活动,就会面临具有挑战性的问题。在如此高的流量下,运营平台需要应对复杂的技术挑战,确保高效稳定地展示和处理活动内容。
简单介绍一下 AMS,其实是QQ会员的活动运营平台,这个平台的日请求量大概是4到12亿,背后涉及的存储和服务超过了120个,高峰期的请求量是15万每秒。
这个系统也支撑了腾讯公司相关的业务,我们连续三年参加了QQ春节红包的活动。
关于这个活动,将会面临三个主要方面的挑战。
首先,流量规模的预估是一个关键问题。我们需要准确地估计活动期间的流量量,可能是10万、20万,甚至更多。预估流量规模是一项复杂的任务,需要考虑多种因素,并做出合理的推测。这个挑战需要我们运用有效的数据分析和预测技术,以应对未知的流量峰值。
其次,对于一个复杂的系统,其中涉及大量系统之间的调用和相互关联,构建整个系统的架构链也是一个难点。即使我们预估了流量规模,但要对架构链进行合适的扩容也是具有挑战性的。有时候,我们可能会陷入无从下手的困境。这就要求我们在系统设计和架构方面做出仔细规划,以便应对可能出现的高负载和压力。
最后,即使前面两个问题都解决了,如何保证活动当天不出问题也是一个关键考量。在活动当天,任何系统故障或瓶颈都可能对用户体验产生负面影响,甚至导致系统崩溃。因此,我们需要在运维和监控方面做好充分准备,及时发现并解决潜在的问题,以确保活动的顺利进行。
第一个问题是关于如何评估流量链路的问题,第一步就需要把系统架构给画出来,以下是简化图,先将用户跟每个子系统的关系梳理出来。
上面这个图主要分为两个部分,一部分是请求领取页面,后面是比较复杂的动态系统。画出这个东西是为了下面一步,就是以具体功能的节点,把用户涉及的所有链路一个一个梳理并且画出来。
比如以查询游戏角色的信息相关的东西为链路,用户首先是领到一个红包,点击领取红包到领取页,在页面可能会是点击一个按钮,然后发送请求,请求先到我们的 STGW(也就是反向代理),然后到 WEB server,再到 Cmem,再到 IDIP 以及背后的庞大的链路。
通过这种方式可以把每个功能点进行拆解和梳理,当我们得到这个图以后,如果只扩容 web server,不扩容后面的每个链路每个环节,扩容是不成立的。
有一个观点是只要有钱购买足够的机器,可以把对机器的方式对接下来,但是这个观点是不准确的。如果不能把每个架构做充分梳理和扩容的话,涉及到状态有关的服务是无法进行扩容的。
怎么评估系统在活动当天的真实流量规模?
首先,要考虑的是流量推广问题。流量的来源有多种,一些来自于在网页上的广告投放,另一些来自于移动应用程序。我们可以获取每个流量来源的数量,然后计算每个环节的转化率,即在一百个人中,有多少人会点击广告。
假设转化率是20%,那么如何确定每个环节的转化率呢?一种方法是依靠经验,回顾过去在这个平台上的广告转化率。如果缺乏环节转化率的数据,可以通过试验进行演练。可能在活动当天,流量会非常庞大,但可以先进行一部分灰度测试,逐步引流。
在一百个用户中,大约有二十个人会点击进入领取页面。关于领取页面的设计,需要考虑到每个页面的单UV(Unique Visitor,独立访客)的概念。一个用户到达页面可能会发起三到四个静态资源请求和三到四个动态相关资源的请求。因此,需要将产生的行为形成一个倍数,并将其估算为3倍。
举个例子来说明:假设有一百个用户每秒领取游戏红包,但在这一百人中,只有60人点击进来,而在点击页面的人中,只有30人真的会去点击领取按钮。通过链路分析计算出UV值,到这一步需要进行转换,因为单UV的总数可能是四个请求,如果假设是四个请求,那么每秒应该是120个。这意味着背后的所有链路都必须支撑每秒120个请求,所以整个后面的环节都应该设计成每秒120个请求的处理能力。
通过之前推广曝光数量的几个环节,最后可以推算出预估值的QPS(Query per Second,每秒查询数)压力是多少。根据多年的扩容公式,3倍的估值是相当合理的。因此,我们需要将几个相应环节的处理能力180乘以3倍,即扩容到360。
以同样的方式,我们在流量层面也进行了相关扩容方面的探测和预估,但是在真实场景中,业务究竟需要多少流量呢?
我们需要应对9.6万每秒的流量,近似于10万每秒。从数字上看,将每个环节扩容到10万每秒似乎能解决问题,听起来很简单。然而,大家都知道,理想很美好,现实却常常残酷。是否每个系统都能轻松地扩容到10万每秒呢?
我们面临一些难题,发现在整个链路中,像 web server 这样的系统很轻松地扩容到14万每秒,但在其他地方却做不到。有些环节涉及第三方不可控的因素,例如与外部公司的通讯结合,或者涉及跨部门或跨业务组(BG)的情况,这些系统很难进行扩容。在扩容方面,我们遇到了难题。
举例来说,像银行发账或对账的接口,如果今天要将其扩容20倍,这是不太现实的。在这种情况下,我们可能会在数量上进行了估算,但实际的扩容却面临困难。那么,我们该如何解决这个问题呢?
这就引出了第二个话题,既然我们已经预估了数目,但又无法进行扩容,这时就涉及到高可用架构的设计实践。
怎么从架构层面解决问题?一般分为三个方面:
第一,异步化: 在遇到真的无法扩容的接口或者与外部公司不可控的情况下,对方也不可能配合你来扩容的接口时,唯有采用异步化的策略。这意味着,生产端可以快速地生产数据,但通过一种生产方式来支持10万每秒的接口。然而,在消费端,我们将使用异步接口来逐步消费数据,从而实现适应性。
第二,缓存模式: 采用所谓的空间换时间策略。举例来说,如果今年遇到需要查询某用户是否是王者荣耀玩家的需求,而这样高频率的查询无法支持几万每秒,该怎么办?虽然听起来有些笨拙,但解决方法是将数亿的王者荣耀玩家名单缓存在内存中。这样,当有用户来查询时,我们可以在我们的系统中进行判断,而不是再走游戏接口。
第三,服务降级: 适用于对你的主要逻辑不产生重大影响的模块,例如实时上报。如果这些任务并不是十分重要,是否可以放弃或者在逻辑上进行优化?当某些环节无法支持10万每秒的链路时,我们进行了改变和扩容。对于一些无法支持且非必要的模块,我们进行了服务降级。将整个链路从原来的方式拆分为两条,上面的链路可以支持10万每秒,而下面的链路则不行。但通过这种方式,我们确保链路仍然可以正常运行。我从可用性的架构方面解决了一些环节和内容无法扩容的问题。
接下来,我们需要考虑与扩容评估相关的要素。这些要素不仅仅局限于上述几个点,如果进行详细细分,有许多细节要考虑。但主要的评估点包括链路的 QPS,以及存储方面的一些考虑,例如磁盘大小、内存大小是否足够,以及带宽的情况。带宽问题涉及到静态文件和动态请求,都需要进行适当的计算。
其中另一个要考虑的因素是如何解决静态资源的问题。实际上,当一个用户进入一个页面时,静态资源的请求量是很大的。例如,进入一个页面可能同时拉取多个图片,这样的请求量通常会是UV的两三万QPS,但往往需要乘以七八个甚至更多的倍数。
在这种情况下,即使使用CDN(Content Delivery Network,内容分发网络)进行部署,也会对服务造成压力,特别是对带宽的需求。由于某些带宽可能不足,可能导致请求的响应更加缓慢。
那么我们该如何解决这个问题呢?解决方案是采用离线包的机制。我们会先准备好静态资源,并在用户的手机在WIFI情况下,提前将可能用到的春节红包活动的图片等资源拉取下来,放到用户的内置浏览器中。
当真正参加活动时,几乎所有的静态文件都将使用用户本地的资源,相当于根本不需要发起网络请求。虽然有一小部分用户从未在WIFI情况下触发过拉取逻辑,但这样的用户是少数。因此,绝大部分用户都通过本地拉取的方式来获得所需资源。这样,我们能够有效解决静态资源带来的流量压力问题。
现在每个环节的流量处理方案已经确定,也完成了扩容的实施。然而,仅仅完成扩容还不足以确保系统没有问题,还需要关注许多要点。
过载保护: 第一个要点是过载保护。无论流量在真正的活动日有多大,保证系统不能挂掉是最重要的。系统的崩溃将是最严重的失误,因此我们可以容忍拒绝一部分请求,但决不能容忍系统无法提供服务。
柔性可用: 第二是柔性可用,这与业务紧密相关。在活动中,有一些逻辑需要判断用户是新用户还是老用户,然后给予相应的奖励包。在这方面,我们可以实现柔性可用性,例如,如果某个接口出现问题导致无法判断用户身份,我们可以采取容错措施,将所有用户都当作新用户处理。这需要业务和产品方面的配合,以保证出现问题的接口在业务逻辑上兼容。
容灾建设: 另一个重要的要点是容灾建设,它分为两个部分。一部分是业务主干逻辑上的容灾,另一部分是针对天灾人祸式的容灾。在部署层面,我们需要对轻重要任务进行隔离,确保重要任务能够继续运行。
监控体系和数据对账: 最后两点是最容易被忽略的,但同样至关重要,那就是监控体系和数据对账。建立完善的监控体系能够实时监控系统运行状态,及时发现异常并采取措施。而数据对账则确保数据在传输和处理过程中的准确性和完整性,避免出现潜在的数据错误问题。这两点的重要性不容忽视,对于确保系统的可靠性和稳定性起着至关重要的作用。
首先是过载保护,在每个环节中建立层层控制开关和流量控制。
推广层: 第一个是推广层。如果系统出现问题,原本处理8万每秒的广告请求可能会卡掉一半,而在情况非常糟糕的情况下,甚至会停止广告投放,以给系统喘息的时间。这种控制开关通常在确实无法承受更多流量时建议使用。
前端层: 第二个流量保护是有意识地延长用户请求的处理时间。在用户点击后,我们可以给它随机等待两秒的时间,或者在两分钟内不允许再次请求后台服务器。这样的功能对于错峰流量非常有帮助,如果不进行用户等待时间,系统的尖峰流量可能会非常尖锐。
CGI层: 另一个保护层是CGI层。当处理压力过大时,它可以过滤掉一部分请求,同时还可以进行流量控制,以保护后端接口。
后端服务层: 在后端服务层,对外部第三方接口的QPS进行控制也是十分重要的。如果外部接口本身每秒只能处理1000次请求,我们就可以设置一个控制机制,确保不超过1000次每秒的请求量,避免超负荷压力。
综上所述,这种层层开关的设置能够确保系统全面准备就绪,使得在活动当天随时随地开启使用。这样的设计可以保证系统在部分不可用的情况下,至少比彻底不可用时的情况更为稳健。
关于轻重分离,核心点就像发货请求,也就是核心请求,不希望被任何其他集群所影响到。轻重的概念只有在具体业务场景里才有意义。
举例来说,查询用户的XX信息这类请求通常是比较轻量级的。即使查询失败或者出现错误,用户可以重新查询或重试,不会造成重大损失。然而,这种请求需要使用专门的资源来保障其正常运行。
在粗略的架构图中,我们将系统进行拆分,确保核心请求的处理不会受到其他部分的影响。这样的部署能够保证在不影响背后发货流程的情况下,专注处理核心请求。通过这种轻重分离的策略,我们能够确保核心功能的稳定性和可靠性,提升用户体验和服务质量。
柔性可用的关键在于随时准备放弃一些非关键的路径,包括上报或者不重要的操作,甚至在业务层面也可能存在这种情况。在实现柔性可用性时,通常有两种方法:
第一种方法是设置一个超短的耗时时间。例如,平时请求只需五六毫秒完成,但我们将设置为二十毫秒。这样,即使连接不上或者请求失败,我们也只会等待最多20毫秒。这是一种常用的方式,对于非关键路径而言,不挂掉是好事,继续为我们提供服务,挂掉了就忽略它。
第二种做法是使用UDP(User Datagram Protocol)。UDP在日志记录中经常被使用,比如QQ钱包的卡就是采用UDP方式。UDP更加直白,我们把数据包扔给你,也不等待你的回包,如果这个包挂掉了,我们也不会去关心。通过这些柔性开关的设计,使得逻辑能够按照业务预期继续往下走。例如,我们通常会先查白名单,然后根据结果进行业务逻辑,如果某个步骤挂了直接跳过,这是我们的操作方式。
通过这样的柔性可用性设计,我们能够在系统出现问题或非关键路径不可用时,保持系统的稳定性,继续为用户提供服务,并确保业务逻辑按预期执行。
日常灾备: 对于异步消息队列服务,我们需要考虑各种假设,包括每个核心服务挂掉的情况,以及如何保障服务在挂掉后能够有另外的机制支持。为了解决这个问题,我们采取了本地磁盘写入的方式,将消息队列以本地磁盘的形式进行保存,这样即使服务挂掉,后续可以通过磁盘日志进行补发。同时,为了保障日志的完整性,我们进行了冗余处理,将日志冗余存储在三个不同的服务中,包括一个磁盘服务器服务、一个Kafka服务,以及公司自研的罗盘数据中心。如此一来,即使其中一个服务出现问题,我们可以使用其他两个进行核对和恢复。
天灾型灾备: 另一方面的容灾建设是针对天灾型的情况,例如机房停电、网络故障等。在这方面,我们与运维同学紧密配合,进行跨地方、跨网关的部署,以避免偶发事件对服务造成影响。
预案一键开关: 除了上述灾备措施,我们还制定了各种预案。然而,为了应对活动当天紧张的情况,所有的预案必须变成一键开关的形式。即使在紧急情况下,我们只需按下相应的按钮,即可执行相应的预案,而不需要花费大量时间来编写和修改代码。这些预案要变成简便的手册,根据不同情况点选相应按钮即可实现简单操作,只有这样,我们才能在有限的时间内迅速做出反应。
监控是多维度的,包括前端返回码的成功率、流量波动、礼包的发货量等,需要按不同维度监控整体数量。消息队列的堆积情况、每个环节的消耗情况等也要按不同维度进行监控。只有多维度的监控,才能及时发现异常情况,避免在流量激增时失去监控的视野。如果只有一个监控维度,一旦出现问题,就无法了解系统处于什么状态。
在制定预案时,我们需要基于充分的监控数据和对当前系统情况的充分了解做出正确的决策。如果没有足够的监控数据来辅助预案启动或不启动,那么做出的决策可能是盲目的,这是非常危险的。因此,监控数据的重要性不容忽视。
为了方便数据汇总和基于数据做预案决策,我们需要建立自动对账和补发脚本。这样可以尽早记录相关数据,从源头开始记录,并在最终到账时再次记录,以确保整个过程中的数据完整性。这对于处理部分用户发生发货失败等问题非常重要,尤其在极端情况下,可能连日志都来不及打出来。通过对账脚本进行数据补发,我们能够有效解决这类问题。
要注意,我们的系统平时流量较为正常,但在极端情况下可能会出现意想不到的场景。甚至在关键模块挂掉、影响了几分钟的情况下,这几分钟的数据仍然可以通过对账脚本进行补发。因此,对账也是非常重要的一环,尽管它相对容易被忽略。
经过业务链路架构的梳理,我们对系统进行了多项改进,使得架构在理论上能够扩容到可以支持所需的量级。然而,在活动当天是否出现问题呢?
在实际执行中,我们确实遇到了几个问题。首先是流量超出了预期,我们之前评估为每秒9.6万的流量,为了省机器将扩容至14万每秒,但实际均值达到了12.6万每秒,峰值甚至达到了20万每秒,导致机器无法承受如此高的负载。
实时上报服务器挂掉了,存储服务的带宽也跑满了,消息队列严重堆积,最高时堆积量甚至达到了1200万。这些问题导致发货速度变得异常缓慢,问题层出不穷。在如此大规模的活动中,出现问题是很正常的,实际上,能找到没有出现问题的大型活动案例并不容易。
系统目前的状况十分严峻,web服务器负载极高,存储服务带宽已经跑满,实时上报服务已经挂掉,数据严重堆积。面对这种情况,一些人或许会觉得我们处于非常紧张的状态,但事实上,我们之前已经准备了十几二十个预案,用于应对各种问题。因此,并不会感到过于慌乱,我们会根据实际情况迅速启用相应的预案来解决问题。
刚才提到的问题,几乎都在我们的预案中。我们对这些问题做出了相应的应对措施:
流量超出预期:我们针对非关键请求采取了取样策略,不让其上报,从而将流量降回正常水平。
实时服务器挂掉:尽管对产品团队影响较大,但对用户参与活动流程并无影响。我们正在努力解决该问题,以确保产品团队能够获得真实的发货数据。
Cmem带宽跑满:虽然此问题未被预料,但我们的运维同学迅速准备了万兆网卡,解决了带宽问题。
消息堆积严重:我们提前考虑到可能出现堆积问题,采取了长期保存方案和扩容特大磁盘的措施,确保数据写入持续进行,并对整体业务效果没有大的影响。
补货失败问题:我们通过对账补账的方式解决了链路中发生补货失败的情况,保障了发货的准确性。
在总结前面的工作时,我们认识到这些预案的重要性。虽然准备这些预案可能会消耗大量时间和工作量,但是在遇到问题时,它们真的派上了用场,拯救了我们的系统。
我们的团队希望这些预案都不需要实际使用,这意味着我们的系统完全符合预期地在线上运行。尽管预案准备过程可能辛苦,但只要用上一两个预案,就已经起到了救命的作用。如果没有这些预案,遇到前面的四个问题,我们可能会面临严重后果。
因此,虽然我们希望这些预案用不上,但它们在危机时刻确实是至关重要的。我们珍视这些预案的价值,它们是我们团队应对各种挑战的有效工具。
在总结这些问题时,我们可以归纳如下:
流量超预期:我们低估了QQ钱包的流量,导致实际流量远超预估。
业务集群隔离和分业务预估不充分:我们没有做好业务集群的隔离,导致QQ钱包的高负载影响了整个系统,而不仅仅是红包集群。
扩容策略不够充分:尽管常说三倍扩容原则,但由于为了节省机器,我们最终只扩容到14.6万每秒,远低于理论安全范围的30万每秒。
高负载场景:由于流量超出了预期且扩容量级不足,导致出现高负载场景。
因此,我们需要严格遵守一些原则,不可抱有侥幸心理,这一点我需要进行自我检讨。
另外,关于实时服务器挂掉的问题,我们应该早期考虑并纳入监控和产品业务数据的考量。
最后,带宽被跑满也是我们梳理不到位的问题。如果我们更加详细地梳理流量和带宽情况,做好预估,就能避免这样的情况。在梳理流量时,要特别关注一些静态资源,如图片等,而不仅仅局限在系统的吞吐量。
在图中我们可以看到外网入网流量量级为5.1G每秒,这并不是图片等静态资源的流量,而是前端普通HTTP包头的流量。这样的请求量达到5.1G每秒,也间接反映了当天系统的峰值流量非常高。
总的来说,我们应该更加谨慎地做流量预估和带宽梳理,以确保我们的系统能够应对高峰时刻,避免出现不必要的问题。同时,我们也要不断总结经验,加强对监控数据和业务数据的关注,以及完善预案准备,以提升系统的稳定性和可用性。
本文围绕了三个主题进行了分享,现在做个总结。
在构建高可用系统时,首先需要对系统架构进行仔细梳理,将整个系统架构以图形方式展示,尽可能细致地绘制每个环节和子系统的关系。基于架构图,进行功能点的链路梳理,确定每个领取功能涉及的环节,查看是否存在重叠,全面计算流量压力来源。
流量压力可以来自不同渠道,例如广告曝光、APP登录等。通过历史数据和演练得出的数字进行综合计算,可以得出流量的压力情况。根据流量预估结果,对系统架构进行扩容评估。然而,在扩容评估中可能会遇到与状态相关或第三方不可控的系统,无法直接进行平行扩容。对于这些系统,需要进行合适的改造,遵循前面探讨的几个原则,例如异步化、缓存模式、服务降级等,并增强其可用性和过载保护等方面。
最后,非常重要的一点是梳理紧急预案。在紧急预案中可能会有多个方案,需要制定表格并设置开关,使值班人员熟悉表格内容,能够迅速应对各种问题。当系统构建和预案准备完成后,系统就可以真正面对大流量的服务。通过这样的流程,我们可以建设出高可用的系统,应对大流量场景,保障系统的稳定性和可用性。
作者:徐汉彬,最初发表于 2017 年