系统的请求量突然增大数倍怎么办?
一般的业务服务系统大体上就是通过网络远程对DB进行读写。如果流量突然飙大,总有一个资源会遇到瓶颈。按照经验大概出问题地方是DB磁盘io、CPU、带宽、连接数、内存其中的一个或几个。不同的业务,不同的系统设计,出问题的地方会有所不同。如果流量增大数倍,势必某个资源会在瞬间被榨干,然后所有的服务都会“开小差”,引起用户的抱怨。而解决问题的关键,是在问题发生时,尽量减少出问题的资源被访问。
对于这个问题,我这里给出两个回答,一个是应付面试的,一个面向实际的。大家各取所需。
面试中怎么回答
面试官其实就想听到几个术语的解释而已——缓存、服务降级、限流。
缓存,就是用内存来顶替一部分DB的查询+数据的处理。这应该是所有业务开发人员的必修课。业务上大致可以把缓存分为三类:浏览器缓存(HTTP Cache-Control Header),CDN和服务器业务缓存。而业务缓存根据实现的结构可以分多个层级,可以用in-memory cache (如Guava Cache),或者是分布式共享Cache(如Redis)。在设计缓存一致性更新模式时,无非就是Cache Aside、Read/Write Through和Write Behind这三大种模式。有些超级NB的缓存系统自带Cluster加持(比如Ehcache即可单机用,也可以组集群)。限于本文主题,具体的缓存设计不赘述。
留意下这里说的缓存仅仅是利用了内存访问比磁盘访问快得多的特性(大概可以理解为2~3个数量级),并不会让用户感知到数据一致性哪里不对劲(与下面的降级不同)。
服务降级,是指通过降低服务质量的方法,达到节省资源的目的。简单来说就是弃车保帅。比如你的服务有ABC,平时消耗差不多的资源。突发事件时,A的请求量极大的增高了,B和C没有变化。那么可以比如减少或者暂停B和C的服务,省出资源给A用。
再比如,一个热点新闻的业务,有新闻内容,有评论,有点赞数等等。一旦新闻热点了,就可以把所有这些内容“静态化”,不必再查DB。这时虽然评论,点赞的数暂时就不准了,但是主要的服务——内容,还是让用户可以看到。这就足够了。
可以看到,降级的具体的方案,要结合业务和系统实现来综合设计,并没有定法。
降级很多时候也会用到缓存。只不过这时候使用缓存的方法就可能会以牺牲数据一致性为代价——内存里的数据和DB不一样,就不一样吧,业务上可接受,并且这段热点时间段过去了,能够恢复为一致就可以。
限流,即限制用户的请求流量。具体的做法有计数器、滑动窗口、滴漏、服务token、请求队列化等办法。这些方法的详细解释,在这里都说得比较清楚,所以我就不重复了。只是值得注意的是,现在很多生产级别的服务都是多节点分布式架构。很多单机上容易做的算法和控制逻辑到了分布式下就会带来一些实现上的麻烦。这又涉及到了分布式一致性、CAP的权衡等等问题。
怎么样,这些足够你在10分钟内和面试官白话一番了吧。下面我们说说真的。
真实世界的流量问题
凡事总有第一次
如果你的系统是第一次遇到流量突然增大的问题,而之前又没有任何准备的话,那么此问题无解。这一般出现在初创公司。你只能眼睁睁的和你的同事大眼瞪小眼,彼此唉声叹气,并祈祷这段时间赶紧过去,同时老板最好不要得到系统挂掉的消息(虽然几乎不可能)。
这事没发生,没发生……
有人说,流量来了,我马上去买几台云服务器,配合使用来自于NB公司的服务框架的配置调整,然后nginx reload一把,不就手到擒来了?问题在于,尽管对于无状态的服务可以比较简单的扩容,作为有状态的DB是无法随意扩容的——而DB在流量突然增高的情况下往往就是系统瓶颈。退一步说,就算一开始是无状态的应用服务器的CPU是瓶颈,然后马上扩容应用服务器,那么这个瓶颈也会最终压到DB上。DB的扩容方案需要另花时间长篇大论一番,这里就不再深入讲了。
另外,即便是在新机器上部署业务服务器,在没有准备的情况下十之八九也会出问题。why?对于一个干净服务器:
你得装环境镜像吧。万一着急OS版本号没看清,和其他业务服务器不一样,从而使得程序跑起来不一样怎么办?内核bug并不少见。万一现有的主机被调整了一些系统参数(比如tcp一类的),而新的环境忘了怎么办?
云服务器的实际环境你是看不见的。你新上了一批,怎么就敢拍胸脯说它们和之前的机器——那个文词怎么说来着——同构?
你得装最新的应用包吧。之前得装业务服务本身(比如一个war包),你能确保在混乱的情况下这里给的war就是能用的war?现在有了docker相对好一些,但是docker是可能有bug的,尤其是早期版本(我遇到过……),比如在一个地方起得来,换一个地方就起不来,或者连不上网。你敢打包票没问题?
有了应用得配置吧,不管是环境变量还是properties文件,外部依赖、DB链接、各种秘钥证书、各种只有开发才知道什么意思的配置可能多达几十个,全都得弄对。你敢说你能一次搞对?
以上这些事情,有任何一步出错,造成的问题都可能比流量本身更严重。我相信,如果这个流量问题是第一次,那么几乎可以肯定,项目组应该没准备过任何的服务治理的方案,更不要说演练。
那些N年前云计算吹嘘的弹性如何如何,能快速部署如何如何的slogan,在没有业务和系统设计的支持下,只是噱头罢了,不要太当真。
以上问题,如果你的公司可以轻易的做到,就说明运维已经做的极度可靠和强大。运维可以达到这种程度,是经过不懈的努力和大量的资源付出才能得到的。
所以,关键的问题是,要对高流量的到来有所准备。要提前设计方案,无论是降级限流还是别的什么歪招。要设计、实现、测试、生产实机演习,只有这样才能保证到了关键的时候才能用得起来。而这些事情将会花费技术团队大量的资源。
总是预先准备
当设计一个业务时,产品设计和研发团队应该找个时间,除了讨论产品本身怎么实现之外,还应该关心一下如下几点的实施:
流量估算。到底大概有多少人可能会用呢?对于大公司,都有长时间运营的经验,可以参照之前的产品/活动给出一个量化的估算结果。但是小公司往往就只能拍脑袋。但即便是拍脑袋也比没有强,可以作为设计容量的依据。这下你明白为啥有些公司面试时会出“你觉得本城市有多少个辆汽车”这样的题目了吧。
作为一个经验,可以把设计流量*3作为系统压力的下限——即,实现完了要压测,压测得到的结果要达到设计流量 * 3。当然如果是你的话,要 * 4, * 5都可以,关键是要给系统留些缓冲。一旦发生了什么,不至于挂的太惨。此时,一般会得到一个带缓存的业务服务系统。考虑到缓存高于后台服务2~3个数量级的性能优势,多撑几倍流量一般不成问题。
降级方案。降级总得是用户可以买账的方式才行,不能瞎降。能降级成什么样,显示成什么样子,都得预先设计好。UI上有的要配图,有的要出警告语提示。而作为后台服务器,需要有对应的实时开关,一旦设置,立刻进入降级方案。
但是,如果核心服务就是热点本身,就没得降级,那就尴尬了…… 比如下单就是下单,不能下一半,不能砍掉支付,不能随机性有的能买有的不能买。这时就得靠限流。
限流方案。上面提到了种种限流算法——计数器、滑动窗口、滴漏、服务token、请求队列化,等办法技术在更加传统的模版式页面的网站更容易做——整个界面是由一个GET请求在后台通过模版产生的。所以只要在这个请求处理过程中做限流控制即可。
但是在SPA或者移动端App时,一个界面可能是由数个或者数十个Ajax接口分批获得。每个请求都做限流可能会得到随机的半残的界面——有几个接口没限制,有几个被限制了。此时做限流还得考虑前后端架构设计。一般来讲,每个主要界面都应该有个主控接口来实现限流(比如产品详情接口)——即,一旦该接口说限流了,后续的前端代码就得配合按照预先的设计显示限流后的界面。同时会影响关键资源的接口在后端要再做一道限流——毕竟你不知道有没有人绕开前端直接压接口使坏不是。嗯,抢票就是这么来的。
提前安排开发和演练排期。如果一切安排妥当,就可以做作演习了。你可以找个没人用你服务的时间点(大半夜?)使用流量replay压一下你的真实生产环境,看看真的发生了流量增高的问题,系统是否足够健壮能够应对,之前设计的种种方案是不是可以达到设计的需要。根据墨菲定律,可能会发生的事情一定会发生,不经演练的系统上线到了出问题的时候100%会让你大开眼界。
然而,高能预警,做演练时:千万千万不要产生像你的模拟流量代替用户下了一单并且扣款的事情……。
为啥技术方案难落地
讲真,相关的工具开发和规范的制定其实并不难。大道理就那么几条。难的是让高层意识到这个事情有多么的重要,如果不做的话会带来多么巨大的代价。这是因为一个公司的高层可能是产品出身、营销出身、供应出身,可以是个彻底的技术外行。让他们理解技术的重要性,难度差不多就相当于让娘亲学会怎么把手机里的视频投屏到电视。此外,技术团队的主要目标往往是实现公司业务需求。在劈天盖地的业务需求的夹缝中找到资源来优化技术基础设施相当的苦难。毕竟公司活下去才是第一位的。
如果你成功的把你的头头劝服,并且为此申请了一大笔资金购买机器、招NB的人来做相关的基础设施,而又不耽误业务的话,你就是三楼楼长——呀不对,是CTO的好苗子。
你就是三楼楼长
当然,如果你说你在BAT,FLAG这样的公司,那请自动忽略本条目。
也许是个业务问题
也许解决了技术问题还远远不够。比如电商这个业务,可能业务上会预计有多少流量,但是到底备多少货呢?多了费库存,少了用户骂。2017年双十一就很明显。淘宝上在双十一的前一周网站的各大热门产品都要“预约”。用户预约了能拿到更多一点折扣,而对于商家,也大致了解了要备货的数量。双赢。
对于我做的业务——财富管理业务,买入信号是个很关键的触发点。我们一旦侦测到了价格合适,就可以分期分批给用户发通知,提示他们机会难得,该买啦。这样用户得到了方便(提示就买,不用自己刷刷刷),而我们可以大致控制进入系统的用户数量不至于过多。双赢。
最后,劳请客服支持的同学提前准备好小礼品,以备不时之需������。
我们的不时之需
相关问题
这里稍微说一下高流量问题带来的一些相关的问题。这里仅仅是简单列举,具体内容之后找时间细细说。
雪崩效应——如果用户看到“服务开小差”,他的第一反应一定是再刷一次;如果是微服务架构,服务与服务之间可能会有自动重试机制。而这些,会让已经半死的系统死的更透彻。对此类问题,一般使用断路器的方案,简单来说就是,如果一个服务已经证明快挂了,就别再调用了,直接fallback。等一会再试。nginx里的upstream控制有max_fails和fail_timeout处理这个问题。Hystrix也实现了该机制。但断路了不等于让用户看到404页面骂娘,一定要结合业务+产品设计来实现断路方案。
无效的服务响应——在高压下,可以简单将等待处理的服务看作是在排队,队首的请求被处理。但被最终“见”到处理逻辑的请求从队尾排到队首时可能已经过了比较长的时间,而客户端那边可能早就超时了。所以业务服务处理了也是白处理。这时如果队列系统做得好,比如要处理前先猜一次是不是处理完了会超时,如果是就忽略扔掉。可以减少这种问题的发生几率。这也算是一种服务降级。
大量的TIME_WAIT——如果业务服务器的压力造成服务端大量主动关闭连接,就会产生大量的TIME_WAIT状态的TCP链接。这些链接会在数分钟内像僵尸一样堆在那里,榨干所有的连接数。这种问题尤其以自研业务服务框架容易出现。
一致性——为了服务降级,可能会把用户请求放内存里缓一缓,再批量进DB。那么一旦系统出现故障,就意味着比如下单数据不一致,支付状态不一致等问题。有时,这些问题在业务上极大的影响用户的使用体验。当系统降级时,尽量保证,要不就告诉用户现在不能给你服务,要服务了结果就明确。对于交易这种业务,事前打脸还是比事后扯皮要好一些。两害取其轻。
系统可能会临时stop the world——对于java这样的系统,会因为GC而暂时卡那么一下;对于mongoDB,可能因为要底层flush数据到磁盘,也会卡那么一下;平时写的什么正则表达式处理一类的逻辑,在高峰期也可能会卡那么一下…… 平时一般没事,但是赶上高峰时,这些问题一旦出现就有可能成为压垮骆驼的最后一根稻草。因此平时还是多多压测和演习,心里踏实。
我一时大抵只想到这些。
最后的话
其实第二部分也是面试内容,只不过用来面试的是高级架构/管理职位,足够你和面试官侃两个小时。怎么样,惊喜不惊喜?