sponsored links

Cache应用中的服务过载案例研究

原文:http://tech.meituan.com/avalanche-study.html

Cache应用中的服务过载案例研究

张杨 ·2016-06-16 17:00

简单地说,过载是外部请求对系统的访问量突然激增,造成请求堆积,服务不可用,最终导致系统崩溃。本文主要分析引入Cache可能造成的服务过载,并讨论相关的预防、恢复策略。Cache在现代系统中使用广泛,由此引入的服务过载隐患无处不在,但却非常隐蔽,容易被忽视。本文希望能为开发者在设计和编写相关类型应用,以及服务过载发生处理时能够有章可循。

一个服务过载案例

本文讨论的案例是指存在正常调用关系的两个系统(假设调用方为A系统,服务方为B系统),A系统对B系统的访问突然超出B系统的承受能力,造成B系统崩溃。造成服务过载的原因很多,这里分析的是严重依赖Cache的系统服务过载。首先来看一种包含Cache的体系结构(如下图所示)。

Cache应用中的服务过载案例研究

A系统依赖B系统的读服务,A系统是60台机器组成的集群,B系统是6台机器组成的集群,之所以6台机器能够扛住60台机器的访问,是因为A系统并不是每次都访问B,而是首先请求Cache,只有Cache的相应数据失效时才会请求B。

这正是Cache存在的意义,它让B系统节省了大量机器;如果没有Cache,B系统不得不组成60台机器的集群,如果A也同时依赖除B系统外的另一个系统(假设为C系统)呢?那么C系统也要60台机器,放大的流量将很快耗尽公司的资源。

然而Cache的引入也不是十全十美的,这个结构中如果Cache发生问题,全部的流量将流向依赖方,造成流量激增,从而引发依赖系统的过载。

回到A和B的架构,造成服务过载的原因至少有下面三种:

  1. B系统的前置代理发生故障或者其他原因造成B系统暂时不可用,等B系统系统服务恢复时,其流量将远远超过正常值。
  2. Cache系统故障,A系统的流量将全部流到B系统,造成B系统过载。
  3. Cache故障恢复,但这时Cache为空,Cache瞬间命中率为0,相当于Cache被击穿,造成B系统过载。

第一个原因不太好理解,为什么B系统恢复后流量会猛增呢?主要原因就是缓存的超时时间。当有数据超时的时候,A系统会访问B系统,但是这时候B系统偏偏故障不可用,那么这个数据只好超时,等发现B系统恢复时,发现缓存里的B系统数据已经都超时了,都成了旧数据,这时当然所有的请求就打到了B。

下文主要介绍服务过载的预防和发生后的一些补救方法,以预防为主,从调用方和服务方的视角阐述一些可行方案。

服务过载的预防

所谓Client端指的就是上文结构中的A系统,相对于B系统,A系统就是B系统的Client,B系统相当于Server。

Client端的方案

针对上文阐述的造成服务过载的三个原因:B系统故障恢复、Cache故障、Cache故障恢复,我们看看A系统有哪些方案可以应对。

合理使用Cache应对B系统宕机

一般情况下,Cache的每个Key除了对应Value,还对应一个过期时间T,在T内,get操作直接在Cache中拿到Key对应Value并返回。但是在T到达时,get操作主要有五种模式:

1. 基于超时的简单(stupid)模式

在T到达后,任何线程get操作发现Cache中的Key和对应Value将被清除或标记为不可用,get操作将发起调用远程服务获取Key对应的Value,并更新写回Cache,然后get操作返回新值;如果远程获取Key-Value失败,则get抛出异常。

为了便于理解,举一个码头工人取货的例子:5个工人(线程)去港口取同样Key的货(get),发现货已经过期被扔掉了,这时5个工人各自分别去对岸取新货,然后返回。

2. 基于超时的常规模式

在T到达后,Cache中的Key和对应Value将被清除或标记为不可用,get操作将调用远程服务获取Key对应的Value,并更新写回Cache;此时,如果另一个线程发现Key和Value已经不可用,get操作还需要判断有没有其他线程发起了远程调用,如果有,那么自己就等待,直到那个线程远程获取操作成功,Cache中得Key变得可用,get操作返回新的Value。如果远程获取操作失败,则get操作抛出异常,不会返回任何Value。

