绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
应对节日高峰-Web架构实践
2020-05-20 15:45:07
2016ArchSummit《全球架构师峰会》的分享技术演讲实录文章,于ArchSummit公众号:

如何应对节日高峰-QQ会员活动运营平台的架构实践

演讲实录内容:

今天要分享的主题是关于QQ会员活动运营平台的架构实践。首先做一个简单的自我介绍,我叫徐汉彬,现在在腾讯的SNG增值产品部工作,主要负责QQ会员生活特权以及今天分享的AMS系统的研发建设。

今天我要分享的内容主要分为三部分:

  • QQ增值业务在海量请求下的技术挑战以及背景;
  • Web系统高并发场景的综合优化策略;
  • 平台高可用的建设实践。

活动有很多特性,今天的主题主要关注点是在节假日高流量的推广,例如五一是典型的节假日,各个业务都有他们的推广需求,他们聚集在一起就会导致流量的突增。有的同学可能会有疑问:我看你PPT上放的几个活动页面都非常简单,你今天讲的这些AMS系统会不会没有什么技术含量?是的,如果我们的系统上只放这几个活动页面确实没有什么技术含量,但是如果把这几个活动换成800+个,即我们的系统同时在线的活动有800+个呢?那么它就是一个相对来说更具有挑战性的场景。

我们这个系统叫做QQ会员活动运营平台,在内部简称AMS,主要承载QQ增值运营业务的Web系统,它有两点定位:

  • 满足QQ增值活动业务需求的发展;
  • 保证平台在海量用户面前的高可用,也就是稳定性。

我们每天由用户触发的Web层CGI请求有3亿-8亿,同时在线的活动有800+个(每个月新上线的活动大概450+个,活动的在线周期通常是1个月,不断有活动上线和下线,所以同时在线的是这个数目),我们这个系统背后涉及的存储和Server超过100个,在典型节假日高峰期的请求量为7w/s。

AMS系统涵盖了很多运营业务,包括QQ、腾讯游戏、个性化(表情)以及动漫阅读等。举一个例子:大家参加的2016年QQ春节红包活动,就是除夕下午抢红包,当时AMS系统就承载了红包活动的游戏礼包和阅读礼包的发放,因此,春节过年我不能回家,在公司值班到第二天的凌晨3点,也就是大年初一的凌晨3点,所以做这个系统有时候也是不容易的。


Web系统高并发场景的综合优化策略

关于Web系统高并发的综合优化策略。我们先聚集到一个指标上——吞吐率。我们的吞吐量主要分为三个方面:

  1. 请求延时:用户请求CGI的响应耗时,举个例子,假设我们的CGI平均耗时是是200毫秒,我想办法把它优化到100毫秒,那么相同的单位时间里面系统的吞吐能力就提升了一倍;
  2. 单机性能:我们期望通过更少的CPU、内存和系统开销支撑更高的并发数和处理更多的用户请求;
  3. 规模:即机器越多,我能支撑的请求就越多,这里面需要在整体系统架构上支持全面的平行扩容能力。

针对上述吞吐量的三个方面,我们对应提了以下解决方案:

一、降低CGI请求延时

天下武功唯快不破,跟快相对的就是慢,慢是一个怎样的行为?它的本质其实就是“等待”。假设我们MySQL在处理一个复杂的查询,它比较慢没有办法响应给后端Server,这个过程中究竟发生了什么事?我们从整个链路看,首先是浏览器端用户发起了连接,它在等待服务器响应(对我们来说它在等待我们反向代理给它响应),反向代理在等待Web Server,Web Server在等待Server层,Server层在等待MySQL,当然这种链路真正在集群系统里远不止这么短,它很可能是一个更长的链路。整个链路所有的环节都在等待,它们等待的过程就需要付出系统的开销和资源

有些同学可能会提出疑问,Server实现异步化不就行了吗?其实在我们的后台系统里大部分Server都已经实现了异步化,我们是采用协程(微线程)来实现。实际上异步化的过程:程序在处理A任务,A任务遇到网络I/O等待时程序迅速切到B任务,但A任务的现场必须得保留,那么A任务所占据的内存、数据、句柄连接和系统开销资源都不能立刻释放出来,都必须保留等到下次继续使用,也就是机器的资源并没有真正释放出来,整条链路不管是同步等待还是异步它都没有被释放,所以我们可以下一个比较小的结论:等待的过程就是对资源占据浪费的过程。

