缓存server设计与实现(五)

上次讲到lru与缓存重建,这次主要讲一下关于过期处理的一些主要问题。

在讨论这个问题之前,有个相关的问题须要大家有所了解。

就是对于一个缓存如期仅仅来说,什么东西应该缓存,什么不应该缓存。这是一个比較复杂的问题。涉及到http协议的诸多细节。

这里赵永明大哥写了一篇文章。讲得非常具体,尽管是以ATS为背景讲的,可是思路是想通的,大家能够点击这里去看一下,文章名字非常骚气叫“to
cache or not to cache,一直是个大问题”

在缓存server里。分hit和miss两种行为。前面的文章已经讲过了。server本地有缓存叫hit处理(也会由于if-modified-since转成miss处理。这个后面讲),无缓存是miss处理。过期处理自然发生的本地有对应文件的基础上,miss情况下根本没有校验文件过期与否的动作可言。

这里我以我们cacheserver为例讲一下主要的过期处理流程,当然开源的squid,ats之类的在这块也是大同小异。

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

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

当请求到来,cacheserver在本地找到了相应的文件,这里所谓的“找到的文件”多数都是所谓的文件在内存中的索引结构,由于这个结构中通常包含相应磁盘文件的全部信息。

为了方便讨论,我们这里暂且把这个索引结构叫做store。

首先store结构中有一个成员。我们暂且叫cache_time,它记录的是缓存server在最開始存储这个文件时的时间,通俗点说就是取源回来開始缓存这个文件,就把当前时间记录在cache_time这个成员里。也能够说这个时间觉得是缓存開始在本地构建这个文件的时间。

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

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

  • 首先处理Cache-Control的相关头部。假设cache_time+ max-age < current_time,说明这个文件过期了。这里的max-age是Cache-Control中的一个字段。

    否则当前的验证说明未过期,须要进一步检查其它过期信息。

  • 假设存在源响应带有Expires头,那么比較expires - backend_date与current_time - cache_time的大小。

    首先前者表示这个文件在源server还有多长时间过期,后者表示这个文件在本机创建,当如今已经过去了多长时间。

    假设前者大于后者,意味文件能够继续使用,未过期。当然这是基于第一点中max-age检查。未过期的基础之上的。假设前者小于后者呢?我们就觉得是过期了。可是会出现这样的情况吗?后者的时间差来自于本地server,我们假定是准确的,可是前者的时间来自于其它后端server,不一定准确。就可能出现这样的情况。

    出现了这样的情况,仅仅能当做过期处理。我们也碰到过自己的机器时间不准了导致文件异常过期的bug。

    所以缓存server通常在启动时要确认机器时间是否准确,这点非常重要。

在以上这里点检查通过之后,未标记为过期的,会进到兴许的hit处理流程。过期的就须要取源。

所以取源有两种,第一种是本地没有缓存该文件,还有一种是文件已经缓存过,可是如今过期了。过期回源是,涉及到回源验证的问题。什么是回源验证?当一个文件过期了,你回源又一次取的时候,可能这个文件在源server上并没有改动过,那么这时缓存server再取一遍是非常傻,也是浪费带宽。减少性能的事情。

这个时候,在store结构中有个成员往往保存了一个时间戳,我们暂且叫做mod_time。这个时间戳来自于最開始构建文件时。源给的响应中的Last-Modified头,这个头中的时间告诉了我们。在我们取源获取这个文件时,该文件在源server最后一次改动的时间。

那么我们在回源验证时,将这个mod_time放到我们取源构建的请求头的If-Modified-Since中,源收到这个头之后。就会去查看文件在这个时间以后。有没有被改动过。假设改动过。那么源通过回应304响应。告知下游文件未发送变化,文件能够重用。否则。就通过200响应。发送最新文件。

一般的情况,缓存server都会把响应头跟响应体保存在一个文件里。接下来作为hit处理的兴许流程,往往须要先去读取保存文件里的响应头。当中我们须要关心的是Last-Modified头。由于client可能携带着If-Modified-Since头。我们须要对照两者是否相等,假设相等,那么我们觉得文件在client询问的时候之后没有改变过,我们给他304响应。这里有两个问题:

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

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

  2. 假设client的If-Modified-Since跟我们的Last-Modified不相等,而是大于或者小于,该怎么处理?

    假设前者小于后者,毫无疑问应该回应200,由于我们的最后修改时间比client问的时间要新。

    假设大于呢?我们是否应该去回源验证呢?这样的情况我们直接使用本地文件响应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在做反向代理的时候,应该对client来说是透明的,不能说请求经过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的计算就非常easy了。用前面提到current_time - cache_time就可以。

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

?

时间: 2024-10-09 14:49:23

缓存server设计与实现(五)的相关文章

分布式系统开发常见问题-1. session的复制与共享 2. 分布式缓存的设计

1. session的复制与共享 在web应用中,为了应对大规模访问,必须实现应用的集群部署.要实现集群部署主要需要实现session共享机制,使得多台应用服务器之间会话统一, tomcat等多数主流web服务器都采用了session复制以及实现session的共享. 但问题还是很明显的: 在节点持续增多的情况下,session复制带来的性能损失会快速增加.特别是当session中保存了较大的对象,而且对象变化较快时,性能下降更加显著.这种特性使得web应用的水平扩展受到了限制. session

