上次讲到lru与缓存重建,这次主要讲一下关于过期处理的一些主要问题。在讨论这个问题之前,有个相关的问题需要大家有所了解。就是对于一个缓存如期只来说,什么东西应该缓存,什么不应该缓存。这是一个比较复杂的问题,涉及到http协议的诸多细节。这里赵永明大哥写了一篇文章,讲得很详细,虽然是以ATS为背景讲的,但是思路是想通的,大家可以点击这里去看一下,文章名字很骚气叫“to cache or not to cache,一直是个大问题”

在缓存服务器里,分hit和miss两种行为。前面的文章已经讲过了,服务器本地有缓存叫hit处理(也会因为if-modified-since转成miss处理,这个后面讲),无缓存是miss处理。过期处理自然发生的本地有相应文件的基础上,miss情况下根本没有校验文件过期与否的动作可言。这里我以我们cache服务器为例讲一下基本的过期处理流程,当然开源的squid,ats之类的在这块也是大同小异。

一个文件缓存与否,包括缓存多长时间,通常取决于取源返回的响应中关于缓存相关的一些头部,例如:Expires,Cache-Control,Pragma, Last-Modified,Etag, Age,Vary等。这些头的解释,大家可以去翻看协议,不想翻协议的,可以从这里找到一个总结,这里就不浪费口舌了。

但是有一种情况必须考虑,对于CDN来说,他们服务的客户运营水平参差不齐,很多都没有相关的响应头来告诉CDN厂商,他们想缓存多长时间。通常在这种情况下,CDN厂商有这样一些规则,要么缓存指定的一段时间,这个时间CDN厂商自己控制。要么针对不同的文件后缀,对缓存时间做更细致化的控制,这种情况就不再讨论了。我们接下来重点讨论有正规缓存头部的情况。

当请求到来,cache服务器在本地找到了相应的文件,这里所谓的“找到的文件”多数都是所谓的文件在内存中的索引结构,因为这个结构中通常包括对应磁盘文件的所有信息。为了方便讨论,我们这里暂且把这个索引结构叫做store。

首先store结构中有一个成员,我们暂且叫cache_time,它记录的是缓存服务器在最开始存储这个文件时的时间,通俗点说就是取源回来开始缓存这个文件,就把当前时间记录在cache_time这个成员里。也可以说这个时间认为是缓存开始在本地构建这个文件的时间。

在store结构中通常有一个成员来保存源响应头中Date字段对应的时间戳,我们暂且将这个成员叫做backend_date,这个头一般是后端服务器在发送响应时拿它的当前时间戳构建的。

为了方便讨论,我们把缓存服务器的当前时间称为current_time。

  • 首先处理Cache-Control的相关头部。如果cache_time+ max-age < current_time,说明这个文件过期了。这里的max-age是Cache-Control中的一个字段。否则当前的验证说明未过期,需要进一步检查其他过期信息。

  • 如果存在源响应带有Expires头,那么比较expires - backend_date与current_time - cache_time的大小。首先前者表示这个文件在源服务器还有多长时间过期,后者表示这个文件在本机创建,当现在已经过去了多长时间。如果前者大于后者,意味文件可以继续使用,未过期。当然这是基于第一点中max-age检查,未过期的基础之上的。如果前者小于后者呢?我们就认为是过期了,但是会出现这种情况吗?后者的时间差来自于本地服务器,我们假定是准确的,但是前者的时间来自于其他后端服务器,不一定准确,就可能出现这种情况。出现了这种情况,只能当做过期处理。我们也碰到过自己的机器时间不准了导致文件异常过期的bug。所以缓存服务器通常在启动时要确认机器时间是否准确,这点很重要。

在以上这里点检查通过之后,未标记为过期的,会进到后续的hit处理流程。过期的就需要取源。所以取源有两种,第一种是本地没有缓存该文件,另一种是文件已经缓存过,但是现在过期了。过期回源是,涉及到回源验证的问题。什么是回源验证?当一个文件过期了,你回源重新取的时候,可能这个文件在源服务器上并没有改动过,那么这时缓存服务器再取一遍是很傻,也是浪费带宽,降低性能的事情。这个时候,在store结构中有个成员往往保存了一个时间戳,我们暂且叫做mod_time,这个时间戳来自于最开始构建文件时,源给的响应中的Last-Modified头,这个头中的时间告诉了我们,在我们取源获取这个文件时,该文件在源服务器最后一次修改的时间。那么我们在回源验证时,将这个mod_time放到我们取源构建的请求头的If-Modified-Since中,源收到这个头之后,就会去查看文件在这个时间以后,有没有被修改过,如果修改过,那么源通过回应304响应,告知下游文件未发送变化,文件可以重用。否则,就通过200响应,发送最新文件。