我们可以从三个方面进行优化:

  • 多级缓存和主动推送;
  • 既然我们知道等待是一个不好的过程,那么我们需要合适的超时时间设置;
  • 非核心操作异步化,尽可能把CGI响应时间降低,把一条长的链路尽快释放出来给其它请求使用。

接下来仔细讲讲这三方面:

,多级缓存和主动推送

缓存是一个好东西,缓存的本质是让用户离我们的数据端更近,例如浏览器本地的Cache、Server和Server之间内存Cache等等,Cache是非常经典的优化策略,被称之为万金油,通常是哪里不舒服就抹哪里,而且效果还不错。

但是,它在活动场景里就比较不一样了,比如一个新活动,在这个活动上线前所有人都没有参加过这个活动,整个Cache链路从前端到后端没有地方会有Cache,这里面我们对新的活动进行大规模推广会遇到一个问题:缓存穿透,我们的缓存策略是无法直接应对的。

那怎么办呢?我们采取的办法是主动推送,以2016年春节抢红包活动为例,当时高峰高达十万级每秒,对于十万级每秒的页面请求即使对CDN地理分布式静态文件服务都是有冲击的,我们真正的做法,其实是提前几天把这些静态CSS、JS和图片等资源推送到用户手机终端,当用户真正参加活动的时候就相当于有本地缓存,此时没有产生网络请求。

实现过程粗略概括大概是这样:我们向离线包管理系统申请一个BID对应的需要推送的离线文件,然后把BID写到URL的参数里面去,我们手Q终端的WebView会拦截这个URL请求,发现有BID就会根据BID找到本地的离线文件,然后把它直接给WebView。通过这种方式避免了网络请求,只有真正找不到的本地离线文件时才会通过网络请求CDN,我们利用这种方式解决静态文件的流量冲击问题。

那么动态的文件呢?我们这样做:在AMS系统早期我们Server里数据保存在MySQL里,MySQL在这种大流量并发冲击下通常支撑力是不够的,怎么办?我们通过数据同步的系统,不断地把这些数据从MySQL(我们认为比较弱的存储)同步到内存级的Cache服务(实际上也是一个存储)上去,包括还有另外一些能在前端直接展示的内容(比如一个活动的提示语)直接通过这个系统打包成静态JSON文件,再把静态JSON文件通过CDN分发出去,简而言之:就是把支撑力弱的东西通过这种推送机制放到强的服务里

可能从PPT上看,这里还不够形象,但是我们把这个蒙层一加后很多同学就看得非常直观,这就是非常经典的Server层和MySQL层之间引进的内存的Cache的模式,只不过我们的内存Cache层的实现稍微复杂一点,有一个推送和同步数据的机制。

第二,超时时间设置

关于超时时间的合适设置,很多人认为超时等待时间过长不好,所以干脆直接设置短一点的超时时间。对于一般业务可以这样做,但对活动运营系统比较不合适,因为,活动运营系统是接入多方业务的系统。比如我们接入的业务组件超过800个,仅游戏就接入了160多款游戏,每一款游戏都是一个独立并且复杂的外部服务Server,每一个服务Server的性能和响应时间都是参差不齐的,我们对应的通信接口数以千计,这时候就很难通过一刀切的方式来设置这个超时时间。

超时时间如果设置太长会有什么问题呢?假设有个服务流量比较大,如果你设置6秒并且该服务发生超时,就会发生一种情况:整个链路的系统资源在整整6秒的时候只处理了一个失败请求,原本可以处理几十个请求,但是现在整整6秒只处理一个失败请求,并且这个失败请求还会引起用户的重试。

为了避免这个问题应该怎么做?我们的做法是:因材施教,快慢分离,例如我们设置天天酷跑平均响应时间为100毫秒,那我设置为1秒的超时时间就够了;例如某款新游戏平均需要700毫秒的响应时间,我们认为把它设置为5秒超时时间比较合理。我们通过这种方式动态的超时时间设置以及快慢分离的方法把它们隔离开来,终使得整个系统的吞吐率不容易出现由于某个服务超时导致大量可控资源被占据的场景出现。