还是码头工人的例子:5个工人(线程)去港口取同样Key的货(get),发现货已经过期被扔掉了,那么只需派出一个人去对岸取货,其他四个人在港口等待即可,而不用5个人全去。

基于超时的简单模式和常规模式区别在于对于同一个超时的Key,前者每个get线程一旦发现Key不存在,则发起远程调用获取值;而后者每个get线程发现Key不存在,则还要判断当前是否有其他线程已经发起了远程调用操作获取新值,如果有,自己就简单的等待即可。

显然基于超时的常规模式比基于超时的简单模式更加优化,减少了超时时并发访问后端的调用量。

实现基于超时的常规模式就需要用到经典的Double-checked locking惯用法了。

3. 基于刷新的简单(stupid)模式

在T到达后,Cache中的Key和相应Value不动,但是如果有线程调用get操作,将触发refresh操作,根据get和refresh的同步关系,又分为两种模式:

  • 同步模式:任何线程发现Key过期,都触发一次refresh操作,get操作等待refresh操作结束,refresh结束后,get操作返回当前Cache中Key对应的Value。注意refresh操作结束并不意味着refresh成功,还可能抛了异常,没有更新Cache,但是get操作不管,get操作返回的值可能是旧值。
  • 异步模式:任何线程发现Key过期,都触发一次refresh操作,get操作触发refresh操作,不等refresh完成,直接返回Cache中的旧值。

举上面码头工人的例子说明基于刷新的常规模式:这次还是5工人去港口取货,这时货都在,但是已经旧了,这时5个工人有两种选择:

  • 5个人各自去远程取新货,如果取货失败,则拿着旧货返回(同步模式)
  • 5个人各自通知5个雇佣工去取新货,5个工人拿着旧货先回(异步模式)

4. 基于刷新的常规模式

在T到达后,Cache中的Key和相应Value都不会被清除,而是被标记为旧数据,如果有线程调用get操作,将触发refresh更新操作,根据get和refresh的同步关系,又分为两种模式:

  • 同步模式:get操作等待refresh操作结束,refresh结束后,get操作返回当前Cache中Key对应的Value,注意:refresh操作结束并不意味着refresh成功,还可能抛了异常,没有更新Cache,但是get操作不管,get操作返回的值可能是旧值。如果其他线程进行get操作,Key已经过期,并且发现有线程触发了refresh操作,则自己不等refresh完成直接返回旧值。
  • 异步模式:get操作触发refresh操作,不等refresh完成,直接返回Cache中的旧值。如果其他线程进行get操作,发现Key已经过期,并且发现有线程触发了refresh操作,则自己不等refresh完成直接返回旧值。

再举上面码头工人的例子说明基于刷新的常规模式:这次还是5工人去港口取货,这时货都在,但是已经旧了,这时5个工人有两种选择:

  • 派一个人去远方港口取新货,其余4个人拿着旧货先回(同步模式)。
  • 5个人通知一个雇佣工去远方取新货,5个人都拿着旧货先回(异步模式)。

基于刷新的简单模式和基于刷新的常规模式区别就在于取数线程之间能否感知当前数据是否正处在刷新状态,因为基于刷新的简单模式中取数线程无法感知当前过期数据是否正处在刷新状态,所以每个取数线程都会触发一个刷新操作,造成一定的线程资源浪费。

而基于超时的常规模式和基于刷新的常规模式区别在于前者过期数据将不能对外访问,所以一旦数据过期,各线程要么拿到数据,要么抛出异常;后者过期数据可以对外访问,所以一旦数据过期,各线程要么拿到新数据,要么拿到旧数据。

5. 基于刷新的续费模式

该模式和基于刷新的常规模式唯一的区别在于refresh操作超时或失败的处理上。在基于刷新的常规模式中,refresh操作超时或失败时抛出异常,Cache中的相应Key-Value还是旧值,这样下一个get操作到来时又会触发一次refresh操作。