一般的情况,缓存服务器都会把响应头跟响应体保存在一个文件中。接下来作为hit处理的后续流程,往往需要先去读取保存文件中的响应头,其中我们需要关心的是Last-Modified头。因为客户端可能携带着If-Modified-Since头,我们需要对比两者是否相等,如果相等,那么我们认为文件在客户端询问的时候之后没有改变过,我们给他304响应。这里有两个问题:

  1. 为什么不把Last-Modified头放在store结构中,这样我们就不需要去读文件来获取这个头了。

    因为不同于一般的webserver,cache服务器给出的正常响应必须忠于源给出的响应,而很少有权利自己打包响应头,除非本机发生像5xx等这类情况,所以既然一定要读文件,就没有必要单独拿出来了。其次store结构作为每个文件的索引,会占用大量的内存。所以减少哪怕一个成员,在大量文件存储时,也能节省不少空间。

  2. 如果客户端的If-Modified-Since跟我们的Last-Modified不相等,而是大于或者小于,该怎么处理?

    如果前者小于后者,毫无疑问应该回应200,因为我们的最后修改时间比客户端问的时间要新。如果大于呢?我们是否应该去回源验证呢?这种情况我们直接使用本地文件响应200处理。很多人认为这样处理不太合理,毕竟你作为缓存代理,无法知道在“未来”的时间点上,源有没有改动过文件。但是如果你去做回源验证,别人可能因此攻击你,每次都发带有If-Modified-Since将来很长时间的一个时间点,让你的缓存产生回源,极大的消耗你的性能,因为作为cache,减少回源是最核心的功能。CDN厂商之间的相互攻击早已不是什么新鲜事,我们都是交过学费的。

上述提到的这些只是一种最简单的情景。即client -- cache  -- origin,缓存直接跟源站交互。但是如果cache是分级的,比如client -- cache1 -- cache2 -- origin这种情况。如果我们cache2对origin给的响应头不做任何处理直接转发给cache1,那么对于cache1来说,他收到的结果跟cache2感知的是一样的,那么原来cache2将某文件缓存了多长时间,到什么时间过期这些信息,在cache1这边看来是一样的。这样就会出现问题。比如origin告诉cache2,最大缓存时间max-age为1个小时,那么cache2在缓存了半个小时的时候,收到了cache1的请求,这时cache2将该文件发送给它,那么对于cache1来说,它也会把文件缓存一个小时。这样一个本应该在CDN中缓存1小时的文件,却被缓存了至少(1小时+半小时)长的时间。对于使用了CDN的origin来说,不管CDN服务商的cache是几级,架构是什么样,1小时的缓存时长是绝对的,不允许被CDN放大。这种情况下CDN必须采取一定的措施来规避这个问题。

一般情况下origin在响应头中携带的Expires,Date这些跟缓存时间相关的头是不允许修改的。除非origin有些个性化的需求,主动告知CDN去做哪些修改。其实这是基本的规则,特别是CDN在做反向代理的时候,应该对客户端来说是透明的,不能说请求经过CDN和不经过,origin的某些原始响应头会发生变化。那怎么办?最基本的就是使用Age这个头,不过这个头在CDN里用的比较少。因为这个头是在HTTP1.1里才支持的,对于现阶段网络上大量HTTP1.0的请求来说,这个头就没法用了。所以CDN往往干脆自己加一个私有的头来替代Age的作用。Age其实就是记录一个文件在一个server上从产生到现在,经过了多长时间,所以字如其名,“年龄”的意思。除了这个头这外还需要另外一个相对的max-age。什么叫相对的max-age?我们可以认为origin给出的max-age是CDN系统内存活的绝对时间,而在一个多级cache的缓存系统里,当前cache中文件能存活的最大时间,应该是绝对的max-age时间减掉在上级(多层)cache已经存活过的时间,得到的差值就可以叫做相对max-age。

​一般CDN自己定义的头通常都使用X-xxx这样的形式,所以这里我们就把相对的max-age时间用X-max-age来表示,Age时间用X-age来表示,origin给max-age用Max-Age。这样每层cache在发送响应头的时候应该携带X-max-age,即X-max-age(要发给下级的)=X-max-age(上级给的) - X-age。在这个公式里,如果上级没有给X-max-age,那么意味这当前cache的上级是origin,那么此时等式变为 X-max-age = Max-Age(origin给的) - X-age。至于X-age的计算就很简单了,用前面提到current_time - cache_time即可。

这里需要注意一点,如果cache被杀掉重启,那么X-max-age,X-age这些信息应该是延续的,而不能重置。所以这就要求cache的设计者把这些信息持久化,只是简单的留在内存中是不行的。