第三、非核心操作异步化

非核心操作异步化,该优化方式比较常规,就不展开详细的讲述。简而言之,就是将非核心操作直接提交到异步队列中,不等待响应结果,目的是尽可能把CGI响应时间降低,把一条很长的链路尽快释放出来给其它请求使用。

CGI延时优化成果

下图是我们做到的优化成果,大家可以看到在我们的AMS系统主框架逻辑内部耗时在CGI层大概只需要35毫秒,这35毫秒我们大概处理了将近10个流程,包括登陆态校验、活动配置读取、Session等安全检测的流程,当然可以看到平均的耗时还是需要100多毫秒,但是这100多毫秒主要耗时在于不可控的外部第三方,比如说我请求一个和我们合作的游戏方的Server,它的耗时我们是不可控的,在可控的范围我们通过CGI的延时优化把它优化到35毫秒。


二、提升Web服务单机性能

因为我们是活动运营系统,考虑到活动运营系统的灵活多变的特点,它比较适合用PHP开发,因此业务逻辑实现主要采用了PHP编写。然而,随着AMS系统规模的逐步扩大,日请求规模从2012年的百万级一直增长到现在的8亿级别,WebServer和PHP的性能不足问题日益突出。

基础软件服务的升级,通常是一件吃力不讨好的事情,因为如果做得好,活动业务侧不一定能感知得到,但是,如果升级搞出问题,则要承担比较严重的后果。而且,AMS系统上很多都是营收活动,和金钱收入直接挂钩,对这个系统做基础升级,并不是一件轻松的事情,勇气和安全风险可控的升级策略,缺一不可。

在WebServer的性能优化方面,我们考虑了三个方案。我们终都没有采纳HHVM和Node升级安全,没有采纳的原因是基础服务的升级是需要兼顾业务场景和投入产出比的考虑

首先是HHVM方案和NodeJS方案都是因为迁移成本太高,前面提到我们的Server接入的服务非常多,我们PHP代码有四十多万行,比较多的业务PHP扩展和组件,这里的兼容性迁移是大成本,并且,迁移风险也比较大;

PHP7+Apache2.4(Event)的升级方案是比较平滑的,因为这个方案即可以兼容业务代码,又可以比较可观的提升单机性能和Web Server并发能力。

我们终选择的升级方案是:Apache2.0升级到2.4的Event模式PHP5.2升级到PHP7.0。有必要简单介绍下我们以前使用的是老Apache的Prefork模式,这里粗略地提下Prefork和Event的两点区别:

MPM模式:Prefork是多进程模式,一个任务对接一个服务进程;Event则是多进程多线程模式,会启数量比较少的进程,每个进程会有几十个线程;Event是出一个线程来处理任务,线程通常比进程更轻量,这样可以让我们并发数更高;

长连接(keep-alive)问题:我们很多Web服务都会开启HTTP长连接,用于减少HTTP连接的建立和断开的系统开销。长连接,通常在刚开始肯定会频繁,但通讯完后它会被保持一段时间,保持期间Perfork模式的服务进程会被占据,除了等什么都不能做,直到长连接断开为止,这是一种系统资源的浪费;在Event模式下,它解决了这个问题,它用了专门的线程来保持这些长连接,当用户真正触发请求的时候,它再把请求给到后端的工作线程,工作线程处理完就把自己释放出来避免被占据,然后工作线程就可以继续为其他请求提供服务。

PHP7同比以前的版本区别主要是大的性能优化,对CPU和内存资源方面的占用比以前更少。

在这个升级过程中有遇到什么问题?

  • 首先我们的版本跨度比较大,Apache2.0升级到Apache2.4,PHP5.2升级到PHP7,我们真正的升级如果一步到位会比较危险,所以我们先升级到过渡版本,PHP方面我们先升级到PHP5.6(另外我们是去年实施升级的,当时PHP7的正式版还没有发布);
  • 除此之外我们还要解决语法兼容性、解决线程安全的问题(以前多进程是不需要考虑线程安全);
  • 有一些扩展要同步升级等等;
  • 比较系统的控制风险策略:逐步升级、灰度观察、现网PHP7运维和监控工具支持等。