缓存架构设计细节二三事

本文主要讨论这么几个问题: (1)"缓存与数据库"需求缘起 (2)"淘汰缓存"还是"更新缓存" (3)缓存和数据库的操作时序 (4)缓存和数据库架构简析 一.需求缘起 场景介绍 缓存是一种提高系统读性能的常见技术,对于读多写少的应用场景,我们经常使用缓存来进行优化. 例如对于用户的余额信息表account(uid, money),业务上的需求是: (1)查询用户的余额,SELECT money FROM account WHERE uid=XXX

专訪阿里陶辉:大规模分布式系统、高性能server设计经验分享

http://www.csdn.net/article/2014-06-27/2820432 摘要:先后就职于在国内知名的互联网公司,眼下在阿里云弹性计算部门做架构设计与核心模块代码的编写,主要负责云server管理系统和存储系统的优化.陶辉就大规模分布式系统.高性能server设计分享了自己的看法. 关注陶辉非常长时间,初次对陶辉的了解还是在我们CSDN的博客上,从2007年開始写博客,一直到如今,假设不是对技术的追求和热爱,以及热爱分享的精神,我想不是非常多人能坚持下来,拥有多年大型互联网公

【58沈剑架构系列】缓存架构设计细节二三事

本文主要讨论这么几个问题: (1)“缓存与数据库”需求缘起 (2)“淘汰缓存”还是“更新缓存” (3)缓存和数据库的操作时序 (4)缓存和数据库架构简析   一.需求缘起 场景介绍 缓存是一种提高系统读性能的常见技术,对于读多写少的应用场景,我们经常使用缓存来进行优化. 例如对于用户的余额信息表account(uid, money),业务上的需求是: (1)查询用户的余额,SELECT money FROM account WHERE uid=XXX,占99%的请求 (2)更改用户余额,UPDA

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

朱晔的互联网架构实践心得S1E9:架构评审一百问和设计文档五要素 [下载文本PDF进行阅读] 本文我会来说说我认为架构评审中应该看的一些点,以及我写设计文档的一些心得.助你在架构评审中过五关斩六将,助你写出能让人收藏点赞的设计文档. 技术架构评审 架构评审或技术方案评审的价值在于集众人的力量大家一起来分析看看方案里是否有坑,方案上线后是否会遇到不可逾越的重大技术问题,提前尽可能把一些事情先考虑到提出质疑其实对项目的健康发展有很大的好处.很多公司都有架构评审委员会都有架构评审的流程,做业务的兄弟要

缓存模块设计

NET 缓存模块设计 上一篇谈了我对缓存的概念,框架上的理解和看法,这篇承接上篇讲讲我自己的缓存模块设计实践. 基本的缓存模块设计 最基础的缓存模块一定有一个统一的CacheHelper,如下: public interface ICacheHelper { T Get<T>(string key); void Set<T>(string key, T value); void Remove(string key); } 然后业务层是这样调用的 public User Get(in

常见的缓存算法设计策略

对于缓存,大家应该都不会感到陌生,但是关于缓存算法有哪些,大家可能不会太清楚,这里我大概介绍下. 缓存的设计目的就是为了我们访问方便,减少访问时间,大体上有这四种策略: 一:基于时间的策略.当缓存未满的时候,一直向缓存区添加,当缓存区满的时候,再有数据进来,就需要将以访问过的数据清除掉. 清除的就是那些访问时间久的数据.说白了就是访问时间距离现在越远的将首先被淘汰. 二:基于频率的策略.当缓冲区满的时候,按照访问频率将数据进行排序,将那些访问频率较少的数据淘汰掉. 三:基于时间和频率的策略.当缓

JavaScript网站设计实践(五)编写photos.html页面,实现点击缩略图显示大图的效果

原文:JavaScript网站设计实践(五)编写photos.html页面,实现点击缩略图显示大图的效果 一.photos.html页面,点击每一张缩略图,就在占位符的位置那里,显示对应的大图. 看到的页面效果是这样的: 1.实现思路 这个功能在之前的JavaScript美术馆那里已经实现了. 首先在页面中使用ul列表显示出所有的缩略图,然后使用JavaScript,for循环检查出当前点击的是哪一张图片,最后把这张图片给显示出来. 用到三个函数:显示图片函数.创建占位符预装图片.点击显示图片

软件设计(第五章)

1.软件系统 2.分解为子系统或包 识别出所有的子系统,包括:确定如何把系统分为主要的子系统,并清楚定义子系统如何使用其他子系统. 子系统之间的交互:一个子系统去调用另外一个子系统的子程序:一个子系统包含另一个子系统中的类:一个子系统继承自另外一个子系统中的类: 常用的子系统:业务规则,用户界面,数据库访问,对系统的依赖性 3.分解为类 识别出系统中所有的类 定义这些类与系统的其余部分打交道的细节 4.分解为子程序 把每个类细分为子程序,将第3步中定义的类接口,细化为类的私用子程序 5.子程序内