缓存服务器设计与实现(五)相关推荐

  1. 缓存服务器设计与实现(六)

    本文讲缓存中的内容管理–文件的删除. 基本原理 缓存系统中的文件,从无到有是被动产生的.初始状态,缓存系统中是空的,请求过来之后,缓存会回源取,然后存在本地.而不像web服务器,文件是通过其他的手段( ...

  2. 分布式缓存服务器设计原理

    1.数据是如何被分布到多个服务器上的?(一致性哈希算法) 假设有n台服务器, 计算这n台服务器的IP地址的哈希值, 把这些哈希值从小到大按顺时针排列组成一个"服务器节点环", 客户 ...

  3. 图解|高性能服务器设计之缓存系统一致性

    缓存系统交互 缓存系统设计是后端开发人员的必备技能,也是实现高并发的重要武器. 对于读多写少的场景,我们通常使用内存型数据库作为缓存,关系型数据库作为主存储,从而形成两层相互依赖的存储体系. 共识:我 ...

  4. 编程方式刷新Squid缓存服务器的五种方法

    网站进行内容更新是常有的事情,当被缓存的资源更新时,前端Squid 缓存服务器内容也必须要相应的更新,否则用户就可能会看到过期的数据.当没有程序支持时就需要每次登录到服务器上执行刷新操作,在服务器数量 ...

  5. 朱晔的互联网架构实践心得S1E9:架构评审一百问和设计文档五要素

    朱晔的互联网架构实践心得S1E9:架构评审一百问和设计文档五要素 [下载文本PDF进行阅读] 本文我会来说说我认为架构评审中应该看的一些点,以及我写设计文档的一些心得.助你在架构评审中过五关斩六将,助 ...

  6. 高性能缓存服务器Varnish详解

    一.简介 Varnish是一款高性能的开源HTTP加速器,挪威最大的在线报纸 Verdens Gang 使用3台Varnish代替了原来的12台Squid,性能比以前更好. Varnish 的作者Po ...

  7. Redis_17_Redis服务器中的数据库(五种基本类型底层存放)

    文章目录 一.前言 二.RedisObject对象 2.1 RedisObject对象 2.2 类型type 2.3 编码encoding 2.4 sds 三.字符串对象string 3.1 int编 ...

  8. 其实一切与游戏无关--yy笔录+转载网络游戏服务器设计

    严格的说,我从来没有玩过网络游戏,对于网游的理解仅限于大学时其他室友之间关于魔兽世界的经验交流.曾经yy过网游的后台实现方法,但现在回想起来那时的想法确实幼稚的很.两个月打酱油般的工作当中给我最深的体 ...

  9. 游戏服务器设计(转)

    有段时间没有研究技术了,这次正好看到了新版的mangos,较之以前我看的版本有了比较大的完善,于是再次浏览了下他的代码,也借此机会整理下我在游戏服务器开发方面的一些心得,与大家探讨. 另外由于为避免与 ...

最新文章

  1. 在tomcat中部署web项目
  2. Android apk动态加载机制的研究(二):资源加载和activity生命周期管理
  3. Science nature合集 2021年度上半年
  4. python-分页代码
  5. mac部署文件服务器,MAC 搭建本地服务器
  6. skywalking 安装_SkyWalking全链路追踪利器
  7. mysql数据库连接锁住_锁mysql方法
  8. 什么叫做石英表_什么是石英表 石英表是什么意思
  9. 未能加载nStuff.ScriptSharp.Web.dll
  10. Nginx设置expires设定页面缓存时间
  11. Web前端开发规范 之html命名规范
  12. 记录:uniapp微信小程序通过高德api获取当前详细的地理位置信息
  13. 裸看美剧必备英文词汇
  14. 三维闭合B样条曲线拟合算法Matlab代码
  15. Android动态生成答题卡,好分数网怎么制作答题卡
  16. ACM的奇计淫巧系列
  17. STM32第二十一课(USB SLAVE, HAL)
  18. 慎用!wordpress的额外css功能会浪费id资源!
  19. gb 28181的20位编码简介
  20. apple watch3连android,无需艳羡苹果党的Apple Watch 3 这款安卓通话神器亮了

热门文章

  1. PS软件打开闪退是什么原因?怎么处理闪退的问题?
  2. 罗爷:创新法律服务交易模式 找到你的专属lawyer
  3. 5G NGC — CHF 融合计费
  4. vue使用element-ui中日期选择器 (el-date-picker) 出现报错
  5. 关于积分过期的设计思路方案(原创)
  6. php中的stripos,php字符串函数stripos()的定义与用法
  7. NOIP 2013 提高组初赛 青蛙跳荷叶
  8. 毕竟几人真得鹿,不知终日梦为鱼
  9. 发展,需求驱动 #183; 一间 所见即所得
  10. 深度学习8 keras中triplet network的搭建