在基于刷新的续费模式中,如果refresh操作失败,那么refresh将把旧值当成新值返回,这样就相当于旧值又被续费了T时间,后续T时间内get操作将取到这个续费的旧值而不会触发refresh操作。

基于刷新的续费模式也像常规模式那样分为同步模式和异步模式,不再赘述。

下面讨论这5种Cache get模式在服务过载发生时的表现,首先假设如下:

  • 假设A系统的访问量为每分钟M次。
  • 假设Cache能存Key为C个,并且Key空间有N个。
  • 假设正常状态下,B系统访问量为每分钟W次,显然W\<\<M。

这时因为某种原因,比如B长时间故障,造成Cache中得Key全部过期,B系统这时从故障中恢复,五种get模式分析表现分析如下:

  1. 在基于超时和刷新的简单模式中,B系统的瞬间流量将达到和A的瞬时流量M大体等同,相当于Cache被击穿。这就发生了服务过载,这时刚刚恢复的B系统将肯定会被大流量压垮。
  2. 在基于超时和刷新的常规模式中,B系统的瞬间流量将和Cache中Key空间N大体等同。这时是否发生服务过载,就要看Key空间N是否超过B系统的流量上限了。
  3. 在基于刷新的续费模式中,B系统的瞬间流量为W,和正常情况相同而不会发生服务过载。实际上,在基于刷新的续费模式中,不存在Cache Key全部过期的情况,就算把B系统永久性地干掉,A系统的Cache也会基于旧值长久的平稳运行。

第3点,B系统不会发生服务过载的主要原因是基于刷新的续费模式下不会出现chache中的Key全部长时间过期的情况,即使B系统长时间不可用,基于刷新的续费模式也会在一个过期周期内把旧值当成新值继续使用。所以当B系统恢复时,A系统的Cache都处在正常工作状态。

从B系统的角度看,能够抵抗服务过载的基于刷新的续费模式最优。

从A系统的角度看,由于一般情况下A系统是一个高访问量的在线web应用,这种应用最讨厌的一个词就是“线程等待”,因此基于刷新的各种异步模式较优。

综合考虑,基于刷新的异步续费模式是首选

然而凡是有利就有弊,有两点需要注意的地方:

  1. 基于刷新模式最大的缺点是Key-Value一旦放入Cache就不会被清除,每次更新也是新值覆盖旧值,JVM GC永远无法对其进行垃圾收集,而基于超时的模式中,Key-Value超时后如果新的访问没有到来,内存是可以被GC垃圾回收的。所以如果你使用的是寸土寸金的本地内存做Cache就要小心了。
  2. 基于刷新的续费模式需要做好监控,不然有可能Cache中的值已经和真实的值相差很远了,应用还以为是新值而使用。

关于具体的Cache,来自Google的Guava本地缓存库支持上文的第二种、第四种和第五种get操作模式。

但是对于Redis等分布式缓存,只提供原始的get、set方法,而提供的get仅仅是获取,与上文提到的五种get操作模式不是一个概念。开发者想用这五种get操作模式的话不得不自己封装和实现。

五种get操作模式中,基于超时和刷新的简单模式是实现起来最简单的模式,但遗憾的是这两种模式对服务过载完全无免疫力,这可能也是服务过载在大量依赖缓存的系统中频繁发生的一个重要原因吧。

本文之所以把第1、3种模式称为stupid模式,是想强调这种模式应该尽量避免,Guava里面根本没有这种模式,而Redis只提供简单的读写操作,很容易就把系统实现成了这种方式。

应对分布式Cache宕机

如果是Cache直接挂了,那么就算是基于刷新的异步续费模式也无能为力了。这时A系统铁定无法对Cache进行存取操作,只能将流量完全打到B系统,B系统面对服务过载在劫难逃......

本节讨论的预防Cache宕机仅限于分布式Cache,因为本地Cache一般和A系统应用共享内存和进程,本地Cache挂了A系统也挂了,不会出现本地Cache挂了而A系统应用正常的情况。