我们大概在2016年4月底的时候进行了单机灰度,5月初在单集群全量发布,在日请求亿级的Web系统中,在国内属于比较早升级到PHP7的。PHP7的AMS对比老AMS,从业务压测结果来看大概有3倍的性能提升。从线上的CPU数据来看,我们实现了用更少的资源来支撑更高的并发、处理更多请求的目标。例如,以前一台普通硬件配置的机器启动500个进程,机器这时已经运作地比较满,但我们现在已经可以启到上千个线程。


三、关于规模——支持快速平行扩容

我们必须实现快速扩容与缩容。扩容这个行为本身在我们公司有丰富的运维工具支持,机器的安装、部署、启动等各方面都是高度自动化完成的。但是,以前我们扩容是依然要花一天多的时间,为什么?还是因为我们是活动运营系统,活动运营系统背后对接了很多外部发货接口,这些发货接口中有很多是比较敏感的发货服务,例如发Q币,还有发一些游戏高价值的道具。

一般我们和敏感业务服务通信有包含两步:加密签名校验和来源IP限制,每次扩容都需要新增IP,然后这些IP需要向各个敏感业务提出申请,让对方的业务领导进行人工审批,加到来源IP限制的白名单中,才能生效。因此,我们大部分时间都花费在权限审批上,用一句话来总结问题就是:不是在审批中,就是在去往审批的路上。

我们的解决方案是通过搭建一个中转Proxy Server把通信的IP进行收拢,收拢为中间的Proxy角色。我们内部再重新跟自己的Proxy实行签名校验和来源IP限制,只要我们的Proxy Server不进行扩容,我们就不需要重新申请IP权限审批。简而言之,我们把一些外部的授权变为内部授权,内部授权尽量做成自动授权,以及会把一些中间的验证的过程尽可能做到自动化验证。我们的扩容时间从原来的一天多缩小到1-2个小时,其中的关键点是大幅度减少人工依赖

第二个问题是机器持有成本的问题,活动运营业务因为严重受到节假日效应的影响,是个流量上串下跳的典型业务。因此,如果我们部署机器太多,平时会利用率不足而导致机器低负载,运维团队会挑战我们的机器成本和预算,说我们占据那么多资源会浪费;如果部署的机器太少,我们在节假日又支撑不住,瞬间七八倍峰值的增长风险又很大。怎么解决机器占有的问题?虽然我们具备快速的扩容和缩容能力,但是在一般情况下,我们也不希望天天变更我们的现网环境,通常我们希望我们现网环境能做一个安静的美男子,没有什么事大家别去随便变更它。

于是,我们利用了运维团队提供的Linux Container虚拟化技术,主要是共享CPU资源支持。例如,一台24核的物理母机,上面分成8台虚拟子机,虚拟子机上进行业务混合部署(不同业务各自占据一台子机),AMS也只占据一子机器,平时非节假日我们具备高优先级使用1/8的CPU资源,但平时可能用不到,到节假日的时候1/8的CPU资源不够,我们就把其它业务的空闲CPU资源拿过来用,就可以突破1/8的CPU的使用限制。实际上,这种CPU共享的技术方案,我感觉是为活动运营类型系统量身订做的策略。可能有同学说提出疑问,这样会带来后续扩展评估无法精准评估的问题,不过,我们配合前面讲的快速扩容能力还是可以很好地应对的。

关于平台高可用建设和实践

既然是高可用,即不能随便挂掉,AMS系统每日的CGI请求增长是比较快的,我们的项目创建于2012年,2012年时每日PV是百万级的系统,之后每一年基本都是跨数量级的增长,一直到现在高峰的时候是8亿多的流量,我们的可用性多次受到比较严峻的挑战。

同样是基于大流量Web系统下的高可用建设实践,那我讲述的又和其他讲师的有什么地方不一样呢?主要有两点:

首先我们面对的是活动运营业务场景下,活动在节假日流量峰值突增变化幅度是比较大;

