`

Web开发基本准则-55实录-缓存策略(转)

 
阅读更多

原文地址:http://www.cnblogs.com/zhengyun_ustc/p/rule2.html

续上篇《Web开发基本准则-55实录-Web访问安全》。

Web开发基本准则-55实录-缓存策略

郑昀 创建于2013年2月
郑昀 最后更新于2013年10月26日

提纲:
  1. Web访问安全
  2. 缓存策略
  3. 存储介质连接池
  4. 业务降级
  5. 并发请求的处理

关键词:
会话串号,Cache-Control头域,缓存穿透,缓存集体失效,缓存重建,缓存雪崩,缓存永不过期,缓存计数器,

 
二,缓存策略
  这里的“缓存”概念不只限于服务器端的“缓存”。
 
2.1.防会话串号
  如果你收到一个投诉,说访问“我的个人中心”页面时进入其他人的帐号,至少订单列表上显示的不是自己的。此时,技术支持人员可以提三个问题,第一,对页面上显示的信息是否有操作权限,如取消订单;第二,浏览器地址栏上给URL增加访问参数,如追加一个&111之类的字符串,看看页面是否还是显示别人的信息;第三,投诉者上网接入方式是什么,如铁通光纤宽带,如通过某款代理软件上网。
  如果既无操作权限,追加URL参数后又能看到自己的帐号信息或页面提示处于未登录状态,那么说明是URL已被各级 HTTP Proxies 缓存
  即在服务器端收到 Request 之前,网络链路上的某一级代理已返回缓存数据
  
2.1.1.简单办法,如利用Expiration Model
  第一种:如果页面 Response 里设置了正确的 Last-modified 和 Expires 头域,那么 基本过期模型 已经能正常运转了,因此,头域里的 Cache-Control:private 声明就已经够了,HTTP Caches 和 User Agent 都会根据这两个字段检查缓存网页是否陈旧。
  第二种:重要页面的URL上加时间戳参数。
  第三种:像淘宝博文[注1]所描述的:“cookie 里增加一个值,用来记录通过关键 cookie 计算出来的签名,这个签名的算法非常简单。每次请求到服务端的时候 session 框架代码里会对此签名进行匹配,如果和服务端获取的数据签名出来的值是一致的,则认为合法,否则清空 session 信息和 cookie 信息,让用户重新登录。”
 
2.1.2.需要有更多背景知识的办法:利用 Cache-Control 头域控制
  Web开发工程师都需要了解 Cache-control 头域背后的 HTTP 1.1缓存控制机制和缓存重验证机制。
 
  先说处理办法是:含个人敏感信息的网页响应头里,声明 Cache-Control:must-revalidate,proxy-revalidate,no-store,private,no-cache 即可。
 
  下面简单地介绍一下背景知识,详细信息请阅读 HTTP/1.1 RFC2616, section 13 HTTP里的缓存 和 HTTP/1.1 RFC2616, section 14 头域定义,或找到 HTTP 1.1协议中文版阅读。
  HTTP1.1协议定义,Response 是可以被各种 HTTP caches 缓存的。
  除非有 Cache-Control 控制指令的特殊约定,否则从浏览器端到源服务器(origin server)端之间链路上所存在的各种 Caching System 都完全有可能缓存一个成功的 response:
  如果这个 cache entry 是 fresh 的,可能不会(去源服务器端)校验直接返回;也可能会做一个校验再返回。
  一个状态码是200, 203, 206, 300, 301, 410的 response,可能会被缓存。
 
2.2.缓存穿透
  • 目的
    • 防止访问(短期内)必然不存在的数据导致请求穿透缓存直接打到 DB。
  • 原因
    • 可能是数据真的不存在,但也可能是第三方恶意构造大量不存在的 id 来冲击 DB
  • 多种手段结合
    • 『存储EMPTY』思路存储一个 EMPTY 对象到缓存对应键值,设置一个较短的过期时间。这样在缓存失效后,还能继续查询数据是否存在。
    • 必须认真对待(不同业务不同端口的)缓存命中率(get_hits/cmd_get * 100)定期监控的结果,认真审视那些命中率低的缓存端口,找到命中率低的原因,提出优化方案。
    • 『先行校验』思想采用布隆过滤器算法,将所有可能存在的数据(如所有有效商品的id)哈希到一个足够大的 bitmap 里,那么一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力。(出处
 
2.3.”半缓存“策略
  缓存命中率低,其中一个原因是,你缓存的数据被人访问间隔长、几率低,于是在下次访问到来之前缓存早已失效。命中率低,为我们指出了优化方向。
  如,用户在查询一个列表页时,我们可以把前6页的数据缓存起来,再往后的页码,访问频次很低,也许就不需要缓存了。(出处
 
2.4.缓存集体失效
  以下原因都会导致缓存集体失效,从而引发系统”抖动“甚至”雪崩“:
  • 系统预热数据的缓存过期时间过于整齐划一;
  • 缓存系统宕机或重启;
  • 访问高峰期间种下了一大批缓存,过期时间非常接近。
  处理手段:
  • 缓存过期时间散列开:在过期时间基础上增加一个随机值,如1秒~120秒随机,将大家的过期时间尽量打散。
  • 防范缓存节点暂不可用的缓存双写策略
    • memcache双写:向 memcahce 的 Master Ring 和 Backup Ring 双写,如下图1所示:
    • http://images.cnblogs.com/cnblogs_com/zhengyun_ustc/255879/o_clipboard%20-45.png 图1 memcache 双写 原图出自点评技术PPT
    • Redis备份写:向 memcache 写入的同时,写一份到备份缓存 Redis 里,键值的缓存过期时间非常大,如原键值在 memcache 过期时间5分钟,在 Redis 里则8小时过期。当 memcache 集群节点暂不可用时,Web工程就切换读取备用缓存 Redis。这种思路是保证基本可用性,所以必要时刻可以给用户返回脏数据。
  • 对于不同的业务场景,缓存的使用策略也不同
    • 当系统面临缓存异常的危险时,有些系统可以采用备份方案继续支撑服务。有些系统则会优雅降级,将某些依赖缓存的功能直接去除,保证主服务的正确性。所以这两种策略的选择需要根据实际的业务场景考虑并实施。(出处
 
2.5.分级缓存
  有些业务场景里,应该把 DB 当成仅是一个存储而已,靠分级缓存策略来层层抵挡缓存失效,不让请求打到 DB。
  • 手段:
    • 由远及近分层建立缓存,越靠近前端,缓存片段越大(或存储粒度越大)。
    • 上一层的缓存失效,可以靠下一级的缓存迅速重建。
  • 目的:
    • 避免系统产生抖动。
    • 减少缓存雪崩,防止 DB 连接数暴涨、响应变慢,连累前端应用连接数持续高涨、最后宕机。
http://images.cnblogs.com/cnblogs_com/zhengyun_ustc/255879/o_clipboard%20-%2046.png
图2 缓存控制体系(图出自 http://www.alidata.org/archives/1789 )
 
2.6.缓存重建
  既然有缓存过期,自然有缓存重建。
  热点数据的缓存重建,无论是本地缓存还是远端缓存,都有必要加锁来确保进程内同一时刻只有一个 Worker 负责重建,甚至利用分布式锁保证集群环境下只有一个重建者,避免缓存雪崩时的 Race Condition。TimYang 早在2010年在《Memcache mutex设计模式》中描述过如下风险:”在大并发的场合,当cache失效时,大量并发同时取不到cache,会同一瞬间去访问db并回设cache,可能会给系统带来潜在的超负荷风险。我们曾经在线上系统出现过类似故障“孙立将这种场景称为 cache key mutex 问题[注7]。
http://pic002.cnblogs.com/img/sunli/201009/2010090709371444.jpg
图3 cache key mutex 问题的解决(图出自 http://www.cnblogs.com/sunli/archive/2010/07/27/cache_key_mutex.html)
  简而言之,缓存重建时,当多个 Client 对同一个缓存数据发起请求时,会在客户端采用加锁等待的方式,对同一个 CacheKey 的重建需要获取到相应的排他锁才行,只有拿到锁的 Client 才能访问数据库重建缓存,其他的 Client 都需要等待这个拿到锁的 Client 重建好缓存后直接读缓存。这样,对同一个缓存数据,只有一次数据库重建访问。但是如果访问分散比较严重,还是会瞬间对数据库造成非常大的压力。
 
  当然也可以不加(悲观)锁,那么多线程并发读写同一个 cache key 可能会带来“ABA问题”。
  解决方法很简单:memcached 1.2.5以及更高版本提供了 gets 和 cas 命令。如果使用 gets 命令查询某个键值,memcached 会返回该键值的唯一标识 casUnique。如果覆写了这个键值并想把它写回到 memcached 中,可以通过 cas 命令把那个 casUnique 一起发送给 memcached。如果该键值存放在 memcached 中的 casUnique 与提供的一致,写操作将会成功。如果另一个进程在这期间也修改了这个键值,那么该键值存放在 memcached 中的 casUnique 将会改变,写操作就会失败。
 
2.7.缓存永不过期
  因为担心缓存失效带来的系统抖动,所以有些业务场景会让缓存永不过期,数据变化时,由后端负责维护缓存数据一致性。
 
2.8.电商场景里的缓存计数器:秒杀和超卖
  我们在秒杀和防超卖场景里的实现逻辑类似于淘宝这篇博客[注3]所提及的”分布式缓存计数器“,所以我就直接照搬过来了:

    分布式缓存的另一个应用场景是缓存计数器。

    对于多服务器的系统,分布式缓存提供了统一的存储和原子操作,便于集群环境下的使用。库存计数器是分布式缓存的一个典型应用场景, 对于集群中的每一台机器,库存都应该是一个统一的值,因此使用本地缓存记录库存,数据肯定是不准确的(下面会陈述例外情况)。因此,统一的存储空间是必要 的条件。

    由于库存数据被多台机器共享,因此,必须使用锁机制控制多个请求的并行并发问题。基于这样的机制就可以实行库存技术器的作用,防止货物超卖。最近的积分商城超值兑换就是使用的这种机制。

    这种机制下,需要注意操作的逻辑顺序,错误的顺序会导致意想不到的结果。积分兑换的业务流程为,用户看到要抢兑的商品,如果库存大于0,则用户可以点击抢兑操作,这时用户会获得兑换该商品的权限,从而优惠购买,这时库存商品应该减一。

    如果完全按照这个业务流程,我们会完成下面这三步操作:

  • 验证库存是否大于0;
  • 给用户打标,使其获得优惠购买资格;
  • 获得资格后,原子减库存,记录用户购买记录。

    乍一看这样的逻辑是很正常的,但是考虑一下异常情况,就会发现它防不住超卖。如果库存只有一件,那么多个用户并发验证库存时,都大于0。这样并发的多个用户都会获得优惠资格,产生了超卖

    正确的逻辑为:

  • 验证库存是否大于0,小于0直接返回;
  • 原子减库存,返回的结果如果小于0说明已经没有库存,直接返回
  • 如果返回的当前库存大于等于0,为用户打标,如果打标成功,记录用户购买记录;如果打标失败,回补原子库存

    这样的方法,无法保证缓存中的值一定大于等于0,因为并发的发生会把缓存减为负数,但是,真正能够优惠购买的用户一定是小于等于库存数的。因为,每次原子减操作后,只有返回的库存值大于等于零的用户才能够获得购买资格无论并发量有多大,原子操作都会成功的防止超卖的发生

    对于上述的逻辑,可以应对绝大多数的情况。
    但是随着量的增加,这种方式也有风险。当用户量极大、货物的库存极少时,就变成了秒杀。这个时候,大量的用户涌入分布式缓存减库存,对分布式缓存有极大冲击,一旦分布式缓存挂掉,秒杀活动也就宣告失败。使用分布式缓存,目的是为了让用户准确的看到剩余库存数 目,秒杀活动非常快,用户还没有看清楚库存,活动就结束了。其实用户只关心有没有秒到商品,并不关心库存的剩余数量,因此,库存减得准不准确并不是主要矛盾,这时就可以放弃分布式缓存的设计,转而使用本地缓存存储库存数,这也就是本地缓存使用的第二个场景
    比如,一共有10件商品,2台机器,可以设置每台机器的本地内存中库存等于10,那么对于外网的千万个用户,就可以有20个人抢到商品,剩下的人都 被挡在库存之外。当这20个人抢到后,就可以实现另一个处理逻辑,从20个人中选出10个真正中标的人,获得10个商品的购买权限。这个选择的逻辑非常灵活,可随意定制。但是从20选10的操作,无论如何也比从千千万万个人中选10要好的多,这样可以确保秒杀的安全完成
    如果秒杀的人继续增多,那么也可以通过客户端(即javascript)设置格挡率的方法,使少量的用户可以发出请求到服务器,绝大多数的用户都被挡在浏览器上。(注:一些技术人士在2013年吐槽小米网站抢购小米手机时,浏览器模拟排队等待其实没有发出任何网络请求,这就是客户端格挡率生效的结果。)
 
-未完待续-
 

备注参考资源:
1,2013,淘宝中间件团队,说说会话串号
3,2013,淘宝搜索技术,关于缓存(上)
4,2013,范凯,Web应用的缓存设计模式
5,2012,kenny,Cache Reload机制设计和实现(防止cache失效引发雪崩) (注:只看他的故障现象即可);
6,2010,timyang,Memcache mutex设计模式
分享到:
评论

相关推荐

    Web开发基本准则-55实录-Web访问安全.pdf

    Web开发基本准则主要关注Web应用的安全性和性能优化。在Web访问安全方面,有几个核心概念和技术值得深入理解: 1. **CSRF(Cross-Site Request Forgery,跨站请求伪造)**:这是一种网络攻击方式,攻击者通过伪造...

    ASP.NET WEB开发学习实录-----源码.rar

    这个"ASP.NET WEB开发学习实录-----源码.rar"压缩包显然包含了某位开发者或者教师在教授ASP.NET Web开发过程中积累的源代码和相关材料,可能是为了帮助学习者理解ASP.NET的核心概念和实际应用。 首先,我们来看ASP...

    Python Web开发学习实录-源代码

    总的来说,这个"Python Web开发学习实录-源代码"的资源可能涵盖了从基本的Web开发概念到高级主题,如RESTful API设计、用户认证、会话管理、性能优化等。通过分析和实践这些源代码,你可以深入理解Python Web开发的...

    ASP.NET Web开发学习实录光盘

    韩啸、王瑞敬、刘健南编著的《ASP.NET Web开发学习实录(附光盘)》重点围绕Web开发,结合精选教学视频,全程推演ASENET 3.5应用开发的整个过程,引导读者深刻理解和掌握以ASP.NET从事Web应用开发所需要的基本知识和...

    MVC Web开发学习实录-配套源码.zip

    **MVC Web开发学习实录** 是一本深入探讨Web应用构建技术的书籍,重点在于Model-View-Controller(MVC)架构模式的应用。MVC是一种软件设计模式,它将应用程序的业务逻辑、用户界面和数据访问分离开来,使得各部分...

    MVC Web开发学习实录-1源码

    在本文中,我们将深入探讨MVC(Model-View-Controller)Web开发模式,结合"FirstMvcApplication"和"MvcApplication1"这两个项目源码的学习实录。MVC是一种广泛应用于Web开发的设计模式,它将应用程序的逻辑分为三个...

    web服务开发学习实录(光盘)

    学习如何在服务端生成和解析这两种格式的数据,以及在客户端如何与之交互,是Web服务开发的基本技能。 此外,你还需要掌握一种或多种开发工具,例如Apache Axis、JAX-WS、SOAPUI等,它们可以帮助你快速创建、调试和...

    ASP.NET Web开发学习实录_源码

    使用Visual Studio的调试工具进行问题排查,了解ASP.NET性能分析器,进行代码优化,如减少数据库查询次数、缓存策略、异步编程等,是提升Web应用性能的关键。 本"ASP.NET Web开发学习实录_源码"提供了丰富的实践...

    jsp web开发学习实录

    jsp web开发学习实录

    MVC Web开发学习实录

    在本" MVC Web开发学习实录"中,我们将会深入探讨这个框架,并通过VS2010的实践操作来深化理解。** **1. 模型(Model)** 模型是应用程序的核心,负责处理业务逻辑和数据管理。在MVC架构中,模型对象获取和存储应用...

    Struts 2 Web开发学习实录

    Struts 2 Web开发学习实录

    PYTHON WEB开发学习实录.pdf

    这份"PYTHON WEB开发学习实录.pdf"很可能是对这个领域的全面指南,旨在帮助初学者和有一定经验的开发者深入理解Python在Web开发中的应用。以下是该主题的一些核心知识点: 1. Python基础:在开始Web开发之前,理解...

    python web开发实录源代码

    "Python Web开发实录源代码"是针对这一主题的学习资料,通常包含一系列的示例项目和练习,旨在帮助开发者通过实践掌握Web开发的关键概念和技术。 首先,Python作为服务器端编程语言,其简洁的语法和强大的库支持...

    asp.net项目开发全程实录--张领

    《ASP.NET项目开发全程实录——张领》是一本深入探讨ASP.NET技术的实践性教程,作者张领通过六个实际的Web项目案例,系统地展示了ASP.NET在开发Web应用程序中的应用。这本书旨在帮助读者理解并掌握ASP.NET的核心概念...

    PHP Web开发学习实录

    《PHP Web开发学习实录》

    Python Web开发学习实录 高清

    《Python Web开发学习实录》完整版 高清扫描 PDF电子书

    《python web开发学习实录》.(李勇 王文强). PDF

    根据提供的信息,《python web开发学习实录》这本书主要面向对Python Web开发感兴趣的读者,特别是希望深入了解Python编程语言及其在Web开发领域应用的技术人员。虽然部分给出的内容似乎与实际书籍的内容不符,仅...

Global site tag (gtag.js) - Google Analytics