首先,A系统请求线程检查分布式Cache状态,如果无应答则说明分布式Cache挂了,则转向请求B系统,这样一来大流量将压垮B系统。这时可选的方案如下:

  1. A系统的当前线程不请求B系统,而是打个日志并设置一个默认值。
  2. A系统的当前线程按照一定概率决定是否请求B系统。
  3. A系统的当前线程检查B系统运行情况,如果良好则请求B系统。

方案1最简单,A系统知道如果没有Cache,B系统可能扛不住自己的全部流量,索性不请求B系统,等待Cache恢复。但这时B系统利用率为0,显然不是最优方案,而且当请求的Value不容易设置默认值时,这个方案就不行了。

方案2可以让一部分线程请求B系统,这部分请求肯定能被B系统hold住。可以保守的设置这个概率 u =(B系统的平均流量)/(A系统的峰值流量)

方案3是一种更为智能的方案,如果B系统运行良好,当前线程请求;如果B系统过载,则不请求,这样A系统将让B系统处于一种宕机与不宕机的临界状态,最大限度挖掘B系统性能。这种方案要求B系统提供一个性能评估接口返回Yes和No,Yes表示B系统良好,可以请求;No表示B系统情况不妙,不要请求。这个接口将被频繁调用,必须高效。

方案3的关键在于如何评估一个系统的运行状况。一个系统中当前主机的性能参数有CPU负载、内存使用率、Swap使用率、GC频率和GC时间、各个接口平均响应时间等,性能评估接口需要根据这些参数返回Yes或者No,是不是机器学习里的二分类问题?