我是AMS系统的初始开发人员,对这个高可用建设实践有比较深刻的体会。初只有两个人的时候我就是它的开发人员,后来系统越变越大,我就慢慢地成为了AMS的负责人。如果我们在系统上面发现一个年代就远的坑,有非常大的可能就会追溯到我自己,这种感觉相当于,自己挖了一个坑自己跳下去,然后千辛万苦爬起来。我就会想,当年的我究竟抱着怎样一种丧心病狂、报复社会的心态挖下这么深的坑,然后来坑害四年后的我呢?这些都会引起我强烈的反思,当年是在什么场景下做出的这个设计?是因为我太年轻?还是因为没有预料到未来的业务发展趋势?这些追溯性的反思,促使我更深入的反省和思考系统上的设计和缺陷,同时,也加深了我对架构演变的认知水平。

我们把AMS早期的问题进行划分,主要分三个方面的问题:

  • 存储:主要是缓存穿透;
  • 架构:在架构早期流量规模比较小的时候不少写死IP的行为导致单点问题很明显,即可能会出现这个点挂了,系统整体可用性都受到影响,还有局部影响全局的问题;
  • 协作:系统越来越大,参与的人越来越多,协作的成本也开始变得越来越高,如果来了一个新人修改了一个模块,这个模块可能涉及到了三个同学的相关模块,他就需要和这三位同学都做确认,如果哪天确认漏了,终发的版本可能就是有问题的。

我们总结上述各类问题为天灾和人祸天灾包括网络故障、机房停电、机器宕机、硬件故障;人祸方面包括异常发布(例如某开发的代码有问题影响了全局)、人工配置失误和多人协作失误。我们后来对这些问题进行了反思,它们之所以会比较频繁地发生的关键原因就是单体架构(单片架构),即系统如果没有做合适的拆分,导致所有的代码或者服务糅合在一起,这会造成比较多问题。尤其是AMS系统是由小变大的,系统小的时候,通常就是一个单体。

于是,我们对系统架构进行合适的调整。Uinx哲学有一句话非常好

Do one thing and do it well

我只做一样的东西并把它做好,对应的架构思想就是SOA微服务,微服务近几年也被大家谈论得比较多。

我们做的件事是L5名字服务,做到去中心化、无状态和平行扩充,简而言之,如果你要访问一个Server,不管它是什么都需要先向L5服务要对应服务的IP和端口,L5就从一组服务路由表里按照一个分配算法随机取一个给你,然后你再去请求它,如果成功了就要上报成功给它,失败也上报失败给它,这时候L5服务会计算出每一台机器的延时和成功率情况,并且可以将失败率高的机器踢掉,如果恢复正常就再加回来,从而,把偶然宕机或者机器异常的问题解决了。

但是这里有些同学可能会有疑问:所有人都请求L5服务,L5服务会不会扛不住?确实,如果所有服务都去请求它,L5压力会很大,所以L5的实际分为两层:

一层部署在本地Server的Client层,相当于本地路由表(服务器本地Server);

另一层是Server层,扩容的时候就往Server层添加机器IP,它就能从Server层下发到各个服务器的本地层。

另外我们把主要的存储从MySQL慢慢迁移到CKV,CKV是我们公司内部研发的Key-Value分布式存储,可以理解为类似的分布式的Redis存储。

下图是AMS早期的系统架构图,可以看出某些模块有比较明显的单体现象,后端服务数也不多。

架构经过多年演变之后如下图,首先是CGI层,我们根据不同的功能、业务进行物理和业务上的拆分,拆分成一个个Web Server集群,后端全部用L5的方式接入,让它们无状态且支持平行扩容,同时把一些Server从原来的大Server拆分成小的Server。当我们做完这一点以后,它们的耦合问题得到了相对来说比较好的优化。

回到前面的问题,天灾怎么避免?我们概括如下:

  • 网络故障:建立合适的机器部署方案,例如把两批机器跨网络端部署,哪一天这边被一锅端或被挖断了,另一边边还能用;
  • 机房停电:我们可以跨机房、跨IDC部署,我们的Web Server层就分别部署在5个机房上,哪天哪个机房停电了,对我们是没有大的影响的;
  • 硬件故障:可以通过L5模式支持自动剔除和自动恢复;
  • 服务进程挂掉:通过Shell编写的监控进程脚本把它重新拉起来。

做一个简单的汇总,我通过路由和L5的模式,以及合适的机器部署情况做跨网络、跨机房的部署,使得我整体的可用性能够承受各种各样的天灾的袭击。

