100 亿次的挑战:如何实现一个 “有把握” 的春晚摇一摇系统
BooGossett
8年前
<p>这是2015年年初(羊年春晚)摇一摇活动的技术复盘文章。在两年之后再次温习,有很多架构上的取舍仍然可以给类似的活动参考,因此重新发出来。</p> <p>羊年春晚摇一摇活动已经落下帷幕,现在回过头来看看这一全民参与的有趣的活动背后,有着怎样的后台系统?这个系统又是如何被设计与实现出来的?</p> <p>春晚摇一摇活动</p> <p>在了解这个系统之前,先看看羊年春晚有哪些活动形式?春晚摇一摇复用了摇一摇入口,但提供了全新的界面和交互内容。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/00970c7d2e7f4c30fb3a3454c4202159.png"></p> <p>在羊年春晚摇一摇界面里,用户摇动手机后,可以看到明星拜年、全家福、好友贺卡等精彩纷呈的活动页;也会有温馨的“休息一下”,或让很多误以为中奖的“挂服务器”等特殊用途的页面。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/e2d4d86d0f30e343afbf0a7bba0bc9f7.jpg"></p> <p>大家最期待的肯定是摇红包,摇中红包的幸运用户除了自己领到一份红包(种子红包)外,还可以领到若干份用于分享给其他好友的红包(分裂红包)。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/dcfd82fb055ddd4ec75216936528a593.jpg"></p> <p>围绕这些活动,下面将会通过4个处于项目不同阶段的里程碑版本来介绍我们设计、实现这一系统过程中的一些思考和做法,特别是标题里提到的“有把握”是由何而来。</p> <p>V0.1原型系统</p> <p>原型系统很简单,但已经基本实现了春晚摇一摇的需求。原型系统的架构见下图。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/67f3ae645356d2694673e680cd7bfca9.jpg"></p> <p>相关的处理流程如下:</p> <ul> <li> <p>用户摇动手机后,客户端产生摇一摇请求,请求发到接入服务后,会被转发到摇一摇服务;</p> </li> <li> <p>摇一摇服务会根据现场节目的流程,经过一系列的逻辑判断,给客户端返回一个结果:明星拜年、红包或者其他活动;</p> </li> <li> <p>假设是摇到了红包,由于红包都是企业赞助的,需要对企业形象进行展示,客户端会从CDN拉回这个企业的LOGO等资源,最终展示出一个完整的红包;</p> </li> <li> <p>随后用户拆红包时,请求会进入红包系统,再到支付系统,最后到财付通系统完成一系列复杂的账务处理,最终拿到红包;</p> </li> <li> <p>用户还可以分享红包,被分享的红包通过消息系统发给好友或群,其他人可以再抢一轮;</p> </li> <li> <p>在这一过程中,安全系统保障红包活动的业务安全。</p> </li> </ul> <p>上述数据的流动可以分下类:资源流、信息流、业务流和资金流。本文将主要聚焦在资源流和信息流。</p> <p>面临的挑战</p> <p>原型系统看起来已经足够简单,功能也基本完备,是不是可以稍加修改后直接用在春晚现场呢?答案肯定是不行。那么问题来了:为什么不行?</p> <p>回答这一问题前,我们先看一下看似简单的活动背后,面临着哪些挑战?</p> <p>海量用户请求,预计请求峰值1000万/秒</p> <p>1000万/秒究竟是多大的规模,可以通过下图更直观地感受下:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/157d4854ef443fd08b91b0a6b1e1f8c1.png"></p> <p>注:抢火车票数据引用自公开数据</p> <p>春晚全程互动,不确定因素多</p> <p>这个系统需要跟羊年春晚全程紧密互动,从项目开始到结束,有一系列的不确定因素会加大系统实现的复杂度:在开发阶段,针对节目与活动形式如何配合这个问题的讨论有可能持续到春晚前,如何使系统能服务多变的需求?在春晚现场,节目数量多,节目时长甚至顺序都有可能调整,如何做到现场节目与摇一摇活动无缝衔接?</p> <p>系统深度定制,成败在此一举</p> <p>作为专为春晚设计的系统,部署上线后真正能运行的时间就只有几个小时,这几个小时内,常规系统所提倡的灰度发布、先扛住再优化等做法并不是太适用。在这短暂的时间内,只有一次机会:要么成功,要么失败。</p> <p>全民高度关注,必须成功</p> <p>春晚会有7亿左右的观众,大家对这一活动抱有很大期望,全民瞩目之下,只能成功,不能失败。</p> <p>缺少历史经验,把握不大</p> <p>如此大型的活动,对我们而言是史无前例的,并没有太大的信心。前边提到的1000万/秒的峰值是如何估算出来?各个环节会有多少用户参与?系统需要预留多少资源?这些问题不会有现成的答案,都需要摸索与思考。</p> <p>可见,在看似简单的活动背后,隐藏了巨大的挑战,之前假设的原型系统不太可能胜任,需要做更深入的优化。</p> <p>需要优化哪些环节?比较显而易见的有三个:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/510d0437bee81045f84c0b4cfa8b88a2.jpg"></p> <p>流量带宽</p> <p>春晚摇一摇需要用到大量的多媒体资源,这些资源都需要从CDN下载。经过评估,带宽峰值需求是3Tb/s,会带来巨大的带宽压力。即使我们有无限的资源,带宽需求能被满足,客户端在摇一摇后下载资源所需的等待时间也会带来很大的用户体验损害,是不可接受的。</p> <p>接入质量</p> <p>接入是后台系统的第一道关,所有请求都会到达接入。预计当晚会有3.5亿的在线,如何尽可能保障外网接入质量?甚至在外网波动时也不受太大影响?</p> <p>海量请求</p> <p>1000万/秒的请求从外网涌进来后,都被路由给摇一摇服务,也就是说摇一摇服务也将有1000万/秒的请求量。这意味着需要确保系统内2个1000万/秒的高可用,在分布式系统内,这是个挺大的问题。</p> <p>如果要对系统能否最终取得成功量化一个信心指数的话,这个原型系统的信心指数是10。这个数字表示如果春晚摇一摇采用这套系统并取得成功,我们认为90%是靠运气。也可以换种说法:拿这个系统到春晚摇一摇,90%的可能会挂掉。</p> <p>系统肯定不能以运气为基础,这几个问题如何解决?</p> <h2><strong>V0.5 测试版</strong></h2> <p>V0.5的目标就是为了解决V0.1原型系统里的几个问题。</p> <h3><strong>资源预下载</strong></h3> <p>春晚使用的资源比较多,也比较大,但基本都是静态资源,是可以提前几天下载到客户端的。保存到本地后,在需要使用的时候,客户端直接从本地加载,从而省去了集中在线下载所需的带宽需求,另外用户摇一摇时不再需要下载资源,可以有更好的用户体验。下图展示了资源预下载过程。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/a26c177d715da2d199951aaf67387a2d.png"></p> <p>资源推送模块负责将资源上传到CDN,同时推送资源列表给客户端。推送过程基于微信消息系统实现,可以在极短时间内把资源列表推送给几亿的用户。</p> <p>资源预下载需要解决以下几个问题:</p> <ul> <li> <p>资源交付情况</p> </li> <li> <p>资源更新</p> </li> <li> <p>资源下载失败</p> </li> <li> <p>资源覆盖率</p> </li> <li> <p>离线资源安全</p> </li> </ul> <p>通过这套资源预下载系统,2015.2.9 - 2015.2.18 期间,下发了资源65个,累计流量3.7PB,其中闲时峰值达到1Tb/s。</p> <h3><strong>外网接入梳理</strong></h3> <p>要保证接入质量,除了需要保证接入服务本身的稳定性外,需要做到:</p> <ul> <li> <p>具备在某些外网接入出现波动的时候,能自动切换到正常接入服务的能力;</p> </li> <li> <p>保障网络与服务具备足够的冗余容量。</p> </li> </ul> <p>微信客户端已经具备了外网自动容灾切换的能力,下边再看一下系统的外网部署情况。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/5e3520f9d3ac16071d36adece16e960b.jpg"></p> <p>我们重新梳理了外网的部署,最终在上海IDC和深圳IDC各设置了9个TGW接入集群。每个IDC都在3个不同的园区分别部署了电信、移动和联通的外网接入线路。</p> <p>另外,接入服务也进行了临时扩充,两地共有638台接入服务器,最多支持14.6亿的同时在线。</p> <h3><strong>接入服务内置“摇一摇”</strong></h3> <p>架构变动</p> <p>前面提到,系统需要面对预计1000万/秒从外网的请求,同时还需要在内网转发同样是1000万/秒的请求给摇一摇服务,除此之外,还需要在各种异常情况下确保高可用。在如此海量请求之下,在这个分布式系统中,任何一点网络或服务的波动都可能是灾难性的,这里边的困难程度可想而知。</p> <p>正面解决这一问题的成本过高,我们选择了去掉摇一摇服务——把摇一摇逻辑集成到了接入服务,从而去掉了1000万/秒的转发请求。</p> <p>但这样做,有一个前提:不能降低接入服务的稳定性。因为不单是春晚摇一摇请求,微信消息收发等基础功能的请求也需要通过接入服务来中转,假如嵌入的摇一摇逻辑把接入服务拖垮,将得不偿失。</p> <p>接入服务的架构刚好有助于解决这个问题。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/0a7696341a2ee12bf60ff7a0dcf2b016.png"></p> <p>如上图所示,接入服务有一个网络IO模块,这个模块维护了来自客户端的TCP连接,收发数据,负责跟客户端通讯。网络IO模块是作为独立的进程组运作的,收到的请求通过本机共享内存发给接入逻辑模块处理。接入逻辑模块也是一组独立运行的进程,通常情况下是由请求中转逻辑通过RPC把请求转发给其他逻辑服务器处理。现在摇一摇逻辑作为嵌入逻辑,整合到了接入逻辑模块。由于网络IO模块与接入逻辑模块相互隔离,可以单独对接入逻辑模块进行升级,不会影响到已有的网络连接,这大大降低了嵌入逻辑带来的风险。</p> <p>但这样还不够,还可以把嵌入到接入逻辑模块里的摇一摇逻辑尽可能简化,只保留比较简单、稳定、只需要单机计算即可完成的轻量逻辑;把其他较复杂、可能需要经常变更、或需要跨机访问的逻辑独立为摇一摇agent,作为独立运行的进程,通过本机共享内存与嵌入摇一摇逻辑通讯。</p> <p>摇一摇逻辑实现</p> <p>接入服务架构的修改使得内置摇一摇逻辑变得可能,剩下的就是怎样实现春晚摇一摇逻辑?春晚摇一摇逻辑最重要的是摇红包,摇红包则需要重点解决以下问题:</p> <p>红包如何发放?</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/dd52f540aa76e47e5c4751a6c1b21bb3.jpg"></p> <p>所有红包都源自红包系统,但为了在运行时不依赖红包系统,红包系统生成的种子红包文件被提前部署到了接入服务。为确保红包文件不会被重复发放,有个文件切分程序完成不同机器的文件切分,每台机只保留自己需要处理的那部分红包;另外,为了确保红包不漏,还有另一个程序完成所有机器红包文件的合并校验。</p> <p>摇到红包的用户如果选择不分享红包,这些红包会被红包系统回收,一部分作为种子红包经由摇一摇agent回流到接入服务进行发放。</p> <p>摇一摇agent会根据预设的策略给内置的摇一摇逻辑提供可下发的种子红包。红包是每秒匀速下发的,可以精确控制全局红包下发速度,保持在红包/支付系统能正常处理的范围之内。</p> <p>怎样保障安全?</p> <ul> <li> <p>单用户红包个数限制</p> </li> </ul> <p>业务要求每个用户最多可以领取 3 个红包,并且每个赞助商最多 1 个。常规实现里,需要在后台存储用户领取记录,每次摇一摇请求时取出来计算,如果拿到红包还需要更新存储。但这里增加了对存储的依赖,海量请求下,稳定性不易保障。我们借鉴了 HTTP 协议的 COOKIE ,在摇一摇协议里也实现了类似的机制:后台可以写入用户的红包领取情况到 COOKIE ,交由客户端保存,客户端在下次请求时带上来,服务器再取出来判断。这就无需后台存储也可以实现同样的目的。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/024a2753d60b7bb48d3d0ed7a781f72b.png"></p> <ul> <li> <p>破解协议的恶意用户</p> </li> </ul> <p>协议总是有可能被破解的,恶意用户可以制作自动机,绕开 COOKIE 检测机制。所以 COOKIE 检测机制只能用于绝大部分使用正常客户端的用户。对于破解协议的恶意用户可以通过在本机存储红包下发记录来打击。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/63f153625298bf1803ba34964ea89956.png"></p> <ul> <li> <p>游击战</p> </li> </ul> <p>虽然接入服务使用长连接,但恶意用户可以不断重连,变换不同的服务器来获取红包,针对这种情况,我们设计了红包发放汇总服务,可以在所有接入服务上同步已经达到红包领取上限的用户。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/da004aca384f8c8c6d5e0ec9ee4bcb9f.jpg"></p> <p>以上策略依次递进,后一步较前一步更深入一层解决问题,但是在设计上,前一步并不依赖后一步,即使后边的步骤出现故障无法按预期进行,也不会影响前边的结果,在最差的情况下,系统仍然可以保证单用户红包个数限制的需求。</p> <p>如何与春晚现场同步互动?</p> <p>春晚摇一摇系统需要配合春晚现场节目进行互动,春晚切换节目或者主持人进行活动的口播时,系统都需要同步切换活动配置。</p> <p>这里一是要求快速,二是要求稳定。为此我们设计了这样一个配置系统。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/e5c97f3b71658c20d3a87dc0adeb13d2.png"></p> <p>春晚现场有一个配置前台,现场人员可以通过这个配置前台发送变更指令到后台。后台有上海和深圳两个接收点,每个接收点由处于不同园区的3组冗余的配置服务构成。配置服务接收到配置后会同步到另一接收点,同时以文件形式下发到所有接入服务。为确保同步成功,使用了RPC/RSYNC/变更系统三个冗余变更通道来同时变更配置。从配置前台发起变更,到接入服务加载配置,全程可以在10秒内完成。</p> <p>看起来快速和稳定都满足了,是不是就已经足够了?请思考以下异常场景:</p> <ul> <li> <p>春晚前台无法工作或网络故障,指令无法发给配置服务;</p> </li> <li> <p>所有配置服务均无法工作或网络故障,配置无法同步给接入服务。</p> </li> </ul> <p>这些异常在春晚节目过程中临时出现的话,一般情况下影响并不大,我们可以临时手工变更配置,赢得足够时间去修复系统。但是,如果此时主持人正在号召大家拿起手机抢红包,我们是没有足够的把握可以在极短时间内完成全系统的配置变更的,也就是说极有可能功亏一篑,关键配置变更不了,红包根本出不来。还有一种类似的场景,就是抢红包开始后,也因为配置变更问题,结束不了,没法切回其他活动,损坏用户体验。</p> <p>针对口播抢红包这个关键点,我们采用这样的配置变更策略:使用抢红包倒计时配置,到点后自动开始抢红包时段,经过预设的时间后,又自动结束。另外,由于春晚节目的口播时间点具有不确定性,我们制定了变更策略,在节目过程中可以逐步校正倒计时。于是这一关键配置就只与时间相关,机器时间相对容易保障,通过NTP就可以达到足够精度的时间同步,我们还对机器时间进行集中监控,可以发现并处理机器时间出现异常的机器。</p> <p>预估带来的问题</p> <p>至此, V0.1 原型系统前边提到的三个问题均已解决, 1000 万 / 秒的海量请求可以扛住。但是, 1000 万 / 秒是预估的,如果最终来了 2000 万怎么办? 4000 万呢?看起来数字有点不可思议,但真有 1 亿甚至几亿用户在不停的摇,也还是有可能出现的。</p> <p>对于2000万/秒,我们其实并不担心,接入服务经过不断的优化,可以在提供容灾冗余的基础上,仍有2500万/秒的吞吐能力。</p> <p>但是4000万/秒大大超出了后台服务能力,怎么办?</p> <p>海量服务之道有个过载保护,如果没法硬扛,可以做到自我保护。简单的说就是前端保护后端,后端拒绝前端。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/99593b4b5c257679691912f0194e06e9.png"></p> <p>客户端在服务访问不了、服务访问超时和服务限速时主动减少请求。</p> <p>接入服务可以在发现某个客户端请求过于频繁时,会自动延迟回包,间接达到拉大请求间隔的效果,最终实现在后台把客户端请求频率限制在合理范围内;另外,接入服务会计算机器的CPU使用情况,CPU使用率到达不同预设阈值时,会自动返回不同档次的限速给客户端,通过客户端配合完成限速。</p> <p>通过这些措施,在请求量超预期时,系统会自动降级,从而实现自保。</p> <p>综上,可以得到V0.5测试版的架构,信心指数50。</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/ebcb5a8ab94ae45dfd63d91b4e45c990.jpg"></p> <h2><strong>V0.8 预览版</strong></h2> <h3><strong>核心体验是什么? </strong></h3> <p>为什么V0.5测试版的信心指数只有50?还有哪些没做到位?</p> <p>反思一下,可以发现:V0.5测试版主要聚焦于如何处理海量的摇一摇请求,让用户可以愉快的摇出红包,那摇出红包之后呢?V0.5测试版并未对这个问题作进一步的思考。</p> <p>先看一下摇一摇的核心体验——摇红包,摇红包涉及摇到红包、拆红包、分享红包、好友抢红包等等步骤。</p> <p>稍加分析即可发现,前三步是本人操作,后续是好友操作。从本人操作到好友操作之间是有一定时延的,这也就意味着这里其实可以接受一定程度的有损体验——延时。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/7f44618dde38272802fab8d39efe6d62.png"></p> <p>V0.5测试版已经解决摇红包的问题,剩下就看拆红包和分享红包了。</p> <h3><strong>如何确保拆红包和分享红包的用户体验? </strong></h3> <p>可以把拆红包和分享红包抽象一下,它们都是由两个部分组成:用户操作(信息流)和后台的红包处理逻辑(业务流)。对用户来说,他关心的是操作是否成功,而不是后台的红包处理逻辑。因此这个问题可以进一步简化为:如何确保拆红包和分享红包的用户操作能成功?</p> <p>我们在用户操作和红包处理逻辑之间加入了一个中间层。这个中间层包括红包简化逻辑和红包异步队列,实现了信息流和业务流的异步化和解耦。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/3ea636bd604b01692f01217a65e2db15.png"></p> <p>红包简化逻辑</p> <p>通过一些算法设计,红包简化逻辑可以通过本地计算完成对红包请求是否合法的初步判断,合法请求放入异步队列,然后就可以返回客户端处理成功;</p> <p>红包异步队列</p> <p>红包异步队列存储来自简化逻辑的请求。在存储上,异步队列使用3机方案,3机写2份成功即算入队成功。异步队列的处理逻辑会从队列取出请求继续发起对红包系统的调用,完成后续的红包逻辑。在红包系统故障时,会有妥善的重试策略,确保在系统恢复后重试成功。</p> <p>接入服务、红包简化逻辑服务、红包异步队列构成了所谓的“铁三角”,可以在红包系统出现严重问题时,保护用户体验在较长时间内基本不受损,争取到修复故障的宝贵时间。</p> <p>其实再仔细思考一下,“铁三角”还可以做得更强:把红包简化逻辑再次拆分为基本的业务逻辑和本地透传队列。业务逻辑完成红包合法性校验后,把请求写入本地文件队列,本地文件队列再透传给红包异步队列完成后续处理。这样即使红包异步队列出现意外抖动,也不会影响到用户操作结果。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/6ba9cc9d3a86966831c6b9707a33aaa2.png"></p> <p>V0.8预览版已经基本成型,信心指数达到70。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/d371babd9536abc1c34c6325be9be6ba.jpg"></p> <h2><strong>V1.0 正式版 </strong></h2> <p>大家知道设计≠实现,为保证最终实现和线上系统运作符合设计预期,我们还做了:</p> <p>全程压测</p> <p>未经压测的系统是很危险的,无法真正了解系统在压力下的表现是否符合预期?系统的极限在哪?为此,我们建立一个压测环境,对系统进行不间断的压测。系统部署后,在现网环境也进行了多轮真实压测。</p> <p>专题CODE REVIEW</p> <p>多部门联合做了专题 CODE REVIEW ,在代码层面对关键路径做了仔细的评估。</p> <p>内部演练</p> <p>客户端发布流程较长,全面铺开版本需要更长的时间,做不到像服务器一样可以迅速上线 fix 问题。因此在发布客户端前,除了测试外,一次次的真实演练是非常必要的。</p> <p>线上预热</p> <p>在2015.2.12和2015.2.15,最终争取到了两次预热的机会,对系统进行了实战验证。</p> <table cellspacing="0"> <tbody> <tr> <td> <p>2015.2.12</p> </td> <td> <p>2015.2.15</p> </td> </tr> <tr> <td> <p>摇一摇总次数 ( 次 )</p> </td> <td> <p>3.1亿</p> </td> <td> <p>4000万</p> </td> </tr> <tr> <td> <p>摇一摇峰值 ( 次 / 分 )</p> </td> <td> <p>5000万</p> </td> <td> <p>1700万</p> </td> </tr> <tr> <td> <p>摇一摇峰值 ( 次 / 秒 )</p> </td> <td> <p>100万</p> </td> <td> <p>43万</p> </td> </tr> <tr> <td> <p>发放红包速率 ( 个 / 秒 )</p> </td> <td> <p>5万</p> </td> <td> <p>5万</p> </td> </tr> </tbody> </table> <p>因为预热规模的原因,摇一摇峰值并未达到预估值。但发放红包的速率一直维持在5万/秒的预设值,对后端“铁三角”和其后的红包系统进行了充分的检验。</p> <p>复盘与调整</p> <p>每次演练和预热都是难得的机会,不仅那些出异常的模块,那些看起来表现正常的模块,也可以通过复盘和推演,确认是否完全符合预期,挖掘出可能的隐患点,进而解决。</p> <p>最终,我们发布了 V1.0 正式版。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/02965a58687bace82770c4e270c8d8e2.jpg"></p> <p>V1.0正式版架构上与V0.8预览版并无不同,但是信心指数加到了80。</p> <p>我们认为剩下的20%里,有10%体现在对突发状况的预案与现场处置。系统如此庞大与复杂,是不是还有没想到、未暴露出来、还做得不够或者在有限资源内无法解决的问题?这些问题一旦如果出现了,是不是有可能最终左右了整个局面?所以最后的10%就是靠运气,别出现这些问题。</p> <h2><strong>后记 </strong></h2> <p style="text-align:center"><img src="https://simg.open-open.com/show/6a115125ed50942dedfb01793c85317b.jpg"></p> <p> </p> <p>来自:http://mp.weixin.qq.com/s/DLbaiSiH15QBkrB_i0imEw</p> <p> </p>