相关资源推荐
  • 小谈后台服务过载、雪崩、过载保护的实际案例(这已经不是我第一次遇到真实雪崩案例了)

    在前面的博文中, 我们聊过系统过载、雪崩和过载保护, 今天继续聊聊, 顺便看看一个实例。       过载: 超过系统负载。        雪崩: 系统过载导致不可用,系统对外的服务能力通常急剧降为零。       过载保护: 剔除过载或者已经超时(过时)的请求,从而保证系统尽可能能用。       举个例子:       你每天只能处理1项任务, 然后领导每天给你塞1项任务,
  • Cache应用中的服务过载案例研究

    前言 简单地说,过载是外部请求对系统的访问量突然激增,造成请求堆积,服务不可用,最终导致系统崩溃。本文主要分析引入Cache可能造成的服务过载,并讨论相关的预防、恢复策略。Cache在现代系统中使用广泛,由此引入的服务过载隐患无处不在,但却非常隐蔽,容易被忽视。本文希望能为开发者在设计和编写相关类型应用,以及服务过载发生处理时能够有章可循。 一个服务过载案例
  • 服务过载保护(下篇)——过载处理新方案
  • 浅尝服务过载保护

    浅尝服务过载保护     首先,我们要对过载保护做一个简单的了解和认识。     功能和过载保护类似的还有柔性服务。有些人也叫作有损服务。我谈一下自己对于过载保护的简单理解吧!在我看来过载保护有两个重要的点,一个是不要被别人拖垮了。一个是认怂。     首先,说一下什么叫做过载。所谓的过载就是超出了服务本身的服务能力。达到服务运行的瓶颈了,用户体验已经崩溃了。这里举点例子来说说明过载的情况吧
  • 关于HTTP 503服务过载问题

      项目还有两个月就要上线了,现在在客户这边一边测试一边开发,一切都平安无事。可就在上个星期出现了个HTTP 503错误,上网一查是服务器超载,怎么会超载呢?服务器是IBM得,系统是Unix AIX,数据库是DB2 Enterprise Server Edition V8.2,应用服务器是IBM Websphere5.1,不应该出现服务器超载啊。赶快联系公司外派客户这边专门负责服务器得同事,给
  • 案例研究:设计与方法-罗伯特.K.殷.pdf

    罗伯特·殷的经典著作,案例研究的入门书籍,学习案例研究必读经典。
  • 案例研究设计与方法中英两本书
  • 案例研究方法的应用

    案例研究方法的应用 MBA案例研究的写作方法及案例研究方法的应用参考。
  • 服务过载保护(上篇)——过载介绍

    过载的定义看似简单,但却是处理过载问题的关键。
  • 系统过载及保护的思考

    家用电器为了防止电流过大,都会有保险装置,当电流过大时,自动切断电流,防止电器损坏, 防洪大坝的水位超过了警戒线,会开闸泄洪,防止大坝崩溃. 而我们的服务系统如果一旦流量过大(用户或请求超过其处理能力),很多情况下,都把自己搞挂了,很显然,我们设计系统时没有像我们的工业及建筑领域那样成熟的引入自我保护机制.稍不注意就把自己搞挂了.      一个系统的处理能力是有限的,就如一个车站在车次一定的情
  • https://download.csdn.net/download/jiangdmdr/8547745
  • 服务限频与降级

    什么是限频和服务降级 ? 要保证一个大流量对外服务的稳定性, 通常我们很相当注意两个功能控制…  一个是请求的限流,一个是服务降级处理,他的意义在于不会让你的服务全瘫痪了,你可以适当的损失点东西利益,来保证最基础的功能, 这就是过载保护. 每个接口所能提供的单位时间服务能力是有限的。超过服务服务的承载能力,一般会造成整个接口服务停顿,或者应用 Crash毁掉,或者带
  • ArcGIS第一个应用--经验总结

     研究成果 一、3.16是最新的稳定版。小数点后是序号,之前误认为3.3版本比3.16大,其实不然16要比3大。 二、?f=jsapi 可以用url参数,以不同的数据形式访问发布的地图服务。 下面地址可以直接以地图形式查看 http://114.112.57.122:6080/arcgis/rest/services/1400demo/MapServer?f=js
  • Nginx反向代理,健康状态检测,过载保护及配置文件详解

    简介 Nginx("engine x")是一个高性能的HTTP和反向代理 服务器,也是一个IMAP/POP3/SMTP 代理服务器。Nginx 是由Igor Sysoev为俄罗斯访问量第二的Rambler.ru站点开发的,第一个公开版本0.1.0发布于2004年10月4日。其将源代码以类BSD许可证的形式发布,因它的稳定性、占有内存少,并发能力强、丰富的功能集、示例配置文件和低系统资源的
  • 供应链设计与管理:概念、战略与案例研究课后答案-英文.pdf

    供应链设计与管理:概念、战略与案例研究++课后答案-英文
  • WebLogic自保护之一:让WebLogic免于过载之苦

    在系统容量达到极限之时,如果中间件仍旧持续接受请求,则必然会导致应用的稳定性和性能急转直下。WebLogic Server从9系起引入了过载保护这一特性,致力于避免业务系统在已经达到容量极限的前提下依然接受客户端请求。本文以WebLogic 10g R3为例,对此特性进行讨论。 过载保护的几种实现手段 Ø       配置线程池,限制请求接入 WebLogic Server从9系
  • asp.net中如何管理cache

    asp.net中如何管理cache,cache应用
  • https://blog.csdn.net/mikeszhang/article/details/47728167

    雪崩是指平时正常调用和被调用的A系统和B系统,突然A系统对B系统的访问超出B系统的承受能力,造成B系统崩溃。 注意将雪崩效应与拒绝服务攻击相区别:两者都是因为B系统过载导致崩溃,但后者是人为的蓄意攻击。 注意将雪崩效应与访问量激增区别:如果A系统直接面对用户,那么激增的用户将直接带来A系统和B系统的流量激增,不过这种case可以通过预估而对A系统和B系统做扩容应对。 一种雪崩case 如果
  • Java重写(覆盖)、重载、过载
  • 基于cache过载问题解决模式

    假设系统A依赖于系统B,同时为了提高访问效率,A系统在本地设置系统B的cache,其过期时间为t。当cache失效时的策略如下: 1)基于超时的常规模式: 单统程请求,其他线程等待 在t到达后,Cache中的Key和对应Value将被清除,get操作将通过RPC获取B 系统的Key对应的Value,并更新本地Cache。 此时,如果另一个线程发现cache过期,get操作先判断有没有其他线程......
Tags: cache&redis