我们聊一下人祸。首先是人工配置问题,配置可以说是业界的难题,为什么?因为在我们公司、甚至说业界,很多大型的现网事故本身并不是由多么高大上的Bug所导致,而是由配置文件引起,例如,配置多一个参数、少一个参数、改错一个参数,进而导致很严重的问题。尤其我们一个月上线450多个活动,每个月对应上线的活动配置多达5000多份,怎么杜绝参数错误的问题呢?

如下图。假设这位运营同学提交了一份有问题的业务配置,首先我们会进行人工测试环节,如果人工测试无法发现,问题就会留落到现网。举个实际的例子:我们库存一共100个,但这个运营同学填写200个,这个问题测试能发现吗?不能,因为只有发送到101的时候才会出现异常。

对我们来说怎么避免?我们的做法是在运营同学发布前建立智能程序检测的环节,回到前面的例子,库存和配置数量不匹配,提交时我们直接把库存两边拿过来对比一下,发现不匹配就直接提示,直到配置正确了之后再让发布通过。那么我们的规则怎么来?这几十个规则就是活鲜鲜的多年血泪史(多么痛的领悟),现网每出一单事故或者问题,我们就把事故拿出来分析、讨论、抽象和总结,看能不能成为检测规则的一部分。解决人工配置难题,我认为没有说放之四海皆通的解决方案,更多是跟着业务亦步亦趋地共同发现和解决

关于可用性,能不能用一个比较收拢的例子对前面所讲的事情进行概括呢?我们一起来讨论一个场景,假设有一个新同事刚加入公司,他修改了一个模块,这个模块修改后可能有问题,有没有办法通过可用性和架构的建设来减轻或者避免这个问题?

也许有同学会质疑,人犯的错误难道还能干预和改变?

我们认为是可以做一些工作的,我们分为事前、事中和事后:

、事前

如果系统是单体架构,那么里面逻辑会很复杂,模块与模块之间的耦合会很重,如果不把里面的代码分离和隔离,耦合是天然的,就会有人写出耦合的代码。所以我们把单体架构经过合理拆分,我们把程序变得更简单,协作更少,让新人看到代码更少,更简单。首先从根源和架构上面尽可能避免新人犯错。

其次,如果是单体架构,哪怕只是修改了一句简单的输出"hello world"的代码,从测试完整的角度出发是需要把单体架构上所有的逻辑都回归一次,这样回归测试的成本是很高的。但如果拆分过的话,只需要回归一个具体的小模块,小模块的测试就可以比较轻松完成回归测试。

这样可以做到三点:程序更简单、更少协作、更容易测试,尽可能从源头上面避免新人犯下错误。从软件的长远生命周期出发,人员总是会变换的,总会有新人加入和人员调整,所以必须考虑新同学加入的门槛成本问题。

另外一个是建立自动化测试,发布之前跑一下自动化测试用例,建立灰度、观察和全量模式,即使有问题我们也争取尽可能早地发现它,如果事前挡不住了就到事中。

第二、事中

我们通过对服务的分离以及对架构的物理隔离。以前是一个单体,如果有人写了一段代码引起CPU,可能单体内所有的机器都受到影响,但如果是业务上物理隔离的,它只影响到自己对应的业务或者功能模块,我们通过这种架构隔离来缩小问题的影响范围。

另外是建立多维度的监控能力,比如说前端CGI响应、L5、模块之间的调用成功率、流量波动等监控,使得问题能够更快更早地被我们发现。

总结一下:通过比较合适的架构设计和多维度监控能力,缩小问题的影响范围,减少问题的影响时长。

三、事后

我们必须建立与发布对应的回滚能力,不能想办法再发一个新的版本去修复,而应该优先恢复现网系统正常,因此,发布系统应该要具备一个按钮,点一下让它回滚到上一个正常的版本,也就是一键回滚能力;另外还需要有可追诉的日志记录,可以把受影响的用户和相关的数据的统计出来,进行后续处理。

我今天的分享就到这里,谢谢大家!

分享好友

分享这个小栈给你的朋友们,一起进步吧。

数据架构
创建时间:2020-05-20 11:23:41
有关数据架构的小栈里面全都有
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

技术专家

查看更多
  • 小雨滴
    专家
戳我,来吐槽~