全部博文(7)
2013年(7)
分类: LINUX
2013-08-01 20:53:30
对于CDN的cache服务器而言,减少回源,提高命中率是一个重要的功能,尤其是在处理大文件的时候。这次我们就讲一讲squid是怎样让尽可能多的对于相同url的请求共用同一个回源请求的。
当然,如果一个object已经完整地存在了squid的磁盘上,在它过期之前是不会回源的。我们要讨论的是正在从原站下载过程中的object,当另外一个客户端来下载它的时候。其他cache软件可能会忽略当前正在下载的object,而另起一个回源请求去下载。这样的话,在第一个客户端完整地下完这个object的时候,cache会回源多次。而squid则可以将这些请求合并起来,用一个回源请求服务所有的客户端。
Squid合并回源请求,主要靠的就是store_client层。
Store_client层的工作原理是:当有多个request请求文件的不同部分时,store_client层会对他们采取不同的处理。
图1表示一个1G的object被多个请求同时访问的情形。
request1是第一个访问这个object的请求,由他发起了回源,现在回源已经下载了200M的内容;
request2是一个从头开始的请求,它开始得比较晚,只请求了100M
request3是一个range请求,它请求的是还没有从原站下到的部分;
图 1
注意,虽然是request1发起了回源,但回源请求并不是request1本身,而是由fwdStart函数发起的一个单独的回源请求。即使request1断掉了,回源也是可以继续的,直到下载完整个object为止。
这3个请求都要通过store_client层的storeClientCopy函数从store拿数据。
request1的copy_offset是199M,而从150M到200M的数据全都在内存里,因此它进入的分支在storeClientCopy3函数里,它是:
request2的copy_offset是100M,比inmem_lo要低,它进入的分支很简单,就是storeClientCopy3函数的最后一行:
storeClientFileRead会调用storeRead,进入多线程的aufs异步io,拿到文件内容之后还是会调用storeClientCallback回调。
request3很显然,数据在磁盘和内存都没有。所以它所能做的就是等待了。它的分支是
点击(此处)折叠或打开
既没有读磁盘,也没有读内存,就return掉了。那么什么时候这个客户端会继续呢?就是当回源链接每从原站读到一块数据,会调用storeAppend,进而调用InvokeHandler,由于request2的store_client是当前storeEntry的client之一,invokeHandler会对request2的store_client重新调用storeClientCopy2和storeClientCopy3,如果到了那个时候它所需要的数据在内存或硬盘了,客户端就会继续收到数据。
刚才说的是store_client层,它的作用是,管理已经在访问同一个object的所有客户端,让他们取到各自需要的数据。
那么,怎样让所有的客户端访问到同一个object呢?
squid会为每一个object创建一个StoreEntry结构,并放到store_hash这个哈希表中,供后来的request查找。当request1从原站下载到了数据时,它的StoreEntry肯定是已经创建好了。这个阶段要让其他请求找到这个object是很容易的。
但问题就是request1的客户端请求已经发到了squid,而原站数据还没有下载到的这个期间,如果其他请求来了怎么办呢?其实squid为这个问题引入了一个配置项,叫做collapsed_forwarding,可以on或者off。这个配置项的意思就是,所有请求共用一个回源请求。
当配制成on的时候,只要request1的客户端请求发到了,这时候url也知道了,就立即创建它的StoreEntry,并放到hash表中。即使回源的数据还没拿到,其他请求也能找到它的StoreEntry。
但是,这时候就有一个问题了,万一request1请求的是动态内容怎么办?难道让其他请求也拿到跟request1一样的内容么?
当然不会!squid在clientCacheHit中做了防范。clientCacheHit是当一个回调函数,在请求hit了,并拿到reply头的时候由storeClientCallback回调。
假如request2就是一个这样的请求,它发生在在request1拿到原站的数据之前,并找到了request1创建的StoreEntry,而且“以为”自己hit了。而回源的reply中有Cache-Control:no-cache,那么当request2在进入clientCacheHit时,它找到的StoreEntry中一定会有RELEASE_REQUEST这个标识。这个标识是httpCachableReply函数发现no-cache之后设置的。当发现这个标识的时候,就会从clientCacheHit转入clientProcessMiss,重新回源,不会跟request1取到相同的数据。代码如下
当配制成off的时候,与on相反,当request1发过来的时候,不立刻将 StoreEntry放到hash表中,只有当回源请求拿到了reply头,确认是可缓存的内容后,才将StoreEntry放到hash表中。
虽然回源拿到响应头的时间通常很短,但终究有可能发生“误会”,request1和request2都回源了。那么这种情况下会不会两个回源请求产生冲突,比如写同一个cache文件之类的问题呢?
其实也不会。squid对这种情况也做了防范。
在发现一个object可缓存时,会调用httpMakePublic,进而调用StoreSetPublicKey。StoreSetPublicKey主要是负责将一个StoreEntry放入hash表。这时候它会先检查hash表中是否已经有了key相同的object,如果有,要先删除旧的再插入新的。
删除旧的object之后,request1和request2的回源都不会停止,以保证客户端下到完整的数据。
那么,它们会不会写到相同的文件里呢?
还是不会。因为squid为新的object分配文件名不是直接将key转换为文件名,而是用一个file bitmap来分配一个文件号,用这个文件号来生成文件名。分配过的文件号是不会被再次分配出去的。
还有最后一个问题,就算文件名不同,request1回源取到的数据还会不会被写到磁盘上呢?
看到这里,你会知道squid是很聪明的,所以答案当然还是不会了。因为旧的entry一旦被打上RELEASE_REQUEST标,同时会清掉ENTRY_CACHABLE标,它的swapout就不会再进行了。这是在storeSwapOutMaintainMemObject里保证的。
其中storeSwapOutAble的定义如下:
4. 思考题
最后,请大家想一想,什么样的业务模式适合collapsed_forwarding on,什么样的适合off呢?