缓存服务器设计与实现(六)
发表于2016-10-29
基本原理
缓存系统中的文件,从无到有是被动产生的。初始状态,缓存系统中是空的,请求过来之后,缓存会回源取,然后存在本地。而不像web服务器,文件是通过其他的手段(传统的是通过ftp上传)来创建的,这个创建文件的过程对web服务器来说是无感知的,请求到服务器以后,server会按照url的路径跟服务器配置的docroot来到本机的文件系统里去定位相应的文件,如果存在,那么回应200 OK,并发送文件内容,如果不存在,则回应404 Not Found。相对来说,缓存服务器自身是不会给响应404的,除非它取源取到的就是404回应。
文件一旦被缓存服务器创建,将一直存在。在没有外界干涉的情况下,文件有两种消亡方式。第一就是文件的缓存时间到了。因为任何一个被缓存的内容,都是有保鲜时间的。保鲜时间要么是源给指定的(Expires等相关字段),要么源没有指定,由CDN缓存服务器自身策略给指定的默认时间。文件一旦过期,缓存就会回源验证原始文件是否被更新了,如果没有更新,源站会给出304回应。更新了的话,则会给200 ok,并回传相应文件内容。第二就是缓存服务器本身存储空间达到了上限,这个时候缓存就会通过自己的LRU机制来淘汰“较冷”的内容。
除了这两种情况外,还有一种特殊情况,就是当前缓存的文件还没有过期,但是此刻源站已经修改了文件。那么在cdn缓存中的文件过期之前,用户都不能访问到最新的内容,这个时候就需要通过外界来干预。这里就用到了一种http请求,方法名叫PURGE。这个方法几乎已经是缓存里的跟GET,HEAD,POST处于相同地位的重要方法。PURGE请求告诉缓存服务器,该url对应的文件需要从系统里删除。删除以后,再来的请求,就会由于缓存里不存在相应文件而去取源。
PURGE请求的产生一般来自CDN系统内部,用户会通过一些CDN提供的任务系统来告知CDN,自己需要删除哪些文件。CDN会通过一定的方式来获取该任务并生成PURGE请求,来向对应缓存服务平台,发起PURGE请求。在缓存系统的配置里,一般都会对PURGE请求的发起方的ip做限制,如果不做限制,那么后果可想而知。
PURGE请求如果找到了文件,并执行了删除,那么会收到200 OK。如果文件还不存在,那么会收到404 Not Found。这是一般缓存服务器默认的行为。
PURGE请求到达缓存服务器之后,处理流程在全半段跟GET类似,也是根据一定的方式去定位文件(一般是用请求url的不同成分算出一个key),定位到文件之后,PURGE请求就跟GET不一样了。它会将文件标记为deleted(缓存里都是异步来实现淘汰的,因为当前要删除的文件,可能还有人在访问,不能立即将相关信息删除。当文件引用计数为0,并且状态为deleted时,文件自然会被系统干掉),来淘汰掉一个文件。
多副本问题
如果仅仅通过url的可以来PURGE文件,那么当存在多副本(同一url对应不同形式的文件,最常见的是压缩跟非压缩)的情况下,就存在问题。所以对于缓存存在多副本的情况下,有两种处理方式,一种就是只删除特定副本(例如压缩和非压缩的某一种),一种情况就是删除所有的副本。在实际运作过程中,第二种是默认的。如果处理第二种,那么就需要在key匹配的基础上做进一步的处理。在很多的简单缓存实现里,多副本存放在某个hash表槽位的冲突链上(因为key相同),那么在处理时就需要遍历整个链,每个副本都需要删除。
nginx的第三方purge模块在处理多副本的问题上存在缺陷,导致单纯的使用url来purge会导致不同的副本不能被删除干净。当然出现这个情况的前提是,nginx的缓存开启了vary功能,而该purge模块又没有对vary提供必要的支持。对于nginx出现的情况,很多小伙伴在发PURGE请求时,都加入了相应的Accept-Encoding头。这样就可以清理相应的压缩副本了。
关于多副本和Vary头的问题,后面单独来讲吧
分布式问题
这个算是,更广义上的多副本问题。一般CDN都存在多层结构,用户直接访问到的缓存服务器,叫做边缘层,边缘层到源站之间一般还有一到两级的父层。层级越多,回源量就越少,随之latency会增加。那么当我们要删除一个文件,如果先PURGE了边缘cache,那么后续请求就会穿过边缘cache到达父层,这时由于父层的旧文件还存在,那么边缘又会将旧文件取走,白忙活。所以面对这种带有层级的缓存架构,一般都是从父层往边缘逐级PURGE文件。
那么所有的问题都解决了?不是。很多CDN的缓存节点,边缘也好,父层也好。都是有集群的。比如几台机器做的集群,假设有A,B,C,D四台。一般缓存服务器都会做基于七层的一致性hash,来保证增删集群内的机器不会带来过大的内容迁移。我们看一种情形,如果文件1.html存在C机器,当C重启下线,那么这时你通过http向集群(往往只是一个vip)发起PURGE请求来删除1.html,由于C机器的下线,文件在集群内就不存在了。你收到404 Not Found,PURGR请求处理完成。随后,C机器又活过来,重新加入了集群。那么旧的1.html文件,又再次出现在系统内,便带来了严重的问题。
上面讨论的这个情景,在http协议下是无解的。必须引入其他的手段来处理。解决方案有很多,首先不能针对VIP发PURGE请求,要对每一个集群机器内搜有机器的真实IP发PURGE。这样就不会受集群架构的影响,每个机器的执行情况可以得到。对应执行结果要有监控,状态无非三种,成功(200 OK或者404 Not Found),失败(其他非预期的响应码),无响应(宕机了)。上层需要收集这些执行情况,并做汇总。这样才可以保证文件被彻底处理干净。
那么,PURGE任务的下发和消费,比较好的方案,应该就是消息队列了。大家可以自行去研究。
腾讯GAD游戏程序交流群:484290331